diff --git a/src/main/java/com/conveyal/gtfs/GTFSFeed.java b/src/main/java/com/conveyal/gtfs/GTFSFeed.java index fe182a8e8..b468e9afb 100644 --- a/src/main/java/com/conveyal/gtfs/GTFSFeed.java +++ b/src/main/java/com/conveyal/gtfs/GTFSFeed.java @@ -110,6 +110,16 @@ public class GTFSFeed implements Cloneable, Closeable { /* A place to store an event bus that is passed through constructor. */ public transient EventBus eventBus; + public final Map areas; + public final Map stop_areas; + public final Map fare_products; + public final Map fare_medias; + public final Map time_frames; + public final Map fare_leg_rules; + public final Map fare_transfer_rules; + public final Map networks; + public final Map route_networks; + /** * The order in which we load the tables is important for two reasons. * 1. We must load feed_info first so we know the feed ID before loading any other entities. This could be relaxed @@ -177,6 +187,18 @@ else if (feedId == null || feedId.isEmpty()) { new Trip.Loader(this).loadTable(zip); new Frequency.Loader(this).loadTable(zip); new StopTime.Loader(this).loadTable(zip); // comment out this line for quick testing using NL feed + + // Fares v2. + new Area.Loader(this).loadTable(zip); + new StopArea.Loader(this).loadTable(zip); + new TimeFrame.Loader(this).loadTable(zip); + new Network.Loader(this).loadTable(zip); + new RouteNetwork.Loader(this).loadTable(zip); + new FareMedia.Loader(this).loadTable(zip); + new FareProduct.Loader(this).loadTable(zip); + new FareLegRule.Loader(this).loadTable(zip); + new FareTransferRule.Loader(this).loadTable(zip); + LOG.info("{} errors", errors.size()); for (GTFSError error : errors) { LOG.info("{}", error); @@ -219,6 +241,17 @@ public void toFile (String file) { new StopTime.Writer(this).writeTable(zip); new Pattern.Writer(this).writeTable(zip); + // Fares v2. + new Area.Writer(this).writeTable(zip); + new StopArea.Writer(this).writeTable(zip); + new TimeFrame.Writer(this).writeTable(zip); + new Network.Writer(this).writeTable(zip); + new RouteNetwork.Writer(this).writeTable(zip); + new FareMedia.Writer(this).writeTable(zip); + new FareProduct.Writer(this).writeTable(zip); + new FareLegRule.Writer(this).writeTable(zip); + new FareTransferRule.Writer(this).writeTable(zip); + zip.close(); LOG.info("GTFS file written"); @@ -608,8 +641,15 @@ private GTFSFeed (DB db) { this.db = db; agency = db.getTreeMap("agency"); + areas = db.getTreeMap("area"); + fare_leg_rules = db.getTreeMap("fare_leg_rules"); + fare_medias = db.getTreeMap("fare_medias"); + fare_products = db.getTreeMap("fare_products"); + fare_transfer_rules = db.getTreeMap("fare_transfer_rules"); feedInfo = db.getTreeMap("feed_info"); + networks = db.getTreeMap("networks"); routes = db.getTreeMap("routes"); + route_networks = db.getTreeMap("route_networks"); trips = db.getTreeMap("trips"); stop_times = db.getTreeMap("stop_times"); frequencies = db.getTreeSet("frequencies"); @@ -618,6 +658,8 @@ private GTFSFeed (DB db) { fares = db.getTreeMap("fares"); services = db.getTreeMap("services"); shape_points = db.getTreeMap("shape_points"); + stop_areas = db.getTreeMap("stop_areas"); + time_frames = db.getTreeMap("time_frames"); translations = db.getTreeMap("translations"); attributions = db.getTreeMap("attributions"); diff --git a/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsFaresV2Schema.java b/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsFaresV2Schema.java new file mode 100644 index 000000000..f1092a3d5 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsFaresV2Schema.java @@ -0,0 +1,171 @@ +package com.conveyal.gtfs.graphql; + +import com.conveyal.gtfs.graphql.fetchers.MapFetcher; +import com.conveyal.gtfs.model.Area; +import com.conveyal.gtfs.model.FareLegRule; +import com.conveyal.gtfs.model.FareMedia; +import com.conveyal.gtfs.model.FareProduct; +import com.conveyal.gtfs.model.FareTransferRule; +import com.conveyal.gtfs.model.Network; +import com.conveyal.gtfs.model.Route; +import com.conveyal.gtfs.model.RouteNetwork; +import com.conveyal.gtfs.model.StopArea; +import com.conveyal.gtfs.model.TimeFrame; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLObjectType; + +import java.util.Arrays; +import java.util.List; + +import static com.conveyal.gtfs.graphql.GraphQLUtil.createFieldDefinition; +import static graphql.Scalars.GraphQLInt; +import static graphql.schema.GraphQLObjectType.newObject; + +public class GraphQLGtfsFaresV2Schema { + + private static final String AREA_TYPE_NAME = "area"; + private static final String STOP_AREA_TYPE_NAME = "stop_area"; + private static final String TIME_FRAME_TYPE_NAME = "time_frame"; + private static final String NETWORK_TYPE_NAME = "network"; + private static final String ROUTE_NETWORK_TYPE_NAME = "route_network"; + private static final String FARE_MEDIA_TYPE_NAME = "fare_media"; + private static final String FARE_PRODUCT_TYPE_NAME = "fare_product"; + private static final String FARE_LEG_RULE_TYPE_NAME = "fare_leg_rule"; + private static final String FARE_TRANSFER_RULE_TYPE_NAME = "fare_transfer_rule"; + + private GraphQLGtfsFaresV2Schema() {} + + public static final GraphQLObjectType stopAreaType = newObject().name(STOP_AREA_TYPE_NAME) + .description("A GTFS stop area object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(StopArea.AREA_ID_NAME)) + .field(MapFetcher.field(StopArea.STOP_ID_NAME)) + .field(createFieldDefinition("stops", GraphQLGtfsSchema.stopType, "stops", StopArea.STOP_ID_NAME)) + .build(); + + public static final GraphQLObjectType areaType = newObject().name(AREA_TYPE_NAME) + .description("A GTFS area object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(Area.AREA_ID_NAME)) + .field(MapFetcher.field(Area.AREA_NAME_NAME)) + .field(createFieldDefinition("stop_areas", stopAreaType, StopArea.TABLE_NAME, Area.AREA_ID_NAME)) + .build(); + + public static final GraphQLObjectType timeFrameType = newObject().name(TIME_FRAME_TYPE_NAME) + .description("A GTFS time frame object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(TimeFrame.TIME_FRAME_GROUP_ID_NAME)) + .field(MapFetcher.field(TimeFrame.START_TIME_NAME)) + .field(MapFetcher.field(TimeFrame.END_TIME_NAME)) + .field(MapFetcher.field(TimeFrame.SERVICE_ID_NAME)) + .build(); + + public static final GraphQLObjectType networkType = newObject().name(NETWORK_TYPE_NAME) + .description("A GTFS network object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(Network.NETWORK_ID_NAME)) + .field(MapFetcher.field(Network.NETWORK_NAME_NAME)) + .build(); + + public static final GraphQLObjectType routeNetworkType = newObject().name(ROUTE_NETWORK_TYPE_NAME) + .description("A GTFS route network object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(RouteNetwork.NETWORK_ID_NAME)) + .field(MapFetcher.field(RouteNetwork.ROUTE_ID_NAME)) + .field(createFieldDefinition("networks", networkType, Network.TABLE_NAME, RouteNetwork.NETWORK_ID_NAME)) + .build(); + + public static final GraphQLObjectType fareMediaType = newObject().name(FARE_MEDIA_TYPE_NAME) + .description("A GTFS fare media object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(FareMedia.FARE_MEDIA_ID_NAME)) + .field(MapFetcher.field(FareMedia.FARE_MEDIA_NAME_NAME)) + .field(MapFetcher.field(FareMedia.FARE_MEDIA_TYPE_NAME)) + .build(); + + public static final GraphQLObjectType fareProductType = newObject().name(FARE_PRODUCT_TYPE_NAME) + .description("A GTFS fare product object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(FareProduct.FARE_PRODUCT_ID_NAME)) + .field(MapFetcher.field(FareProduct.FARE_PRODUCT_NAME_NAME)) + .field(MapFetcher.field(FareProduct.FARE_MEDIA_ID_NAME)) + .field(MapFetcher.field(FareProduct.AMOUNT_NAME)) + .field(MapFetcher.field(FareProduct.CURRENCY_NAME)) + .field(createFieldDefinition(FARE_MEDIA_TYPE_NAME, fareMediaType, FareMedia.TABLE_NAME, FareProduct.FARE_MEDIA_ID_NAME)) + .build(); + + public static final GraphQLObjectType fareLegRuleType = newObject().name(FARE_LEG_RULE_TYPE_NAME) + .description("A GTFS fare leg rule object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(FareLegRule.LEG_GROUP_ID_NAME)) + .field(MapFetcher.field(FareLegRule.NETWORK_ID_NAME)) + .field(MapFetcher.field(FareLegRule.FROM_AREA_ID_NAME)) + .field(MapFetcher.field(FareLegRule.TO_AREA_ID_NAME)) + .field(MapFetcher.field(FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME)) + .field(MapFetcher.field(FareLegRule.TO_TIMEFRAME_GROUP_ID_NAME)) + .field(MapFetcher.field(FareLegRule.FARE_PRODUCT_ID_NAME)) + .field(MapFetcher.field(FareLegRule.RULE_PRIORITY_NAME)) + // Will return either routes or networks, not both. + .field(createFieldDefinition("routes", GraphQLGtfsSchema.routeType, Route.TABLE_NAME, FareLegRule.NETWORK_ID_NAME)) + .field(createFieldDefinition("networks", networkType, Network.TABLE_NAME, Network.NETWORK_ID_NAME)) + .field(createFieldDefinition("fare_products", fareProductType, FareProduct.TABLE_NAME, FareLegRule.FARE_PRODUCT_ID_NAME)) + // fromTimeFrame and toTimeFrame may return multiple time frames. + .field(createFieldDefinition( + "from_time_frame", + timeFrameType, + TimeFrame.TABLE_NAME, + FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME, + TimeFrame.TIME_FRAME_GROUP_ID_NAME + )) + .field(createFieldDefinition( + "to_time_frame", + timeFrameType, + TimeFrame.TABLE_NAME, + FareLegRule.TO_TIMEFRAME_GROUP_ID_NAME, + TimeFrame.TIME_FRAME_GROUP_ID_NAME + )) + .field(createFieldDefinition("to_area", areaType, Area.TABLE_NAME, FareLegRule.TO_AREA_ID_NAME, Area.AREA_ID_NAME)) + .field(createFieldDefinition("from_area", areaType, Area.TABLE_NAME, FareLegRule.FROM_AREA_ID_NAME, Area.AREA_ID_NAME)) + .build(); + + public static final GraphQLObjectType fareTransferRuleType = newObject().name(FARE_TRANSFER_RULE_TYPE_NAME) + .description("A GTFS fare transfer rule object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field(FareTransferRule.FROM_LEG_GROUP_ID_NAME)) + .field(MapFetcher.field(FareTransferRule.TO_LEG_GROUP_ID_NAME)) + .field(MapFetcher.field(FareTransferRule.TRANSFER_COUNT_NAME)) + .field(MapFetcher.field(FareTransferRule.DURATION_LIMIT_NAME)) + .field(MapFetcher.field(FareTransferRule.DURATION_LIMIT_TYPE_NAME)) + .field(MapFetcher.field(FareTransferRule.FARE_PRODUCT_ID_NAME)) + .field(createFieldDefinition("to_area", areaType, Area.TABLE_NAME, FareLegRule.TO_AREA_ID_NAME, Area.AREA_ID_NAME)) + .field(createFieldDefinition("fare_products", fareProductType, FareProduct.TABLE_NAME, FareProduct.FARE_PRODUCT_ID_NAME)) + .field(createFieldDefinition( + "from_fare_leg_rule", + fareLegRuleType, + FareLegRule.TABLE_NAME, + FareTransferRule.FROM_LEG_GROUP_ID_NAME, + FareLegRule.LEG_GROUP_ID_NAME + )) + .field(createFieldDefinition( + "to_fare_leg_rule", + fareLegRuleType, + FareLegRule.TABLE_NAME, + FareTransferRule.TO_LEG_GROUP_ID_NAME, + FareLegRule.LEG_GROUP_ID_NAME + )) + .build(); + + public static List getFaresV2FieldDefinitions() { + return Arrays.asList( + createFieldDefinition(AREA_TYPE_NAME, areaType, Area.TABLE_NAME), + createFieldDefinition(FARE_LEG_RULE_TYPE_NAME, fareLegRuleType, FareLegRule.TABLE_NAME), + createFieldDefinition(FARE_MEDIA_TYPE_NAME, fareMediaType, FareMedia.TABLE_NAME), + createFieldDefinition(FARE_PRODUCT_TYPE_NAME, fareProductType, FareProduct.TABLE_NAME), + createFieldDefinition(FARE_TRANSFER_RULE_TYPE_NAME, fareTransferRuleType, FareTransferRule.TABLE_NAME), + createFieldDefinition(NETWORK_TYPE_NAME, networkType, Network.TABLE_NAME), + createFieldDefinition(ROUTE_NETWORK_TYPE_NAME, routeNetworkType, RouteNetwork.TABLE_NAME), + createFieldDefinition(STOP_AREA_TYPE_NAME, stopAreaType, StopArea.TABLE_NAME), + createFieldDefinition(TIME_FRAME_TYPE_NAME, timeFrameType, TimeFrame.TABLE_NAME) + ); + } +} diff --git a/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsSchema.java b/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsSchema.java index 6c4032dc2..af233ed1f 100644 --- a/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsSchema.java +++ b/src/main/java/com/conveyal/gtfs/graphql/GraphQLGtfsSchema.java @@ -10,6 +10,7 @@ import com.conveyal.gtfs.graphql.fetchers.SQLColumnFetcher; import com.conveyal.gtfs.graphql.fetchers.SourceObjectFetcher; import graphql.schema.Coercing; +import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLScalarType; @@ -18,8 +19,11 @@ import java.sql.Array; import java.sql.SQLException; +import java.util.List; +import static com.conveyal.gtfs.graphql.GraphQLUtil.createFieldDefinition; import static com.conveyal.gtfs.graphql.GraphQLUtil.floatArg; +import static com.conveyal.gtfs.graphql.GraphQLUtil.buildArgs; import static com.conveyal.gtfs.graphql.GraphQLUtil.intArg; import static com.conveyal.gtfs.graphql.GraphQLUtil.intt; import static com.conveyal.gtfs.graphql.GraphQLUtil.multiStringArg; @@ -58,37 +62,38 @@ public class GraphQLGtfsSchema { // by using static fields to hold these types, backward references are enforced. a few forward references are inserted explicitly. // Represents rows from agency.txt - public static final GraphQLObjectType agencyType = newObject().name("agency") - .description("A GTFS agency object") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("agency_id")) - .field(MapFetcher.field("agency_name")) - .field(MapFetcher.field("agency_url")) - .field(MapFetcher.field("agency_branding_url")) - .field(MapFetcher.field("agency_phone")) - .field(MapFetcher.field("agency_email")) - .field(MapFetcher.field("agency_lang")) - .field(MapFetcher.field("agency_fare_url")) - .field(MapFetcher.field("agency_timezone")) - .build(); + public static final GraphQLObjectType agencyType = newObject() + .name("agency") + .description("A GTFS agency object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("agency_id")) + .field(MapFetcher.field("agency_name")) + .field(MapFetcher.field("agency_url")) + .field(MapFetcher.field("agency_branding_url")) + .field(MapFetcher.field("agency_phone")) + .field(MapFetcher.field("agency_email")) + .field(MapFetcher.field("agency_lang")) + .field(MapFetcher.field("agency_fare_url")) + .field(MapFetcher.field("agency_timezone")) + .build(); // Represents rows from calendar.txt public static final GraphQLObjectType calendarType = newObject() - .name("calendar") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("service_id")) - .field(MapFetcher.field("monday", GraphQLInt)) - .field(MapFetcher.field("tuesday", GraphQLInt)) - .field(MapFetcher.field("wednesday", GraphQLInt)) - .field(MapFetcher.field("thursday", GraphQLInt)) - .field(MapFetcher.field("friday", GraphQLInt)) - .field(MapFetcher.field("saturday", GraphQLInt)) - .field(MapFetcher.field("sunday", GraphQLInt)) - .field(MapFetcher.field("start_date")) - .field(MapFetcher.field("end_date")) - // FIXME: Description is an editor-specific field - .field(MapFetcher.field("description")) - .build(); + .name("calendar") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("service_id")) + .field(MapFetcher.field("monday", GraphQLInt)) + .field(MapFetcher.field("tuesday", GraphQLInt)) + .field(MapFetcher.field("wednesday", GraphQLInt)) + .field(MapFetcher.field("thursday", GraphQLInt)) + .field(MapFetcher.field("friday", GraphQLInt)) + .field(MapFetcher.field("saturday", GraphQLInt)) + .field(MapFetcher.field("sunday", GraphQLInt)) + .field(MapFetcher.field("start_date")) + .field(MapFetcher.field("end_date")) + // FIXME: Description is an editor-specific field + .field(MapFetcher.field("description")) + .build(); private static final GraphQLScalarType stringList = GraphQLScalarType .newScalar() @@ -98,180 +103,144 @@ public class GraphQLGtfsSchema { .build(); // Represents GTFS Editor service exceptions. - public static final GraphQLObjectType scheduleExceptionType = newObject().name("scheduleException") - .description("A GTFS Editor schedule exception type") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("name")) - .field(MapFetcher.field("exemplar", GraphQLInt)) - .field(MapFetcher.field("dates", stringList)) - .field(MapFetcher.field("custom_schedule", stringList)) - .field(MapFetcher.field("added_service", stringList)) - .field(MapFetcher.field("removed_service", stringList)) - .build(); + public static final GraphQLObjectType scheduleExceptionType = newObject() + .name("scheduleException") + .description("A GTFS Editor schedule exception type") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("name")) + .field(MapFetcher.field("exemplar", GraphQLInt)) + .field(MapFetcher.field("dates", stringList)) + .field(MapFetcher.field("custom_schedule", stringList)) + .field(MapFetcher.field("added_service", stringList)) + .field(MapFetcher.field("removed_service", stringList)) + .build(); // Represents rows from fare_rules.txt - public static final GraphQLObjectType fareRuleType = newObject().name("fareRule") - .description("A GTFS agency object") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("fare_id")) - .field(MapFetcher.field("route_id")) - .field(MapFetcher.field("origin_id")) - .field(MapFetcher.field("destination_id")) - .field(MapFetcher.field("contains_id")) - .build(); + public static final GraphQLObjectType fareRuleType = newObject() + .name("fareRule") + .description("A GTFS agency object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("fare_id")) + .field(MapFetcher.field("route_id")) + .field(MapFetcher.field("origin_id")) + .field(MapFetcher.field("destination_id")) + .field(MapFetcher.field("contains_id")) + .build(); // Represents rows from fare_attributes.txt - public static final GraphQLObjectType fareType = newObject().name("fare_attributes") - .description("A GTFS agency object") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("agency_id")) - .field(MapFetcher.field("fare_id")) - .field(MapFetcher.field("price", GraphQLFloat)) - .field(MapFetcher.field("currency_type")) - .field(MapFetcher.field("payment_method", GraphQLInt)) - .field(MapFetcher.field("transfers", GraphQLInt)) - .field(MapFetcher.field("transfer_duration", GraphQLInt)) - .field(newFieldDefinition() - .name("fare_rules") - .type(new GraphQLList(fareRuleType)) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher("fare_rules", "fare_id")) - .build() - ) - .build(); + public static final GraphQLObjectType fareType = newObject() + .name("fare_attributes") + .description("A GTFS agency object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("agency_id")) + .field(MapFetcher.field("fare_id")) + .field(MapFetcher.field("price", GraphQLFloat)) + .field(MapFetcher.field("currency_type")) + .field(MapFetcher.field("payment_method", GraphQLInt)) + .field(MapFetcher.field("transfers", GraphQLInt)) + .field(MapFetcher.field("transfer_duration", GraphQLInt)) + .field(createFieldDefinition("fare_rules", fareRuleType, "fare_rules", "fare_id")) + .build(); // Represents feed_info.txt - public static final GraphQLObjectType feedInfoType = newObject().name("feed_info") - .description("A GTFS feed_info object") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("feed_id")) - .field(MapFetcher.field("feed_contact_email")) - .field(MapFetcher.field("feed_contact_url")) - .field(MapFetcher.field("feed_publisher_name")) - .field(MapFetcher.field("feed_publisher_url")) - .field(MapFetcher.field("feed_lang")) - .field(MapFetcher.field("default_lang")) - .field(MapFetcher.field("feed_start_date")) - .field(MapFetcher.field("feed_end_date")) - .field(MapFetcher.field("feed_version")) - // Editor-specific fields - .field(MapFetcher.field("default_route_color")) - .field(MapFetcher.field("default_route_type")) - .build(); + public static final GraphQLObjectType feedInfoType = newObject() + .name("feed_info") + .description("A GTFS feed_info object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("feed_id")) + .field(MapFetcher.field("feed_contact_email")) + .field(MapFetcher.field("feed_contact_url")) + .field(MapFetcher.field("feed_publisher_name")) + .field(MapFetcher.field("feed_publisher_url")) + .field(MapFetcher.field("feed_lang")) + .field(MapFetcher.field("default_lang")) + .field(MapFetcher.field("feed_start_date")) + .field(MapFetcher.field("feed_end_date")) + .field(MapFetcher.field("feed_version")) + // Editor-specific fields + .field(MapFetcher.field("default_route_color")) + .field(MapFetcher.field("default_route_type")) + .build(); // Represents rows from shapes.txt - public static final GraphQLObjectType shapePointType = newObject().name("shapePoint") - .field(MapFetcher.field("shape_id")) - .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) - .field(MapFetcher.field("shape_pt_lat", GraphQLFloat)) - .field(MapFetcher.field("shape_pt_lon", GraphQLFloat)) - .field(MapFetcher.field("shape_pt_sequence", GraphQLInt)) - .field(MapFetcher.field("point_type", GraphQLInt)) - .build(); + public static final GraphQLObjectType shapePointType = newObject() + .name("shapePoint") + .field(MapFetcher.field("shape_id")) + .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) + .field(MapFetcher.field("shape_pt_lat", GraphQLFloat)) + .field(MapFetcher.field("shape_pt_lon", GraphQLFloat)) + .field(MapFetcher.field("shape_pt_sequence", GraphQLInt)) + .field(MapFetcher.field("point_type", GraphQLInt)) + .build(); // Represents a set of rows from shapes.txt joined by shape_id - public static final GraphQLObjectType shapeEncodedPolylineType = newObject().name("shapeEncodedPolyline") + public static final GraphQLObjectType shapeEncodedPolylineType = newObject() + .name("shapeEncodedPolyline") .field(string("shape_id")) .field(string("polyline")) .build(); // Represents rows from frequencies.txt - public static final GraphQLObjectType frequencyType = newObject().name("frequency") - .field(MapFetcher.field("trip_id")) - .field(MapFetcher.field("start_time", GraphQLInt)) - .field(MapFetcher.field("end_time", GraphQLInt)) - .field(MapFetcher.field("headway_secs", GraphQLInt)) - .field(MapFetcher.field("exact_times", GraphQLInt)) - .build(); + public static final GraphQLObjectType frequencyType = newObject() + .name("frequency") + .field(MapFetcher.field("trip_id")) + .field(MapFetcher.field("start_time", GraphQLInt)) + .field(MapFetcher.field("end_time", GraphQLInt)) + .field(MapFetcher.field("headway_secs", GraphQLInt)) + .field(MapFetcher.field("exact_times", GraphQLInt)) + .build(); // Represents rows from trips.txt public static final GraphQLObjectType tripType = newObject() - .name("trip") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("trip_id")) - .field(MapFetcher.field("trip_headsign")) - .field(MapFetcher.field("trip_short_name")) - .field(MapFetcher.field("block_id")) - .field(MapFetcher.field("direction_id", GraphQLInt)) - .field(MapFetcher.field("route_id")) - .field(MapFetcher.field("service_id")) - .field(MapFetcher.field("wheelchair_accessible", GraphQLInt)) - .field(MapFetcher.field("bikes_allowed", GraphQLInt)) - .field(MapFetcher.field("shape_id")) - .field(MapFetcher.field("pattern_id")) - .field(newFieldDefinition() - .name("stop_times") - // forward reference to the as yet undefined stopTimeType (must be defined - // after tripType) - .type(new GraphQLList(new GraphQLTypeReference("stopTime"))) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" - // nested types (i.e., nested types that typically would only be nested under - // another entity and only make sense with the entire set -- fares -> fare - // rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher( - "stop_times", - "trip_id", - "stop_sequence", - false)) - .build() - ) - .field(newFieldDefinition() - .name("frequencies") - // forward reference to the as yet undefined stopTimeType (must be defined after tripType) - .type(new GraphQLList(frequencyType)) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher("frequencies", "trip_id")) - .build() - ) - // TODO should this be included in the query? - .field(newFieldDefinition() - .name("shape") - .type(new GraphQLList(shapePointType)) - .dataFetcher(new JDBCFetcher("shapes", "shape_id")) - .build()) -// // some pseudo-fields to reduce the amount of data that has to be fetched over GraphQL to summarize -// .field(newFieldDefinition() -// .name("start_time") -// .type(GraphQLInt) -// .dataFetcher(TripDataFetcher::getStartTime) -// .build() -// ) -// .field(newFieldDefinition() -// .name("duration") -// .type(GraphQLInt) -// .dataFetcher(TripDataFetcher::getDuration) -// .build() -// ) - .build(); + .name("trip") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("trip_id")) + .field(MapFetcher.field("trip_headsign")) + .field(MapFetcher.field("trip_short_name")) + .field(MapFetcher.field("block_id")) + .field(MapFetcher.field("direction_id", GraphQLInt)) + .field(MapFetcher.field("route_id")) + .field(MapFetcher.field("service_id")) + .field(MapFetcher.field("wheelchair_accessible", GraphQLInt)) + .field(MapFetcher.field("bikes_allowed", GraphQLInt)) + .field(MapFetcher.field("shape_id")) + .field(MapFetcher.field("pattern_id")) + .field(newFieldDefinition() + .name("stop_times") + // forward reference to the as yet undefined stopTimeType (must be defined + // after tripType) + .type(new GraphQLList(new GraphQLTypeReference("stopTime"))) + .argument(intArg(LIMIT_ARG)) + .dataFetcher(new JDBCFetcher("stop_times", "trip_id", "stop_sequence", false)) + .build() + ) + .field(createFieldDefinition("frequencies", frequencyType, "frequencies", "trip_id")) + .field(createFieldDefinition("shape", shapePointType, "shapes", "shape_id")) + .build(); // Represents rows from stop_times.txt - public static final GraphQLObjectType stopTimeType = newObject().name("stopTime") - .field(MapFetcher.field("trip_id")) - .field(MapFetcher.field("stop_id")) - .field(MapFetcher.field("stop_sequence", GraphQLInt)) - .field(MapFetcher.field("arrival_time", GraphQLInt)) - .field(MapFetcher.field("departure_time", GraphQLInt)) - .field(MapFetcher.field("stop_headsign")) - .field(MapFetcher.field("timepoint", GraphQLInt)) - .field(MapFetcher.field("drop_off_type", GraphQLInt)) - .field(MapFetcher.field("pickup_type", GraphQLInt)) - .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) - .field(MapFetcher.field("continuous_pickup", GraphQLInt)) - .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) - .build(); + public static final GraphQLObjectType stopTimeType = newObject() + .name("stopTime") + .field(MapFetcher.field("trip_id")) + .field(MapFetcher.field("stop_id")) + .field(MapFetcher.field("stop_sequence", GraphQLInt)) + .field(MapFetcher.field("arrival_time", GraphQLInt)) + .field(MapFetcher.field("departure_time", GraphQLInt)) + .field(MapFetcher.field("stop_headsign")) + .field(MapFetcher.field("timepoint", GraphQLInt)) + .field(MapFetcher.field("drop_off_type", GraphQLInt)) + .field(MapFetcher.field("pickup_type", GraphQLInt)) + .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) + .field(MapFetcher.field("continuous_pickup", GraphQLInt)) + .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) + .build(); // Represents rows from attributions.txt - public static final GraphQLObjectType attributionsType = newObject().name("attributions") + public static final GraphQLObjectType attributionsType = newObject() + .name("attributions") .field(MapFetcher.field("attribution_id")) .field(MapFetcher.field("agency_id")) .field(MapFetcher.field("route_id")) @@ -286,7 +255,8 @@ public class GraphQLGtfsSchema { .build(); // Represents rows from translations.txt - public static final GraphQLObjectType translationsType = newObject().name("translations") + public static final GraphQLObjectType translationsType = newObject() + .name("translations") .field(MapFetcher.field("table_name")) .field(MapFetcher.field("field_name")) .field(MapFetcher.field("language")) @@ -297,133 +267,133 @@ public class GraphQLGtfsSchema { .build(); // Represents rows from routes.txt - public static final GraphQLObjectType routeType = newObject().name("route") - .description("A line from a GTFS routes.txt table") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("agency_id")) - .field(MapFetcher.field("route_id")) - .field(MapFetcher.field("route_short_name")) - .field(MapFetcher.field("route_long_name")) - .field(MapFetcher.field("route_desc")) - .field(MapFetcher.field("route_url")) - .field(MapFetcher.field("route_branding_url")) - .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) - .field(MapFetcher.field("continuous_pickup", GraphQLInt)) - .field(MapFetcher.field("route_type", GraphQLInt)) - .field(MapFetcher.field("route_color")) - .field(MapFetcher.field("route_text_color")) - // FIXME ˇˇ Editor fields that should perhaps be moved elsewhere. - .field(MapFetcher.field("wheelchair_accessible")) - .field(MapFetcher.field("publicly_visible", GraphQLInt)) - .field(MapFetcher.field("status", GraphQLInt)) - .field(MapFetcher.field("route_sort_order")) - // FIXME ^^ - .field(RowCountFetcher.field("trip_count", "trips", "route_id")) - .field(RowCountFetcher.field("pattern_count", "patterns", "route_id")) - .field(newFieldDefinition() - .name("stops") - .description("GTFS stop entities that the route serves") - // Field type should be equivalent to the final JDBCFetcher table type. - .type(new GraphQLList(new GraphQLTypeReference("stop"))) - // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. - .argument(stringArg("namespace")) - .argument(stringArg(SEARCH_ARG)) - .argument(intArg(LIMIT_ARG)) - // We allow querying only for a single stop, otherwise result processing can take a long time (lots - // of join queries). - .argument(stringArg("route_id")) - .dataFetcher(new NestedJDBCFetcher( - new JDBCFetcher("patterns", "route_id", null, false), - new JDBCFetcher("pattern_stops", "pattern_id", null, false), - new JDBCFetcher("stops", "stop_id"))) - .build()) - .field(newFieldDefinition() - .type(new GraphQLList(tripType)) - .name("trips") - .argument(multiStringArg("trip_id")) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .argument(stringArg(DATE_ARG)) - .argument(intArg(FROM_ARG)) - .argument(intArg(TO_ARG)) - .dataFetcher(new JDBCFetcher("trips", "route_id")) - .build() - ) - .field(newFieldDefinition() - .type(new GraphQLList(new GraphQLTypeReference("pattern"))) - .name("patterns") - .argument(intArg(LIMIT_ARG)) - .argument(multiStringArg("pattern_id")) - .dataFetcher(new JDBCFetcher("patterns", "route_id")) - .build() - ) - .field(RowCountFetcher.field("count", "routes")) - .build(); + public static final GraphQLObjectType routeType = newObject() + .name("route") + .description("A line from a GTFS routes.txt table") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("agency_id")) + .field(MapFetcher.field("route_id")) + .field(MapFetcher.field("route_short_name")) + .field(MapFetcher.field("route_long_name")) + .field(MapFetcher.field("route_desc")) + .field(MapFetcher.field("route_url")) + .field(MapFetcher.field("route_branding_url")) + .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) + .field(MapFetcher.field("continuous_pickup", GraphQLInt)) + .field(MapFetcher.field("route_type", GraphQLInt)) + .field(MapFetcher.field("route_color")) + .field(MapFetcher.field("route_text_color")) + // FIXME ˇˇ Editor fields that should perhaps be moved elsewhere. + .field(MapFetcher.field("wheelchair_accessible")) + .field(MapFetcher.field("publicly_visible", GraphQLInt)) + .field(MapFetcher.field("status", GraphQLInt)) + .field(MapFetcher.field("route_sort_order")) + // FIXME ^^ + .field(RowCountFetcher.field("trip_count", "trips", "route_id")) + .field(RowCountFetcher.field("pattern_count", "patterns", "route_id")) + .field(newFieldDefinition() + .name("stops") + .description("GTFS stop entities that the route serves") + // Field type should be equivalent to the final JDBCFetcher table type. + .type(new GraphQLList(new GraphQLTypeReference("stop"))) + // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. + .argument(stringArg("namespace")) + .argument(stringArg(SEARCH_ARG)) + .argument(intArg(LIMIT_ARG)) + // We allow querying only for a single stop, otherwise result processing can take a long time (lots + // of join queries). + .argument(stringArg("route_id")) + .dataFetcher(new NestedJDBCFetcher( + new JDBCFetcher("patterns", "route_id", null, false), + new JDBCFetcher("pattern_stops", "pattern_id", null, false), + new JDBCFetcher("stops", "stop_id"))) + .build()) + .field(newFieldDefinition() + .type(new GraphQLList(tripType)) + .name("trips") + .argument(multiStringArg("trip_id")) + .argument(intArg(LIMIT_ARG)) + .argument(stringArg(DATE_ARG)) + .argument(intArg(FROM_ARG)) + .argument(intArg(TO_ARG)) + .dataFetcher(new JDBCFetcher("trips", "route_id")) + .build() + ) + .field(newFieldDefinition() + .type(new GraphQLList(new GraphQLTypeReference("pattern"))) + .name("patterns") + .argument(intArg(LIMIT_ARG)) + .argument(multiStringArg("pattern_id")) + .dataFetcher(new JDBCFetcher("patterns", "route_id")) + .build() + ) + .field(RowCountFetcher.field("count", "routes")) + .field(MapFetcher.field("network_id")) + .build(); // Represents rows from stops.txt // Contains a reference to stopTimeType and routeType - public static final GraphQLObjectType stopType = newObject().name("stop") - .description("A GTFS stop object") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("stop_id")) - .field(MapFetcher.field("stop_name")) - .field(MapFetcher.field("stop_code")) - .field(MapFetcher.field("stop_desc")) - .field(MapFetcher.field("stop_lon", GraphQLFloat)) - .field(MapFetcher.field("stop_lat", GraphQLFloat)) - .field(MapFetcher.field("zone_id")) - .field(MapFetcher.field("stop_url")) - .field(MapFetcher.field("stop_timezone")) - .field(MapFetcher.field("parent_station")) - .field(MapFetcher.field("platform_code")) - .field(MapFetcher.field("location_type", GraphQLInt)) - .field(MapFetcher.field("wheelchair_boarding", GraphQLInt)) - // Returns all stops that reference parent stop's stop_id - .field(newFieldDefinition() - .name("child_stops") - .type(new GraphQLList(new GraphQLTypeReference("stop"))) - .dataFetcher(new JDBCFetcher( - "stops", - "stop_id", - null, - false, - "parent_station" - )) - .build()) - .field(RowCountFetcher.field("stop_time_count", "stop_times", "stop_id")) - .field(newFieldDefinition() - .name("patterns") - // Field type should be equivalent to the final JDBCFetcher table type. - .type(new GraphQLList(new GraphQLTypeReference("pattern"))) - .argument(stringArg("namespace")) - .dataFetcher(new NestedJDBCFetcher( - new JDBCFetcher("pattern_stops", "stop_id", null, false), - new JDBCFetcher("patterns", "pattern_id"))) - .build()) - .field(newFieldDefinition() - .name("routes") - // Field type should be equivalent to the final JDBCFetcher table type. - .type(new GraphQLList(routeType)) - .argument(stringArg("namespace")) - .argument(stringArg(SEARCH_ARG)) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new NestedJDBCFetcher( - new JDBCFetcher("pattern_stops", "stop_id", null, false), - new JDBCFetcher("patterns", "pattern_id", null, false), - new JDBCFetcher("routes", "route_id"))) - .build()) - .field(newFieldDefinition() - .name("stop_times") - // forward reference to the as yet undefined stopTimeType (must be defined after tripType) - .type(new GraphQLList(new GraphQLTypeReference("stopTime"))) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher("stop_times", "stop_id", "stop_sequence", false))) - .build(); + public static final GraphQLObjectType stopType = newObject() + .name("stop") + .description("A GTFS stop object") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("stop_id")) + .field(MapFetcher.field("stop_name")) + .field(MapFetcher.field("stop_code")) + .field(MapFetcher.field("stop_desc")) + .field(MapFetcher.field("stop_lon", GraphQLFloat)) + .field(MapFetcher.field("stop_lat", GraphQLFloat)) + .field(MapFetcher.field("zone_id")) + .field(MapFetcher.field("stop_url")) + .field(MapFetcher.field("stop_timezone")) + .field(MapFetcher.field("parent_station")) + .field(MapFetcher.field("platform_code")) + .field(MapFetcher.field("location_type", GraphQLInt)) + .field(MapFetcher.field("wheelchair_boarding", GraphQLInt)) + // Returns all stops that reference parent stop's stop_id + .field(newFieldDefinition() + .name("child_stops") + .type(new GraphQLList(new GraphQLTypeReference("stop"))) + .dataFetcher(new JDBCFetcher( + "stops", + "stop_id", + null, + false, + "parent_station" + )) + .build()) + .field(RowCountFetcher.field("stop_time_count", "stop_times", "stop_id")) + .field(newFieldDefinition() + .name("patterns") + // Field type should be equivalent to the final JDBCFetcher table type. + .type(new GraphQLList(new GraphQLTypeReference("pattern"))) + .argument(stringArg("namespace")) + .dataFetcher(new NestedJDBCFetcher( + new JDBCFetcher("pattern_stops", "stop_id", null, false), + new JDBCFetcher("patterns", "pattern_id"))) + .build()) + .field(newFieldDefinition() + .name("routes") + // Field type should be equivalent to the final JDBCFetcher table type. + .type(new GraphQLList(routeType)) + .argument(stringArg("namespace")) + .argument(stringArg(SEARCH_ARG)) + .argument(intArg(LIMIT_ARG)) + .dataFetcher(new NestedJDBCFetcher( + new JDBCFetcher("pattern_stops", "stop_id", null, false), + new JDBCFetcher("patterns", "pattern_id", null, false), + new JDBCFetcher("routes", "route_id"))) + .build()) + .field(newFieldDefinition() + .name("stop_times") + // forward reference to the as yet undefined stopTimeType (must be defined after tripType) + .type(new GraphQLList(new GraphQLTypeReference("stopTime"))) + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .argument(intArg(LIMIT_ARG)) + .dataFetcher(new JDBCFetcher("stop_times", "stop_id", "stop_sequence", false))) + .build(); /** * Represents each stop in a list of stops within a pattern. @@ -431,202 +401,211 @@ public class GraphQLGtfsSchema { * that structure would prevent us from joining tables and returning additional stop details * like lat and lon, or pickup and dropoff types if we add those to the pattern signature. */ - public static final GraphQLObjectType patternStopType = newObject().name("patternStop") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("pattern_id")) - .field(MapFetcher.field("stop_id")) - .field(MapFetcher.field("default_travel_time", GraphQLInt)) - .field(MapFetcher.field("default_dwell_time", GraphQLInt)) - .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) - .field(MapFetcher.field("drop_off_type", GraphQLInt)) - .field(MapFetcher.field("pickup_type", GraphQLInt)) - .field(MapFetcher.field("stop_headsign")) - .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) - .field(MapFetcher.field("continuous_pickup", GraphQLInt)) - .field(MapFetcher.field("stop_sequence", GraphQLInt)) - .field(MapFetcher.field("timepoint", GraphQLInt)) - // FIXME: This will only returns a list with one stop entity (unless there is a referential integrity issue) - // Should this be modified to be an object, rather than a list? - .field(newFieldDefinition() - .type(new GraphQLList(stopType)) - .name("stop") - .dataFetcher(new JDBCFetcher("stops", "stop_id")) - .build() - ) - .build(); + public static final GraphQLObjectType patternStopType = newObject() + .name("patternStop") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("pattern_id")) + .field(MapFetcher.field("stop_id")) + .field(MapFetcher.field("default_travel_time", GraphQLInt)) + .field(MapFetcher.field("default_dwell_time", GraphQLInt)) + .field(MapFetcher.field("shape_dist_traveled", GraphQLFloat)) + .field(MapFetcher.field("drop_off_type", GraphQLInt)) + .field(MapFetcher.field("pickup_type", GraphQLInt)) + .field(MapFetcher.field("stop_headsign")) + .field(MapFetcher.field("continuous_drop_off", GraphQLInt)) + .field(MapFetcher.field("continuous_pickup", GraphQLInt)) + .field(MapFetcher.field("stop_sequence", GraphQLInt)) + .field(MapFetcher.field("timepoint", GraphQLInt)) + // FIXME: This will only returns a list with one stop entity (unless there is a referential integrity issue) + // Should this be modified to be an object, rather than a list? + .field(newFieldDefinition() + .type(new GraphQLList(stopType)) + .name("stop") + .dataFetcher(new JDBCFetcher("stops", "stop_id")) + .build() + ) + .build(); /** * The GraphQL API type representing entries in the table of errors encountered while loading or validating a feed. */ - public static GraphQLObjectType validationErrorType = newObject().name("validationError") - .description("An error detected when loading or validating a feed.") - .field(MapFetcher.field("error_id", GraphQLInt)) - .field(MapFetcher.field("error_type")) - .field(MapFetcher.field("entity_type")) - // FIXME: change to id? - .field(MapFetcher.field("line_number", GraphQLInt)) - .field(MapFetcher.field("entity_id")) - .field(MapFetcher.field("entity_sequence", GraphQLInt)) - .field(MapFetcher.field("bad_value")) - .build(); + public static GraphQLObjectType validationErrorType = newObject() + .name("validationError") + .description("An error detected when loading or validating a feed.") + .field(MapFetcher.field("error_id", GraphQLInt)) + .field(MapFetcher.field("error_type")) + .field(MapFetcher.field("entity_type")) + // FIXME: change to id? + .field(MapFetcher.field("line_number", GraphQLInt)) + .field(MapFetcher.field("entity_id")) + .field(MapFetcher.field("entity_sequence", GraphQLInt)) + .field(MapFetcher.field("bad_value")) + .build(); /** * The GraphQL API type representing counts of rows in the various GTFS tables. * The context here for fetching subfields is the feedType. A special dataFetcher is used to pass that identical * context down. */ - public static GraphQLObjectType rowCountsType = newObject().name("rowCounts") - .description("Counts of rows in the various GTFS tables.") - .field(RowCountFetcher.field("stops")) - .field(RowCountFetcher.field("trips")) - .field(RowCountFetcher.field("routes")) - .field(RowCountFetcher.field("stop_times")) - .field(RowCountFetcher.field("agency")) - .field(RowCountFetcher.field("calendar")) - .field(RowCountFetcher.field("calendar_dates")) - .field(RowCountFetcher.field("errors")) - .build(); + public static GraphQLObjectType rowCountsType = newObject() + .name("rowCounts") + .description("Counts of rows in the various GTFS tables.") + .field(RowCountFetcher.field("stops")) + .field(RowCountFetcher.field("trips")) + .field(RowCountFetcher.field("routes")) + .field(RowCountFetcher.field("stop_times")) + .field(RowCountFetcher.field("agency")) + .field(RowCountFetcher.field("calendar")) + .field(RowCountFetcher.field("calendar_dates")) + .field(RowCountFetcher.field("errors")) + .build(); - public static GraphQLObjectType tripGroupCountType = newObject().name("tripGroupCount") - .description("") - .field(RowCountFetcher.groupedField("trips", "service_id")) - .field(RowCountFetcher.groupedField("trips", "route_id")) - .field(RowCountFetcher.groupedField("trips", "pattern_id")) - .build(); + public static GraphQLObjectType tripGroupCountType = newObject() + .name("tripGroupCount") + .description("") + .field(RowCountFetcher.groupedField("trips", "service_id")) + .field(RowCountFetcher.groupedField("trips", "route_id")) + .field(RowCountFetcher.groupedField("trips", "pattern_id")) + .build(); /** * GraphQL does not have a type for arbitrary maps (String -> X). Such maps must be expressed as a list of * key-value pairs. This is probably intended to protect us from ourselves (sending untyped data) but it just * leads to silly workarounds like this. */ - public static GraphQLObjectType errorCountType = newObject().name("errorCount") - .description("Quantity of validation errors of a specific type.") - .field(string("type")) - .field(intt("count")) - .field(string("message")) - .field(string("priority")) - .build(); + public static GraphQLObjectType errorCountType = newObject() + .name("errorCount") + .description("Quantity of validation errors of a specific type.") + .field(string("type")) + .field(intt("count")) + .field(string("message")) + .field(string("priority")) + .build(); /** * The GraphQL API type representing a unique sequence of stops on a route. This is used to group trips together. */ - public static final GraphQLObjectType patternType = newObject().name("pattern") - .description("A sequence of stops that characterizes a set of trips on a single route.") - .field(MapFetcher.field("id", GraphQLInt)) - .field(MapFetcher.field("pattern_id")) - .field(MapFetcher.field("shape_id")) - .field(MapFetcher.field("route_id")) - // FIXME: Fields directly below are editor-specific. Move somewhere else? - .field(MapFetcher.field("direction_id", GraphQLInt)) - .field(MapFetcher.field("use_frequency", GraphQLInt)) - .field(MapFetcher.field("name")) - .field(newFieldDefinition() - .name("shape") - .type(new GraphQLList(shapePointType)) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher("shapes", - "shape_id", - "shape_pt_sequence", - false)) - .build()) - .field(RowCountFetcher.field("trip_count", "trips", "pattern_id")) - .field(newFieldDefinition() - .name("pattern_stops") - .type(new GraphQLList(patternStopType)) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .dataFetcher(new JDBCFetcher("pattern_stops", - "pattern_id", - "stop_sequence", - false)) - .build()) - .field(newFieldDefinition() - .name("stops") - .description("GTFS stop entities that the pattern serves") - // Field type should be equivalent to the final JDBCFetcher table type. - .type(new GraphQLList(stopType)) - // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. - .argument(stringArg("namespace")) - .argument(intArg(LIMIT_ARG)) - // We allow querying only for a single stop, otherwise result processing can take a long time (lots - // of join queries). - .argument(stringArg("pattern_id")) - .dataFetcher(new NestedJDBCFetcher( - new JDBCFetcher("pattern_stops", "pattern_id", null, false), - new JDBCFetcher("stops", "stop_id"))) - .build()) - .field(newFieldDefinition() - .name("trips") - .type(new GraphQLList(tripType)) - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .argument(intArg(LIMIT_ARG)) - .argument(stringArg(DATE_ARG)) - .argument(intArg(FROM_ARG)) - .argument(intArg(TO_ARG)) - .argument(multiStringArg("service_id")) - .dataFetcher(new JDBCFetcher("trips", "pattern_id")) - .build()) - // FIXME This is a singleton array because the JdbcFetcher currently only works with one-to-many joins. - .field(newFieldDefinition() - .name("route") - // Field type should be equivalent to the final JDBCFetcher table type. - .type(new GraphQLList(routeType)) - .argument(stringArg("namespace")) - .dataFetcher(new JDBCFetcher("routes", "route_id")) - .build()) - .build(); + public static final GraphQLObjectType patternType = newObject() + .name("pattern") + .description("A sequence of stops that characterizes a set of trips on a single route.") + .field(MapFetcher.field("id", GraphQLInt)) + .field(MapFetcher.field("pattern_id")) + .field(MapFetcher.field("shape_id")) + .field(MapFetcher.field("route_id")) + // FIXME: Fields directly below are editor-specific. Move somewhere else? + .field(MapFetcher.field("direction_id", GraphQLInt)) + .field(MapFetcher.field("use_frequency", GraphQLInt)) + .field(MapFetcher.field("name")) + .field(newFieldDefinition() + .name("shape") + .type(new GraphQLList(shapePointType)) + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .argument(intArg(LIMIT_ARG)) + .dataFetcher(new JDBCFetcher( + "shapes", + "shape_id", + "shape_pt_sequence", + false) + ) + .build()) + .field(RowCountFetcher.field("trip_count", "trips", "pattern_id")) + .field(newFieldDefinition() + .name("pattern_stops") + .type(new GraphQLList(patternStopType)) + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .argument(intArg(LIMIT_ARG)) + .dataFetcher(new JDBCFetcher( + "pattern_stops", + "pattern_id", + "stop_sequence", + false) + ) + .build()) + .field(newFieldDefinition() + .name("stops") + .description("GTFS stop entities that the pattern serves") + // Field type should be equivalent to the final JDBCFetcher table type. + .type(new GraphQLList(stopType)) + // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. + .argument(stringArg("namespace")) + .argument(intArg(LIMIT_ARG)) + // We allow querying only for a single stop, otherwise result processing can take a long time (lots + // of join queries). + .argument(stringArg("pattern_id")) + .dataFetcher(new NestedJDBCFetcher( + new JDBCFetcher("pattern_stops", "pattern_id", null, false), + new JDBCFetcher("stops", "stop_id"))) + .build()) + .field(newFieldDefinition() + .name("trips") + .type(new GraphQLList(tripType)) + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .argument(intArg(LIMIT_ARG)) + .argument(stringArg(DATE_ARG)) + .argument(intArg(FROM_ARG)) + .argument(intArg(TO_ARG)) + .argument(multiStringArg("service_id")) + .dataFetcher(new JDBCFetcher("trips", "pattern_id")) + .build()) + // FIXME This is a singleton array because the JdbcFetcher currently only works with one-to-many joins. + .field(newFieldDefinition() + .name("route") + // Field type should be equivalent to the final JDBCFetcher table type. + .type(new GraphQLList(routeType)) + .argument(stringArg("namespace")) + .dataFetcher(new JDBCFetcher("routes", "route_id")) + .build()) + .build(); /** * Durations that a service runs on each mode of transport (route_type). */ - public static final GraphQLObjectType serviceDurationType = newObject().name("serviceDuration") - .field(MapFetcher.field("route_type", GraphQLInt)) - .field(MapFetcher.field("duration_seconds", GraphQLInt)) - .build(); + public static final GraphQLObjectType serviceDurationType = newObject() + .name("serviceDuration") + .field(MapFetcher.field("route_type", GraphQLInt)) + .field(MapFetcher.field("duration_seconds", GraphQLInt)) + .build(); /** * The GraphQL API type representing a service (a service_id attached to trips to say they run on certain days). */ - public static GraphQLObjectType serviceType = newObject().name("service") - .description("A group of trips that all run together on certain days.") - .field(MapFetcher.field("service_id")) - .field(MapFetcher.field("n_days_active")) - .field(MapFetcher.field("duration_seconds")) - .field(newFieldDefinition() - .name("dates") - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .type(new GraphQLList(GraphQLString)) - .dataFetcher(new SQLColumnFetcher("service_dates", "service_id", "service_date")) - .build()) - .field(newFieldDefinition() - .name("trips") - // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types - // (i.e., nested types that typically would only be nested under another entity and only make sense - // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) - .type(new GraphQLList(tripType)) - .dataFetcher(new JDBCFetcher("trips", "service_id")) - .build()) - .field(newFieldDefinition() - .name("durations") - .type(new GraphQLList(serviceDurationType)) - .dataFetcher(new JDBCFetcher("service_durations", "service_id")) - .build()) - .build(); + public static GraphQLObjectType serviceType = newObject() + .name("service") + .description("A group of trips that all run together on certain days.") + .field(MapFetcher.field("service_id")) + .field(MapFetcher.field("n_days_active")) + .field(MapFetcher.field("duration_seconds")) + .field(newFieldDefinition() + .name("dates") + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .type(new GraphQLList(GraphQLString)) + .dataFetcher(new SQLColumnFetcher("service_dates", "service_id", "service_date")) + .build()) + .field(newFieldDefinition() + .name("trips") + // FIXME Update JDBCFetcher to have noLimit boolean for fetchers on "naturally" nested types + // (i.e., nested types that typically would only be nested under another entity and only make sense + // with the entire set -- fares -> fare rules, trips -> stop times, patterns -> pattern stops/shapes) + .type(new GraphQLList(tripType)) + .dataFetcher(new JDBCFetcher("trips", "service_id")) + .build()) + .field(newFieldDefinition() + .name("durations") + .type(new GraphQLList(serviceDurationType)) + .dataFetcher(new JDBCFetcher("service_durations", "service_id")) + .build()) + .build(); - /** - * The GraphQL API type representing entries in the top-level table listing all the feeds imported into a gtfs-api - * database, and with sub-fields for each table of GTFS entities within a single feed. - */ - public static final GraphQLObjectType feedType = newObject().name("feedVersion") + public static GraphQLObjectType getFeedType(List faresV2FieldDefinitions) { + return newObject().name("feedVersion") // First, the fields present in the top level table. .field(MapFetcher.field("namespace")) .field(MapFetcher.field("feed_id")) @@ -638,59 +617,34 @@ public class GraphQLGtfsSchema { .field(MapFetcher.field("snapshot_of")) // A field containing row counts for every table. .field(newFieldDefinition() - .name("row_counts") - .type(rowCountsType) - .dataFetcher(new SourceObjectFetcher()) - .build()) + .name("row_counts") + .type(rowCountsType) + .dataFetcher(new SourceObjectFetcher()) + .build()) .field(newFieldDefinition() - .name("trip_counts") - .type(tripGroupCountType) - .dataFetcher(new SourceObjectFetcher()) - .build()) + .name("trip_counts") + .type(tripGroupCountType) + .dataFetcher(new SourceObjectFetcher()) + .build()) // A field containing counts for each type of error independently. .field(newFieldDefinition() - .name("error_counts") - .type(new GraphQLList(errorCountType)) - .dataFetcher(new ErrorCountFetcher()) - .build()) + .name("error_counts") + .type(new GraphQLList(errorCountType)) + .dataFetcher(new ErrorCountFetcher()) + .build()) // A field for the errors themselves. - .field(newFieldDefinition() - .name("errors") - .type(new GraphQLList(validationErrorType)) - .argument(stringArg("namespace")) - .argument(multiStringArg("error_type")) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("errors")) - .build() - ) - // A field containing the feed info table. - .field(newFieldDefinition() - .name("feed_info") - .type(new GraphQLList(feedInfoType)) - // FIXME: These arguments really don't make sense for feed info, but in order to create generic - // fetches on the client-side they have been included here. - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - // DataFetchers can either be class instances implementing the interface, or a static function reference - .dataFetcher(new JDBCFetcher("feed_info")) - .build()) + .field(createFieldDefinition( + "errors", + validationErrorType, + GraphQLUtil.buildArgs(multiStringArg("error_type")) + )) + .field(createFieldDefinition("feed_info", feedInfoType, "feed_info")) // A field containing all the unique stop sequences (patterns) in this feed. - .field(newFieldDefinition() - .name("patterns") - .type(new GraphQLList(patternType)) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .argument(floatArg(MIN_LAT)) - .argument(floatArg(MIN_LON)) - .argument(floatArg(MAX_LAT)) - .argument(floatArg(MAX_LON)) - .argument(multiStringArg("pattern_id")) - // DataFetchers can either be class instances implementing the interface, or a static function reference - .dataFetcher(new JDBCFetcher("patterns")) - .build()) + .field(createFieldDefinition( + "patterns", + patternType, + GraphQLUtil.buildArgs(floatArg(MIN_LAT), floatArg(MIN_LON), floatArg(MAX_LAT), floatArg(MAX_LON)) + )) .field(newFieldDefinition() .name("shapes_as_polylines") .type(new GraphQLList(shapeEncodedPolylineType)) @@ -698,180 +652,80 @@ public class GraphQLGtfsSchema { .dataFetcher(new PolylineFetcher()) .build()) // Then the fields for the sub-tables within the feed (loaded directly from GTFS). - .field(newFieldDefinition() - .name("agency") - .type(new GraphQLList(GraphQLGtfsSchema.agencyType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(multiStringArg("agency_id")) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("agency")) - .build() - ) - .field(newFieldDefinition() - .name("calendar") - .type(new GraphQLList(GraphQLGtfsSchema.calendarType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(multiStringArg("service_id")) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("calendar")) - .build() - ) - .field(newFieldDefinition() - .name("fares") - .type(new GraphQLList(GraphQLGtfsSchema.fareType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(multiStringArg("fare_id")) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("fare_attributes")) - .build() - ) - .field(newFieldDefinition() - .name("routes") - .type(new GraphQLList(GraphQLGtfsSchema.routeType)) - .argument(stringArg("namespace")) - .argument(multiStringArg("route_id")) - .argument(stringArg(SEARCH_ARG)) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("routes")) - .build() - ) - .field(newFieldDefinition() - .name("stops") - .type(new GraphQLList(GraphQLGtfsSchema.stopType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(multiStringArg("stop_id")) - .argument(multiStringArg("pattern_id")) - .argument(floatArg(MIN_LAT)) - .argument(floatArg(MIN_LON)) - .argument(floatArg(MAX_LAT)) - .argument(floatArg(MAX_LON)) - .argument(stringArg(SEARCH_ARG)) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("stops")) - .build() - ) - .field(newFieldDefinition() - .name("trips") - .type(new GraphQLList(GraphQLGtfsSchema.tripType)) - .argument(stringArg("namespace")) - .argument(multiStringArg("trip_id")) - .argument(multiStringArg("route_id")) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(stringArg(DATE_ARG)) - .argument(intArg(FROM_ARG)) - .argument(intArg(TO_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("trips")) - .build() - ) - .field(newFieldDefinition() - .name("schedule_exceptions") - .type(new GraphQLList(GraphQLGtfsSchema.scheduleExceptionType)) - .argument(stringArg("namespace")) - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("schedule_exceptions")) - .build() - ) - .field(newFieldDefinition() - .name("stop_times") - .type(new GraphQLList(GraphQLGtfsSchema.stopTimeType)) - .argument(stringArg("namespace")) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("stop_times")) - .build() - ) - .field(newFieldDefinition() - .name("services") - .argument(multiStringArg("service_id")) - .type(new GraphQLList(GraphQLGtfsSchema.serviceType)) - .argument(intArg(LIMIT_ARG)) // Todo somehow autogenerate these JDBCFetcher builders to include standard params. - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("services")) - .build() - ) - .field(newFieldDefinition() - .name("attributions") - .type(new GraphQLList(GraphQLGtfsSchema.attributionsType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("attributions")) - .build() - ) - .field(newFieldDefinition() - .name("translations") - .type(new GraphQLList(GraphQLGtfsSchema.translationsType)) - .argument(stringArg("namespace")) // FIXME maybe these nested namespace arguments are not doing anything. - .argument(intArg(ID_ARG)) - .argument(intArg(LIMIT_ARG)) - .argument(intArg(OFFSET_ARG)) - .dataFetcher(new JDBCFetcher("translations")) - .build() - ) + .field(createFieldDefinition( + "agency", + agencyType, + GraphQLUtil.buildArgs(multiStringArg("agency_id")) + )) + .field(createFieldDefinition("calendar", calendarType, GraphQLUtil.buildArgs(multiStringArg("service_id")))) + .field(createFieldDefinition( + "fares", + fareType, + "fare_attributes", + GraphQLUtil.buildArgs(multiStringArg("fare_id")) + )) + .field(createFieldDefinition( + "routes", + routeType, + GraphQLUtil.buildArgs(multiStringArg("route_id"), stringArg(SEARCH_ARG)) + )) + .field(createFieldDefinition( + "stops", + stopType, + GraphQLUtil.buildArgs( + multiStringArg("stop_id"), + multiStringArg("pattern_id"), + floatArg(MIN_LAT), + floatArg(MIN_LON), + floatArg(MAX_LAT), + floatArg(MAX_LON), + stringArg(SEARCH_ARG) + ) + )) + .field(createFieldDefinition( + "trips", + tripType, + GraphQLUtil.buildArgs( + multiStringArg("trip_id"), + multiStringArg("route_id"), + stringArg(DATE_ARG), + intArg(FROM_ARG), + intArg(TO_ARG) + ) + )) + .field(createFieldDefinition("schedule_exceptions", scheduleExceptionType, buildArgs())) + .field(createFieldDefinition("stop_times", stopTimeType, buildArgs())) + .field(createFieldDefinition("services", serviceType, buildArgs())) + .field(createFieldDefinition("attributions", attributionsType, buildArgs())) + .field(createFieldDefinition("translations", translationsType, buildArgs())) + .fields(faresV2FieldDefinitions) .build(); + } /** * This is the top-level query - you must always specify a feed to fetch, and then some other things inside that feed. * TODO decide whether to call this feedVersion or feed within gtfs-lib context. */ private static GraphQLObjectType feedQuery = newObject() - .name("feedQuery") - .field(newFieldDefinition() - .name("feed") - .type(feedType) - // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. - .argument(stringArg("namespace")) - .dataFetcher(new FeedFetcher()) - .build() - ) - .build(); - - /** - * A top-level query that returns all of the patterns that serve a given stop ID. This demonstrates the use of - * NestedJDBCFetcher. - */ -// private static GraphQLObjectType patternsForStopQuery = newObject() -// .name("patternsForStopQuery") -// .field(newFieldDefinition() -// .name("patternsForStop") -// // Field type should be equivalent to the final JDBCFetcher table type. -// .type(new GraphQLList(patternType)) -// // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. -// .argument(stringArg("namespace")) -// // We allow querying only for a single stop, otherwise result processing can take a long time (lots -// // of join queries). -// .argument(stringArg("stop_id")) -// .dataFetcher(new NestedJDBCFetcher( -// new JDBCFetcher("pattern_stops", "stop_id"), -// new JDBCFetcher("patterns", "pattern_id"))) -// .build()) -// .build(); - + .name("feedQuery") + .field(newFieldDefinition() + .name("feed") + .type(getFeedType(GraphQLGtfsFaresV2Schema.getFaresV2FieldDefinitions())) + // We scope to a single feed namespace, otherwise GTFS entity IDs are ambiguous. + .argument(stringArg("namespace")) + .dataFetcher(new FeedFetcher()) + .build() + ) + .build(); /** * This is the new schema as of July 2017, where all sub-entities are wrapped in a feed. * Because all of these fields are static (ugh) this must be declared after the feedQuery it references. */ public static final GraphQLSchema feedBasedSchema = GraphQLSchema - .newSchema() - .query(feedQuery) -// .query(patternsForStopQuery) - .build(); + .newSchema() + .query(feedQuery) + .build(); private static class StringCoercing implements Coercing { @@ -880,7 +734,6 @@ public Object serialize(Object input) { String[] strings = new String[]{}; try { strings = (String[])((Array) input).getArray(); -// if (strings == null) strings = new String[]{}; } catch (SQLException e) { e.printStackTrace(); } diff --git a/src/main/java/com/conveyal/gtfs/graphql/GraphQLUtil.java b/src/main/java/com/conveyal/gtfs/graphql/GraphQLUtil.java index 5002e5ccc..73a1cf6a3 100644 --- a/src/main/java/com/conveyal/gtfs/graphql/GraphQLUtil.java +++ b/src/main/java/com/conveyal/gtfs/graphql/GraphQLUtil.java @@ -1,10 +1,20 @@ package com.conveyal.gtfs.graphql; +import com.conveyal.gtfs.graphql.fetchers.JDBCFetcher; import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; +import graphql.schema.GraphQLType; import graphql.schema.PropertyDataFetcher; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.conveyal.gtfs.graphql.fetchers.JDBCFetcher.ID_ARG; +import static com.conveyal.gtfs.graphql.fetchers.JDBCFetcher.LIMIT_ARG; +import static com.conveyal.gtfs.graphql.fetchers.JDBCFetcher.OFFSET_ARG; import static graphql.Scalars.GraphQLFloat; import static graphql.Scalars.GraphQLInt; import static graphql.Scalars.GraphQLString; @@ -57,4 +67,88 @@ public static GraphQLArgument floatArg (String name) { .build(); } + /** + * Standard base arguments. + */ + public static List buildArgs() { + return new ArrayList<>(Arrays.asList(intArg(ID_ARG), intArg(LIMIT_ARG), intArg(OFFSET_ARG))); + } + + /** + * Standard base arguments with additions. + */ + public static List buildArgs(GraphQLArgument... addOns) { + List args = buildArgs(); + Collections.addAll(args, addOns); + return args; + } + + /** + * Standard field definition with base arguments. + */ + public static GraphQLFieldDefinition createFieldDefinition(String name, GraphQLType graphQLType, String tableName) { + return createFieldDefinition(name, graphQLType, tableName, buildArgs()); + } + + /** + * Field definition with bespoke arguments. Name and table name are the same. + */ + public static GraphQLFieldDefinition createFieldDefinition( + String name, + GraphQLType graphQLType, + List arguments + ) { + return createFieldDefinition(name, graphQLType, name, arguments); + } + + /** + * Field definition with bespoke arguments. + */ + public static GraphQLFieldDefinition createFieldDefinition( + String name, + GraphQLType graphQLType, + String tableName, + List arguments + ) { + return newFieldDefinition() + .name(name) + .type(new GraphQLList(graphQLType)) + .argument(stringArg("namespace")) + .arguments(arguments) + .dataFetcher(new JDBCFetcher(tableName)) + .build(); + } + + /** + * Field definition for standard table join. + */ + public static GraphQLFieldDefinition createFieldDefinition( + String name, + GraphQLType graphQLType, + String tableName, + String parentJoinField + ) { + return newFieldDefinition() + .name(name) + .type(new GraphQLList(graphQLType)) + .dataFetcher(new JDBCFetcher(tableName, parentJoinField)) + .build(); + } + + /** + * Field definition for join with child table. + */ + public static GraphQLFieldDefinition createFieldDefinition( + String name, + GraphQLType graphQLType, + String tableName, + String parentJoinField, + String childJoinField + ) { + return newFieldDefinition() + .name(name) + .type(new GraphQLList(graphQLType)) + .dataFetcher(new JDBCFetcher(tableName, parentJoinField, null, false, childJoinField)) + .build(); + } } diff --git a/src/main/java/com/conveyal/gtfs/loader/EntityPopulator.java b/src/main/java/com/conveyal/gtfs/loader/EntityPopulator.java index c5c59d900..e6ed31fdb 100644 --- a/src/main/java/com/conveyal/gtfs/loader/EntityPopulator.java +++ b/src/main/java/com/conveyal/gtfs/loader/EntityPopulator.java @@ -1,18 +1,27 @@ package com.conveyal.gtfs.loader; import com.conveyal.gtfs.model.Agency; +import com.conveyal.gtfs.model.Area; import com.conveyal.gtfs.model.Calendar; import com.conveyal.gtfs.model.CalendarDate; import com.conveyal.gtfs.model.Entity; import com.conveyal.gtfs.model.FareAttribute; +import com.conveyal.gtfs.model.FareLegRule; +import com.conveyal.gtfs.model.FareMedia; +import com.conveyal.gtfs.model.FareProduct; +import com.conveyal.gtfs.model.FareTransferRule; import com.conveyal.gtfs.model.Frequency; +import com.conveyal.gtfs.model.Network; import com.conveyal.gtfs.model.Pattern; import com.conveyal.gtfs.model.PatternStop; import com.conveyal.gtfs.model.Route; +import com.conveyal.gtfs.model.RouteNetwork; import com.conveyal.gtfs.model.ScheduleException; import com.conveyal.gtfs.model.ShapePoint; import com.conveyal.gtfs.model.Stop; +import com.conveyal.gtfs.model.StopArea; import com.conveyal.gtfs.model.StopTime; +import com.conveyal.gtfs.model.TimeFrame; import com.conveyal.gtfs.model.Trip; import gnu.trove.map.TObjectIntMap; import org.slf4j.Logger; @@ -230,6 +239,85 @@ public interface EntityPopulator { return stopTime; }; + EntityPopulator AREA = (result, columnForName) -> { + Area area = new Area(); + area.area_id = getStringIfPresent(result, Area.AREA_ID_NAME, columnForName); + area.area_name = getStringIfPresent(result, Area.AREA_NAME_NAME, columnForName); + return area; + }; + + EntityPopulator STOP_AREA = (result, columnForName) -> { + StopArea stopArea = new StopArea(); + stopArea.area_id = getStringIfPresent(result, StopArea.AREA_ID_NAME, columnForName); + stopArea.stop_id = getStringIfPresent(result, StopArea.STOP_ID_NAME, columnForName); + return stopArea; + }; + + EntityPopulator FARE_MEDIA = (result, columnForName) -> { + FareMedia fareMedia = new FareMedia(); + fareMedia.fare_media_id = getStringIfPresent(result, FareMedia.FARE_MEDIA_ID_NAME, columnForName); + fareMedia.fare_media_name = getStringIfPresent(result, FareMedia.FARE_MEDIA_NAME_NAME, columnForName); + fareMedia.fare_media_type = getIntIfPresent(result, FareMedia.FARE_MEDIA_TYPE_NAME, columnForName); + return fareMedia; + }; + + EntityPopulator FARE_PRODUCT = (result, columnForName) -> { + FareProduct fareProduct = new FareProduct(); + fareProduct.fare_product_id = getStringIfPresent(result, FareProduct.FARE_PRODUCT_ID_NAME, columnForName); + fareProduct.fare_product_name = getStringIfPresent(result, FareProduct.FARE_PRODUCT_NAME_NAME, columnForName); + fareProduct.fare_media_id = getStringIfPresent(result, FareProduct.FARE_MEDIA_ID_NAME, columnForName); + fareProduct.amount = getDoubleIfPresent(result, FareProduct.AMOUNT_NAME, columnForName); + fareProduct.currency = getStringIfPresent(result, FareProduct.CURRENCY_NAME, columnForName); + return fareProduct; + }; + + EntityPopulator TIME_FRAME = (result, columnForName) -> { + TimeFrame timeFrame = new TimeFrame(); + timeFrame.timeframe_group_id = getStringIfPresent(result, TimeFrame.TIME_FRAME_GROUP_ID_NAME, columnForName); + timeFrame.start_time = getIntIfPresent(result, TimeFrame.START_TIME_NAME, columnForName); + timeFrame.end_time = getIntIfPresent(result, TimeFrame.END_TIME_NAME, columnForName); + timeFrame.service_id = getStringIfPresent(result, FareProduct.FARE_MEDIA_ID_NAME, columnForName); + return timeFrame; + }; + + EntityPopulator FARE_LEG_RULE = (result, columnForName) -> { + FareLegRule fareLegRule = new FareLegRule(); + fareLegRule.leg_group_id = getStringIfPresent(result, FareLegRule.LEG_GROUP_ID_NAME, columnForName); + fareLegRule.from_timeframe_group_id = getStringIfPresent(result, FareLegRule.FROM_AREA_ID_NAME, columnForName); + fareLegRule.to_timeframe_group_id = getStringIfPresent(result, FareLegRule.TO_AREA_ID_NAME, columnForName); + fareLegRule.from_timeframe_group_id = getStringIfPresent(result, FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME, columnForName); + fareLegRule.to_timeframe_group_id = getStringIfPresent(result, FareLegRule.TO_TIMEFRAME_GROUP_ID_NAME, columnForName); + fareLegRule.fare_product_id = getStringIfPresent(result, FareLegRule.FARE_PRODUCT_ID_NAME, columnForName); + fareLegRule.rule_priority = getIntIfPresent(result, FareLegRule.RULE_PRIORITY_NAME, columnForName); + return fareLegRule; + }; + + EntityPopulator FARE_TRANSFER_RULE = (result, columnForName) -> { + FareTransferRule fareTransferRule = new FareTransferRule(); + fareTransferRule.from_leg_group_id = getStringIfPresent(result, FareTransferRule.FROM_LEG_GROUP_ID_NAME, columnForName); + fareTransferRule.to_leg_group_id = getStringIfPresent(result, FareTransferRule.TO_LEG_GROUP_ID_NAME, columnForName); + fareTransferRule.transfer_count = getIntIfPresent(result, FareTransferRule.TRANSFER_COUNT_NAME, columnForName); + fareTransferRule.duration_limit = getIntIfPresent(result, FareTransferRule.DURATION_LIMIT_NAME, columnForName); + fareTransferRule.duration_limit_type = getIntIfPresent(result, FareTransferRule.DURATION_LIMIT_TYPE_NAME, columnForName); + fareTransferRule.fare_transfer_type = getIntIfPresent(result, FareTransferRule.FARE_TRANSFER_TYPE_NAME, columnForName); + fareTransferRule.fare_product_id = getStringIfPresent(result, FareTransferRule.FARE_PRODUCT_ID_NAME, columnForName); + return fareTransferRule; + }; + + EntityPopulator NETWORK = (result, columnForName) -> { + Network network = new Network(); + network.network_id = getStringIfPresent(result, Network.NETWORK_ID_NAME, columnForName); + network.network_name = getStringIfPresent(result, Network.NETWORK_NAME_NAME, columnForName); + return network; + }; + + EntityPopulator ROUTE_NETWORK = (result, columnForName) -> { + RouteNetwork routeNetwork = new RouteNetwork(); + routeNetwork.network_id = getStringIfPresent(result, RouteNetwork.NETWORK_ID_NAME, columnForName); + routeNetwork.route_id = getStringIfPresent(result, RouteNetwork.ROUTE_ID_NAME, columnForName); + return routeNetwork; + }; + // The reason we're passing in the columnForName map is that resultSet.getX(columnName) throws an exception // when the column is not present. // Exceptions should only be used in exceptional circumstances (ones that should be logged as errors). diff --git a/src/main/java/com/conveyal/gtfs/loader/Feed.java b/src/main/java/com/conveyal/gtfs/loader/Feed.java index 1430b5751..0711429e8 100644 --- a/src/main/java/com/conveyal/gtfs/loader/Feed.java +++ b/src/main/java/com/conveyal/gtfs/loader/Feed.java @@ -40,6 +40,15 @@ public class Feed { public final TableReader trips; public final TableReader stopTimes; public final TableReader patterns; + public final TableReader areas; + public final TableReader stopAreas; + public final TableReader fareMedia; + public final TableReader fareProducts; + public final TableReader timeFrames; + public final TableReader fareLegRules; + public final TableReader fareTransferRules; + public final TableReader networks; + public final TableReader routeNetworks; /** * Create a feed that reads tables over a JDBC connection. The connection should already be set to the right @@ -61,6 +70,15 @@ public Feed (DataSource dataSource, String databaseSchemaPrefix) { trips = new JDBCTableReader(Table.TRIPS, dataSource, databaseSchemaPrefix, EntityPopulator.TRIP); stopTimes = new JDBCTableReader(Table.STOP_TIMES, dataSource, databaseSchemaPrefix, EntityPopulator.STOP_TIME); patterns = new JDBCTableReader(Table.PATTERNS, dataSource, databaseSchemaPrefix, EntityPopulator.PATTERN); + areas = new JDBCTableReader(Table.AREAS, dataSource, databaseSchemaPrefix, EntityPopulator.AREA); + stopAreas = new JDBCTableReader(Table.STOP_AREAS, dataSource, databaseSchemaPrefix, EntityPopulator.STOP_AREA); + fareMedia = new JDBCTableReader(Table.FARE_MEDIAS, dataSource, databaseSchemaPrefix, EntityPopulator.FARE_MEDIA); + fareProducts = new JDBCTableReader(Table.FARE_PRODUCTS, dataSource, databaseSchemaPrefix, EntityPopulator.FARE_PRODUCT); + timeFrames = new JDBCTableReader(Table.TIME_FRAMES, dataSource, databaseSchemaPrefix, EntityPopulator.TIME_FRAME); + fareLegRules = new JDBCTableReader(Table.FARE_LEG_RULES, dataSource, databaseSchemaPrefix, EntityPopulator.FARE_LEG_RULE); + fareTransferRules = new JDBCTableReader(Table.FARE_TRANSFER_RULES, dataSource, databaseSchemaPrefix, EntityPopulator.FARE_TRANSFER_RULE); + networks = new JDBCTableReader(Table.NETWORKS, dataSource, databaseSchemaPrefix, EntityPopulator.NETWORK); + routeNetworks = new JDBCTableReader(Table.ROUTE_NETWORKS, dataSource, databaseSchemaPrefix, EntityPopulator.ROUTE_NETWORK); } /** diff --git a/src/main/java/com/conveyal/gtfs/loader/FeedLoadResult.java b/src/main/java/com/conveyal/gtfs/loader/FeedLoadResult.java index 301ed0d6e..4da375f53 100644 --- a/src/main/java/com/conveyal/gtfs/loader/FeedLoadResult.java +++ b/src/main/java/com/conveyal/gtfs/loader/FeedLoadResult.java @@ -38,6 +38,17 @@ public class FeedLoadResult implements Serializable { public TableLoadResult translations; public TableLoadResult attributions; + // Fares v2. + public TableLoadResult areas; + public TableLoadResult stopAreas; + public TableLoadResult fareMedias; + public TableLoadResult fareProducts; + public TableLoadResult timeFrames; + public TableLoadResult fareLegRules; + public TableLoadResult fareTransferRules; + public TableLoadResult networks; + public TableLoadResult routeNetworks; + public long loadTimeMillis; public long completionTime; @@ -64,5 +75,16 @@ public FeedLoadResult (boolean constructTableResults) { trips = new TableLoadResult(); translations = new TableLoadResult(); attributions = new TableLoadResult(); + + // Fares v2. + areas = new TableLoadResult(); + stopAreas = new TableLoadResult(); + fareMedias = new TableLoadResult(); + fareProducts = new TableLoadResult(); + timeFrames = new TableLoadResult(); + fareLegRules = new TableLoadResult(); + fareTransferRules = new TableLoadResult(); + networks = new TableLoadResult(); + routeNetworks = new TableLoadResult(); } } diff --git a/src/main/java/com/conveyal/gtfs/loader/JdbcGTFSFeedConverter.java b/src/main/java/com/conveyal/gtfs/loader/JdbcGTFSFeedConverter.java index 01990b24a..0734dae37 100644 --- a/src/main/java/com/conveyal/gtfs/loader/JdbcGTFSFeedConverter.java +++ b/src/main/java/com/conveyal/gtfs/loader/JdbcGTFSFeedConverter.java @@ -122,6 +122,18 @@ public FeedLoadResult loadTables () { copyEntityToSql(gtfsFeed.trips.values(), Table.TRIPS); // refs routes copyEntityToSql(frequencies, Table.FREQUENCIES); // refs trips copyEntityToSql(gtfsFeed.stop_times.values(), Table.STOP_TIMES); + + // Fares v2. + copyEntityToSql(gtfsFeed.areas.values(), Table.AREAS); + copyEntityToSql(gtfsFeed.stop_areas.values(), Table.STOP_AREAS); + copyEntityToSql(gtfsFeed.fare_medias.values(), Table.FARE_MEDIAS); + copyEntityToSql(gtfsFeed.fare_products.values(), Table.FARE_PRODUCTS); + copyEntityToSql(gtfsFeed.time_frames.values(), Table.TIME_FRAMES); + copyEntityToSql(gtfsFeed.fare_leg_rules.values(), Table.FARE_LEG_RULES); + copyEntityToSql(gtfsFeed.fare_transfer_rules.values(), Table.FARE_TRANSFER_RULES); + copyEntityToSql(gtfsFeed.networks.values(), Table.NETWORKS); + copyEntityToSql(gtfsFeed.route_networks.values(), Table.ROUTE_NETWORKS); + // result.errorCount = errorStorage.getErrorCount(); // This will commit and close the single connection that has been shared between all preceding load steps. errorStorage.commitAndClose(); diff --git a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsExporter.java b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsExporter.java index 78e6591d7..86172a72f 100644 --- a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsExporter.java +++ b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsExporter.java @@ -14,7 +14,6 @@ import javax.sql.DataSource; import java.io.File; -import java.io.FileOutputStream; import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -29,10 +28,8 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; @@ -347,6 +344,16 @@ public FeedLoadResult exportTables() { result.trips = export(Table.TRIPS, connection); } + result.areas = export(Table.AREAS, connection); + result.stopAreas = export(Table.STOP_AREAS, connection); + result.fareMedias = export(Table.FARE_MEDIAS, connection); + result.fareProducts = export(Table.FARE_PRODUCTS, connection); + result.timeFrames = export(Table.TIME_FRAMES, connection); + result.fareLegRules = export(Table.FARE_LEG_RULES, connection); + result.fareTransferRules = export(Table.FARE_TRANSFER_RULES, connection); + result.networks = export(Table.NETWORKS, connection); + result.routeNetworks = export(Table.ROUTE_NETWORKS, connection); + exportProprietaryFiles(result); zipOutputStream.close(); diff --git a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsLoader.java b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsLoader.java index 25ffc0340..299af9484 100644 --- a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsLoader.java +++ b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsLoader.java @@ -156,12 +156,21 @@ public FeedLoadResult loadTables() { result.agency = load(Table.AGENCY); result.calendar = load(Table.CALENDAR); result.calendarDates = load(Table.CALENDAR_DATES); + result.timeFrames = load(Table.TIME_FRAMES); result.routes = load(Table.ROUTES); result.fareAttributes = load(Table.FARE_ATTRIBUTES); + result.fareMedias = load(Table.FARE_MEDIAS); + result.fareProducts = load(Table.FARE_PRODUCTS); + result.networks = load(Table.NETWORKS); + result.routeNetworks = load(Table.ROUTE_NETWORKS); // refs networks. + result.areas = load(Table.AREAS); + result.fareLegRules = load(Table.FARE_LEG_RULES); // ref areas + result.fareTransferRules = load(Table.FARE_TRANSFER_RULES); result.feedInfo = load(Table.FEED_INFO); result.shapes = load(Table.SHAPES); result.patterns = load(Table.PATTERNS); // refs shapes and routes. result.stops = load(Table.STOPS); + result.stopAreas = load(Table.STOP_AREAS); result.fareRules = load(Table.FARE_RULES); result.trips = load(Table.TRIPS); // refs routes result.transfers = load(Table.TRANSFERS); // refs trips. @@ -169,6 +178,7 @@ public FeedLoadResult loadTables() { result.stopTimes = load(Table.STOP_TIMES); result.translations = load(Table.TRANSLATIONS); result.attributions = load(Table.ATTRIBUTIONS); + result.errorCount = errorStorage.getErrorCount(); // This will commit and close the single connection that has been shared between all preceding load steps. errorStorage.commitAndClose(); diff --git a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsSnapshotter.java b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsSnapshotter.java index 67a9c9ebc..0f3bb12a0 100644 --- a/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsSnapshotter.java +++ b/src/main/java/com/conveyal/gtfs/loader/JdbcGtfsSnapshotter.java @@ -115,6 +115,17 @@ public SnapshotResult copyTables() { result.trips = copy(Table.TRIPS, true); result.attributions = copy(Table.ATTRIBUTIONS, true); result.translations = copy(Table.TRANSLATIONS, true); + // Fares v2. + result.areas = copy(Table.AREAS, true); + result.stopAreas = copy(Table.STOP_AREAS, true); + result.fareMedias = copy(Table.FARE_MEDIAS, true); + result.fareProducts = copy(Table.FARE_PRODUCTS, true); + result.timeFrames = copy(Table.TIME_FRAMES, true); + result.fareLegRules = copy(Table.FARE_LEG_RULES, true); + result.fareTransferRules = copy(Table.FARE_TRANSFER_RULES, true); + result.networks = copy(Table.NETWORKS, true); + result.routeNetworks = copy(Table.ROUTE_NETWORKS, true); + result.completionTime = System.currentTimeMillis(); result.loadTimeMillis = result.completionTime - startTime; LOG.info("Copying tables took {} sec", (result.loadTimeMillis) / 1000); diff --git a/src/main/java/com/conveyal/gtfs/loader/Table.java b/src/main/java/com/conveyal/gtfs/loader/Table.java index 1ac7a0030..1c4954c9b 100644 --- a/src/main/java/com/conveyal/gtfs/loader/Table.java +++ b/src/main/java/com/conveyal/gtfs/loader/Table.java @@ -10,21 +10,30 @@ import com.conveyal.gtfs.loader.conditions.ForeignRefExistsCheck; import com.conveyal.gtfs.loader.conditions.ReferenceFieldShouldBeProvidedCheck; import com.conveyal.gtfs.model.Agency; +import com.conveyal.gtfs.model.Area; import com.conveyal.gtfs.model.Attribution; import com.conveyal.gtfs.model.Calendar; import com.conveyal.gtfs.model.CalendarDate; import com.conveyal.gtfs.model.Entity; import com.conveyal.gtfs.model.FareAttribute; +import com.conveyal.gtfs.model.FareLegRule; +import com.conveyal.gtfs.model.FareMedia; +import com.conveyal.gtfs.model.FareProduct; import com.conveyal.gtfs.model.FareRule; +import com.conveyal.gtfs.model.FareTransferRule; import com.conveyal.gtfs.model.FeedInfo; import com.conveyal.gtfs.model.Frequency; +import com.conveyal.gtfs.model.Network; import com.conveyal.gtfs.model.Pattern; import com.conveyal.gtfs.model.PatternStop; import com.conveyal.gtfs.model.Route; +import com.conveyal.gtfs.model.RouteNetwork; import com.conveyal.gtfs.model.ScheduleException; import com.conveyal.gtfs.model.ShapePoint; import com.conveyal.gtfs.model.Stop; +import com.conveyal.gtfs.model.StopArea; import com.conveyal.gtfs.model.StopTime; +import com.conveyal.gtfs.model.TimeFrame; import com.conveyal.gtfs.model.Transfer; import com.conveyal.gtfs.model.Translation; import com.conveyal.gtfs.model.Trip; @@ -37,6 +46,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.Charset; +import java.nio.file.Paths; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -133,6 +143,7 @@ public Table (String name, Class entityClass, Requirement requ .addPrimaryKey() .addPrimaryKeyNames("agency_id"); + // The GTFS spec says this table is required, but in practice it is not required if calendar_dates is present. public static final Table CALENDAR = new Table("calendar", Calendar.class, OPTIONAL, new StringField("service_id", REQUIRED), @@ -213,7 +224,7 @@ public Table (String name, Class entityClass, Requirement requ new StringField("route_short_name", OPTIONAL), // one of short or long must be provided new StringField("route_long_name", OPTIONAL), new StringField("route_desc", OPTIONAL), - // Max route type according to the GTFS spec is 7; however, there is a GTFS proposal that could see this + // Max route type according to the GTFS spec is 7; however, there is a GTFS proposal that could see this // max value grow to around 1800: https://groups.google.com/forum/#!msg/gtfs-changes/keT5rTPS7Y0/71uMz2l6ke0J new IntegerField("route_type", REQUIRED, 1800), new URLField("route_url", OPTIONAL), @@ -228,7 +239,8 @@ public Table (String name, Class entityClass, Requirement requ // Status values are In progress (0), Pending approval (1), and Approved (2). new ShortField("status", EDITOR, 2), new ShortField("continuous_pickup", OPTIONAL,3), - new ShortField("continuous_drop_off", OPTIONAL,3) + new ShortField("continuous_drop_off", OPTIONAL,3), + new StringField("network_id", OPTIONAL) ).addPrimaryKey() .addPrimaryKeyNames("route_id"); @@ -287,6 +299,109 @@ public Table (String name, Class entityClass, Requirement requ .addPrimaryKey() .addPrimaryKeyNames("stop_id"); + public static final Table AREAS = new Table(Area.TABLE_NAME, Area.class, OPTIONAL, + new StringField(Area.AREA_ID_NAME, REQUIRED), + new StringField(Area.AREA_NAME_NAME, OPTIONAL) + ) + .restrictDelete() + .addPrimaryKeyNames(Area.AREA_ID_NAME); + + public static final Table STOP_AREAS = new Table(StopArea.TABLE_NAME, StopArea.class, OPTIONAL, + new StringField(StopArea.AREA_ID_NAME, REQUIRED).isReferenceTo(AREAS), + new StringField(StopArea.STOP_ID_NAME, REQUIRED).isReferenceTo(STOPS) + ) + .keyFieldIsNotUnique() + .addPrimaryKeyNames(StopArea.AREA_ID_NAME, StopArea.STOP_ID_NAME); + + public static final Table FARE_MEDIAS = new Table(FareMedia.TABLE_NAME, FareMedia.class, OPTIONAL, + new StringField(FareMedia.FARE_MEDIA_ID_NAME, REQUIRED), + new StringField(FareMedia.FARE_MEDIA_NAME_NAME, OPTIONAL), + new IntegerField(FareMedia.FARE_MEDIA_TYPE_NAME, REQUIRED) + ) + .restrictDelete() + .addPrimaryKeyNames(FareMedia.FARE_MEDIA_ID_NAME); + + public static final Table FARE_PRODUCTS = new Table(FareProduct.TABLE_NAME, FareProduct.class, OPTIONAL, + new StringField(FareProduct.FARE_PRODUCT_ID_NAME, REQUIRED), + new StringField(FareProduct.FARE_PRODUCT_NAME_NAME, OPTIONAL), + new StringField(FareProduct.FARE_MEDIA_ID_NAME, OPTIONAL).isReferenceTo(FARE_MEDIAS), + new DoubleField(FareProduct.AMOUNT_NAME, REQUIRED, 0.0, Double.MAX_VALUE, 2), + new StringField(FareProduct.CURRENCY_NAME, REQUIRED) + ) + .restrictDelete() + .keyFieldIsNotUnique() + .addPrimaryKeyNames(FareProduct.FARE_PRODUCT_ID_NAME, FareProduct.FARE_MEDIA_ID_NAME); + + public static final Table TIME_FRAMES = new Table(TimeFrame.TABLE_NAME, TimeFrame.class, OPTIONAL, + new StringField(TimeFrame.TIME_FRAME_GROUP_ID_NAME, REQUIRED), + new TimeField(TimeFrame.START_TIME_NAME, OPTIONAL), + new TimeField(TimeFrame.END_TIME_NAME, OPTIONAL), + new StringField(TimeFrame.SERVICE_ID_NAME, OPTIONAL).isReferenceTo(CALENDAR).isReferenceTo(CALENDAR_DATES) + ) + .restrictDelete() + .keyFieldIsNotUnique() + .addPrimaryKeyNames( + TimeFrame.TIME_FRAME_GROUP_ID_NAME, + TimeFrame.START_TIME_NAME, + TimeFrame.END_TIME_NAME, + TimeFrame.SERVICE_ID_NAME + ); + + public static final Table FARE_LEG_RULES = new Table(FareLegRule.TABLE_NAME, FareLegRule.class, OPTIONAL, + new StringField(FareLegRule.LEG_GROUP_ID_NAME, OPTIONAL), + new StringField(FareLegRule.NETWORK_ID_NAME, OPTIONAL), + new StringField(FareLegRule.FROM_AREA_ID_NAME, OPTIONAL).isReferenceTo(AREAS), + new StringField(FareLegRule.TO_AREA_ID_NAME, OPTIONAL).isReferenceTo(AREAS), + new StringField(FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME, OPTIONAL).isReferenceTo(TIME_FRAMES), + new StringField(FareLegRule.TO_TIMEFRAME_GROUP_ID_NAME, OPTIONAL).isReferenceTo(TIME_FRAMES), + new StringField(FareLegRule.FARE_PRODUCT_ID_NAME, REQUIRED).isReferenceTo(FARE_PRODUCTS), + new IntegerField(FareLegRule.RULE_PRIORITY_NAME, OPTIONAL) + ) + .restrictDelete() + .keyFieldIsNotUnique() + .addPrimaryKeyNames( + FareLegRule.NETWORK_ID_NAME, + FareLegRule.FROM_AREA_ID_NAME, + FareLegRule.TO_AREA_ID_NAME, + FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME, + FareLegRule.TO_TIMEFRAME_GROUP_ID_NAME, + FareLegRule.FARE_PRODUCT_ID_NAME + ); + + public static final Table FARE_TRANSFER_RULES = new Table(FareTransferRule.TABLE_NAME, FareTransferRule.class, OPTIONAL, + new StringField(FareTransferRule.FROM_LEG_GROUP_ID_NAME, OPTIONAL).isReferenceTo(FARE_LEG_RULES), + new StringField(FareTransferRule.TO_LEG_GROUP_ID_NAME, OPTIONAL).isReferenceTo(FARE_LEG_RULES), + new IntegerField(FareTransferRule.TRANSFER_COUNT_NAME, OPTIONAL, -1, Integer.MAX_VALUE), + new IntegerField(FareTransferRule.DURATION_LIMIT_NAME, OPTIONAL), + new IntegerField(FareTransferRule.DURATION_LIMIT_TYPE_NAME, OPTIONAL), + new IntegerField(FareTransferRule.FARE_TRANSFER_TYPE_NAME, REQUIRED), + new StringField(FareTransferRule.FARE_PRODUCT_ID_NAME, OPTIONAL).isReferenceTo(FARE_PRODUCTS) + ) + .restrictDelete() + .keyFieldIsNotUnique() + .addPrimaryKeyNames( + FareTransferRule.FROM_LEG_GROUP_ID_NAME, + FareTransferRule.TO_LEG_GROUP_ID_NAME, + FareTransferRule.FARE_PRODUCT_ID_NAME, + FareTransferRule.TRANSFER_COUNT_NAME, + FareTransferRule.DURATION_LIMIT_NAME + ); + + public static final Table NETWORKS = new Table(Network.TABLE_NAME, Network.class, OPTIONAL, + new StringField(Network.NETWORK_ID_NAME, REQUIRED), + new StringField(Network.NETWORK_NAME_NAME, OPTIONAL) + ) + .restrictDelete() + .addPrimaryKeyNames(Network.NETWORK_ID_NAME); + + public static final Table ROUTE_NETWORKS = new Table(RouteNetwork.TABLE_NAME, RouteNetwork.class, OPTIONAL, + new StringField(RouteNetwork.NETWORK_ID_NAME, REQUIRED).isReferenceTo(NETWORKS), + new StringField(RouteNetwork.ROUTE_ID_NAME, REQUIRED).isReferenceTo(ROUTES) + ) + .keyFieldIsNotUnique() + .addPrimaryKeyNames(RouteNetwork.ROUTE_ID_NAME); + + // GTFS reference: https://developers.google.com/transit/gtfs/reference#fare_rulestxt public static final Table FARE_RULES = new Table("fare_rules", FareRule.class, OPTIONAL, new StringField("fare_id", REQUIRED).isReferenceTo(FARE_ATTRIBUTES), @@ -437,12 +552,21 @@ public Table (String name, Class entityClass, Requirement requ CALENDAR, SCHEDULE_EXCEPTIONS, CALENDAR_DATES, + TIME_FRAMES, FARE_ATTRIBUTES, + FARE_MEDIAS, + FARE_PRODUCTS, + NETWORKS, + FARE_LEG_RULES, + FARE_TRANSFER_RULES, FEED_INFO, ROUTES, + ROUTE_NETWORKS, PATTERNS, SHAPES, STOPS, + AREAS, + STOP_AREAS, FARE_RULES, PATTERN_STOP, TRANSFERS, @@ -659,7 +783,7 @@ public CsvReader getCsvReader(ZipFile zipFile, SQLErrorStorage sqlErrorStorage) Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); - if (e.getName().endsWith(tableFileName)) { + if (Paths.get(e.getName()).getFileName().toString().equals(tableFileName)) { entry = e; if (sqlErrorStorage != null) sqlErrorStorage.storeError(NewGTFSError.forTable(this, TABLE_IN_SUBDIRECTORY)); break; diff --git a/src/main/java/com/conveyal/gtfs/model/Area.java b/src/main/java/com/conveyal/gtfs/model/Area.java new file mode 100644 index 000000000..c8e1f7467 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/Area.java @@ -0,0 +1,87 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class Area extends Entity { + + private static final long serialVersionUID = -2825890165823575940L; + public String area_id; + public String area_name; + public String feed_id; + + public static final String TABLE_NAME = "areas"; + public static final String AREA_ID_NAME = "area_id"; + public static final String AREA_NAME_NAME = "area_name"; + + + @Override + public String getId () { + return area_id; + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#AREAS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, area_id); + statement.setString(oneBasedIndex, area_name); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + Area area = new Area(); + area.id = row + 1; // offset line number by 1 to account for 0-based row index + area.area_id = getStringField(AREA_ID_NAME, true); + area.area_name = getStringField(AREA_NAME_NAME, false); + area.feed = feed; + area.feed_id = feed.feedId; + feed.areas.put(area.getId(), area); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] {AREA_ID_NAME, AREA_NAME_NAME}); + } + + @Override + public void writeOneRow(Area area) throws IOException { + writeStringField(area.area_id); + writeStringField(area.area_name); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.areas.values().iterator(); + } + } + +} + diff --git a/src/main/java/com/conveyal/gtfs/model/Entity.java b/src/main/java/com/conveyal/gtfs/model/Entity.java index 29657b6e6..20ba92488 100644 --- a/src/main/java/com/conveyal/gtfs/model/Entity.java +++ b/src/main/java/com/conveyal/gtfs/model/Entity.java @@ -30,17 +30,20 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.Charset; +import java.nio.file.Paths; import java.sql.JDBCType; import java.sql.PreparedStatement; import java.sql.SQLException; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.Arrays; import java.util.Enumeration; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -280,15 +283,16 @@ protected V getRefField(String column, boolean required, Map target * @param zip the zip file from which to read a table */ public void loadTable(ZipFile zip) throws IOException { - ZipEntry entry = zip.getEntry(tableName + ".txt"); + String fileName = tableName + ".txt"; + ZipEntry entry = zip.getEntry(fileName); if (entry == null) { Enumeration entries = zip.entries(); // check if table is contained within sub-directory while (entries.hasMoreElements()) { ZipEntry e = entries.nextElement(); - if (e.getName().endsWith(tableName + ".txt")) { + if (Paths.get(e.getName()).getFileName().toString().equals(fileName)) { entry = e; - feed.errors.add(new TableInSubdirectoryError(tableName, entry.getName().replace(tableName + ".txt", ""))); + feed.errors.add(new TableInSubdirectoryError(tableName, entry.getName().replace(fileName, ""))); } } /* This GTFS table did not exist in the zip. */ @@ -498,4 +502,15 @@ public static final String human (long n) { if (n >= 1000) return String.format("%.1fk", n/1000.0); else return String.format("%d", n); } + + /** + * Creates a primary key from the provided fields. It is acceptable for a field that makes up the primary key to be + * optional! In this case the null value is represented with "empty". + */ + protected static String createPrimaryKey(Object... fields) { + return Arrays + .stream(fields) + .map(id -> id == null ? "empty" : id.toString()) + .collect(Collectors.joining("_")); + } } diff --git a/src/main/java/com/conveyal/gtfs/model/FareLegRule.java b/src/main/java/com/conveyal/gtfs/model/FareLegRule.java new file mode 100644 index 000000000..d1c417d20 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/FareLegRule.java @@ -0,0 +1,138 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class FareLegRule extends Entity { + + private static final long serialVersionUID = -795847376633855940L; + + public String leg_group_id; + public String network_id; + public String from_area_id; + public String to_area_id; + public String from_timeframe_group_id; + public String to_timeframe_group_id; + public String fare_product_id; + public int rule_priority = INT_MISSING; + + public String feed_id; + + public static final String TABLE_NAME = "fare_leg_rules"; + public static final String LEG_GROUP_ID_NAME = "leg_group_id"; + public static final String NETWORK_ID_NAME = "network_id"; + public static final String FROM_AREA_ID_NAME = "from_area_id"; + public static final String TO_AREA_ID_NAME = "to_area_id"; + public static final String FROM_TIMEFRAME_GROUP_ID_NAME = "from_timeframe_group_id"; + public static final String TO_TIMEFRAME_GROUP_ID_NAME = "to_timeframe_group_id"; + public static final String FARE_PRODUCT_ID_NAME = "fare_product_id"; + public static final String RULE_PRIORITY_NAME = "rule_priority"; + + @Override + public String getId () { + return createPrimaryKey( + network_id, + from_area_id, + to_area_id, + from_timeframe_group_id, + to_timeframe_group_id, + fare_product_id + ); + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#FARE_LEG_RULES}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, leg_group_id); + statement.setString(oneBasedIndex++, network_id); + statement.setString(oneBasedIndex++, from_area_id); + statement.setString(oneBasedIndex++, to_area_id); + statement.setString(oneBasedIndex++, from_timeframe_group_id); + statement.setString(oneBasedIndex++, to_timeframe_group_id); + statement.setString(oneBasedIndex++, fare_product_id); + setIntParameter(statement, oneBasedIndex, rule_priority); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + FareLegRule fareLegRule = new FareLegRule(); + fareLegRule.id = row + 1; // offset line number by 1 to account for 0-based row index + fareLegRule.leg_group_id = getStringField(LEG_GROUP_ID_NAME, false); + fareLegRule.network_id = getStringField(NETWORK_ID_NAME, false); + fareLegRule.from_area_id = getStringField(FROM_AREA_ID_NAME, false); + fareLegRule.to_area_id = getStringField(TO_AREA_ID_NAME, false); + fareLegRule.from_timeframe_group_id = getStringField(FROM_TIMEFRAME_GROUP_ID_NAME, false); + fareLegRule.to_timeframe_group_id = getStringField(TO_TIMEFRAME_GROUP_ID_NAME, false); + fareLegRule.fare_product_id = getStringField(FARE_PRODUCT_ID_NAME, true); + fareLegRule.rule_priority = getIntField(RULE_PRIORITY_NAME, false, 0, Integer.MAX_VALUE); + if (fareLegRule.rule_priority == INT_MISSING) { + // An empty value for rule_priority is treated as zero. + fareLegRule.rule_priority = 0; + } + fareLegRule.feed = feed; + fareLegRule.feed_id = feed.feedId; + feed.fare_leg_rules.put(fareLegRule.getId(), fareLegRule); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] { + LEG_GROUP_ID_NAME, + NETWORK_ID_NAME, + FROM_AREA_ID_NAME, + TO_AREA_ID_NAME, + FROM_TIMEFRAME_GROUP_ID_NAME, + TO_TIMEFRAME_GROUP_ID_NAME, + FARE_PRODUCT_ID_NAME, + RULE_PRIORITY_NAME + }); + } + + @Override + public void writeOneRow(FareLegRule fareLegRule) throws IOException { + writeStringField(fareLegRule.leg_group_id); + writeStringField(fareLegRule.network_id); + writeStringField(fareLegRule.from_area_id); + writeStringField(fareLegRule.to_area_id); + writeStringField(fareLegRule.from_timeframe_group_id); + writeStringField(fareLegRule.to_timeframe_group_id); + writeStringField(fareLegRule.fare_product_id); + writeIntField(fareLegRule.rule_priority); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.fare_leg_rules.values().iterator(); + } + } +} + + diff --git a/src/main/java/com/conveyal/gtfs/model/FareMedia.java b/src/main/java/com/conveyal/gtfs/model/FareMedia.java new file mode 100644 index 000000000..a7927c014 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/FareMedia.java @@ -0,0 +1,97 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class FareMedia extends Entity { + + private static final long serialVersionUID = -4968771968571945940L; + + public String fare_media_id; + public String fare_media_name; + public int fare_media_type; + public String feed_id; + + public static final String TABLE_NAME = "fare_media"; + public static final String FARE_MEDIA_ID_NAME = "fare_media_id"; + public static final String FARE_MEDIA_NAME_NAME = "fare_media_name"; + public static final String FARE_MEDIA_TYPE_NAME = "fare_media_type"; + + @Override + public String getId () { + return fare_media_id; + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#FARE_MEDIAS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, fare_media_id); + statement.setString(oneBasedIndex++, fare_media_name); + setIntParameter(statement, oneBasedIndex, fare_media_type); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + FareMedia fareMedia = new FareMedia(); + fareMedia.id = row + 1; // offset line number by 1 to account for 0-based row index + fareMedia.fare_media_id = getStringField(FARE_MEDIA_ID_NAME, true); + fareMedia.fare_media_name = getStringField(FARE_MEDIA_NAME_NAME, false); + fareMedia.fare_media_type = getIntField(FARE_MEDIA_TYPE_NAME, true, 0, 4); + fareMedia.feed = feed; + fareMedia.feed_id = feed.feedId; + feed.fare_medias.put(fareMedia.getId(), fareMedia); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] { + FARE_MEDIA_ID_NAME, + FARE_MEDIA_NAME_NAME, + FARE_MEDIA_TYPE_NAME + }); + } + + @Override + public void writeOneRow(FareMedia fareMedia) throws IOException { + writeStringField(fareMedia.fare_media_id); + writeStringField(fareMedia.fare_media_name); + writeIntField(fareMedia.fare_media_type); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.fare_medias.values().iterator(); + } + } +} + + + diff --git a/src/main/java/com/conveyal/gtfs/model/FareProduct.java b/src/main/java/com/conveyal/gtfs/model/FareProduct.java new file mode 100644 index 000000000..68600b218 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/FareProduct.java @@ -0,0 +1,113 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class FareProduct extends Entity { + + private static final long serialVersionUID = -5678890165823575940L; + + public String fare_product_id; + public String fare_product_name; + public String fare_media_id; + public double amount; + public String currency; + public String feed_id; + + public static final String TABLE_NAME = "fare_products"; + public static final String FARE_PRODUCT_ID_NAME = "fare_product_id"; + public static final String FARE_PRODUCT_NAME_NAME = "fare_product_name"; + public static final String FARE_MEDIA_ID_NAME = "fare_media_id"; + public static final String AMOUNT_NAME = "amount"; + public static final String CURRENCY_NAME = "currency"; + + + @Override + public String getId () { + return createPrimaryKey(fare_product_id, fare_media_id); + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#FARE_PRODUCTS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, fare_product_id); + statement.setString(oneBasedIndex++, fare_product_name); + statement.setString(oneBasedIndex++, fare_media_id); + setDoubleParameter(statement, oneBasedIndex++, amount); + statement.setString(oneBasedIndex, currency); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + FareProduct fareProduct = new FareProduct(); + fareProduct.id = row + 1; // offset line number by 1 to account for 0-based row index + fareProduct.fare_product_id = getStringField(FARE_PRODUCT_ID_NAME, true); + fareProduct.fare_product_name = getStringField(FARE_PRODUCT_NAME_NAME, false); + fareProduct.fare_media_id = getStringField(FARE_MEDIA_ID_NAME, false); + fareProduct.amount = getDoubleField(AMOUNT_NAME, true, 0.0, Double.MAX_VALUE); + fareProduct.currency = getStringField(CURRENCY_NAME, true); + fareProduct.feed = feed; + fareProduct.feed_id = feed.feedId; + feed.fare_products.put(fareProduct.getId(), fareProduct); + + /* + Check referential integrity without storing references. + */ + getRefField(FARE_MEDIA_ID_NAME, false, feed.fare_medias); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] { + FARE_PRODUCT_ID_NAME, + FARE_PRODUCT_NAME_NAME, + FARE_MEDIA_ID_NAME, + AMOUNT_NAME, CURRENCY_NAME + }); + } + + @Override + public void writeOneRow(FareProduct fareProduct) throws IOException { + writeStringField(fareProduct.fare_product_id); + writeStringField(fareProduct.fare_product_name); + writeStringField(fareProduct.fare_media_id); + writeDoubleField(fareProduct.amount); + writeStringField(fareProduct.currency); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.fare_products.values().iterator(); + } + } +} + + diff --git a/src/main/java/com/conveyal/gtfs/model/FareTransferRule.java b/src/main/java/com/conveyal/gtfs/model/FareTransferRule.java new file mode 100644 index 000000000..d22b43fcd --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/FareTransferRule.java @@ -0,0 +1,128 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class FareTransferRule extends Entity { + + private static final long serialVersionUID = -958672649111736468L; + + public String from_leg_group_id; + public String to_leg_group_id; + public int transfer_count = INT_MISSING; + public int duration_limit; + public int duration_limit_type; + public int fare_transfer_type; + public String fare_product_id; + + public String feed_id; + + public static final String TABLE_NAME = "fare_transfer_rules"; + public static final String FROM_LEG_GROUP_ID_NAME = "from_leg_group_id"; + public static final String TO_LEG_GROUP_ID_NAME = "to_leg_group_id"; + public static final String TRANSFER_COUNT_NAME = "transfer_count"; + public static final String DURATION_LIMIT_NAME = "duration_limit"; + public static final String DURATION_LIMIT_TYPE_NAME = "duration_limit_type"; + public static final String FARE_TRANSFER_TYPE_NAME = "fare_transfer_type"; + public static final String FARE_PRODUCT_ID_NAME = "fare_product_id"; + + @Override + public String getId () { + return createPrimaryKey( + from_leg_group_id, + to_leg_group_id, + fare_product_id, + transfer_count, + duration_limit + ); + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#FARE_TRANSFER_RULES}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, from_leg_group_id); + statement.setString(oneBasedIndex++, to_leg_group_id); + setIntParameter(statement, oneBasedIndex++, transfer_count); + setIntParameter(statement, oneBasedIndex++, duration_limit); + setIntParameter(statement, oneBasedIndex++, duration_limit_type); + setIntParameter(statement, oneBasedIndex++, fare_transfer_type); + statement.setString(oneBasedIndex, fare_product_id); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + FareTransferRule fareTransferRule = new FareTransferRule(); + fareTransferRule.id = row + 1; // offset line number by 1 to account for 0-based row index + fareTransferRule.from_leg_group_id = getStringField(FROM_LEG_GROUP_ID_NAME, false); + fareTransferRule.to_leg_group_id = getStringField(TO_LEG_GROUP_ID_NAME, false); + fareTransferRule.transfer_count = getIntField(TRANSFER_COUNT_NAME, false, -1, Integer.MAX_VALUE, INT_MISSING); + fareTransferRule.duration_limit = getIntField(DURATION_LIMIT_NAME, false, 0, Integer.MAX_VALUE); + fareTransferRule.duration_limit_type = getIntField(DURATION_LIMIT_TYPE_NAME, false, 0, 3); + fareTransferRule.fare_transfer_type = getIntField(FARE_TRANSFER_TYPE_NAME, true, 0, 2); + fareTransferRule.fare_product_id = getStringField(FARE_PRODUCT_ID_NAME, false); + fareTransferRule.feed = feed; + fareTransferRule.feed_id = feed.feedId; + feed.fare_transfer_rules.put(fareTransferRule.getId(), fareTransferRule); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] { + FROM_LEG_GROUP_ID_NAME, + TO_LEG_GROUP_ID_NAME, + TRANSFER_COUNT_NAME, + DURATION_LIMIT_NAME, + DURATION_LIMIT_TYPE_NAME, + FARE_TRANSFER_TYPE_NAME, + FARE_PRODUCT_ID_NAME, + }); + } + + @Override + public void writeOneRow(FareTransferRule fareTransferRule) throws IOException { + writeStringField(fareTransferRule.from_leg_group_id); + writeStringField(fareTransferRule.to_leg_group_id); + writeIntField(fareTransferRule.transfer_count); + writeIntField(fareTransferRule.duration_limit); + writeIntField(fareTransferRule.duration_limit_type); + writeIntField(fareTransferRule.fare_transfer_type); + writeStringField(fareTransferRule.fare_product_id); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.fare_transfer_rules.values().iterator(); + } + } +} + + + diff --git a/src/main/java/com/conveyal/gtfs/model/Network.java b/src/main/java/com/conveyal/gtfs/model/Network.java new file mode 100644 index 000000000..4d509d5df --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/Network.java @@ -0,0 +1,86 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class Network extends Entity { + + private static final long serialVersionUID = -4739475958736362940L; + public String network_id; + public String network_name; + public String feed_id; + + public static final String TABLE_NAME = "networks"; + public static final String NETWORK_ID_NAME = "network_id"; + public static final String NETWORK_NAME_NAME = "network_name"; + + @Override + public String getId () { + return network_id; + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#NETWORKS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, network_id); + statement.setString(oneBasedIndex, network_name); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + Network network = new Network(); + network.id = row + 1; // offset line number by 1 to account for 0-based row index + network.network_id = getStringField(NETWORK_ID_NAME, true); + network.network_name = getStringField(NETWORK_NAME_NAME, false); + network.feed = feed; + network.feed_id = feed.feedId; + feed.networks.put(network.getId(), network); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] {NETWORK_ID_NAME, NETWORK_NAME_NAME}); + } + + @Override + public void writeOneRow(Network network) throws IOException { + writeStringField(network.network_id); + writeStringField(network.network_name); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.networks.values().iterator(); + } + } + +} + diff --git a/src/main/java/com/conveyal/gtfs/model/Route.java b/src/main/java/com/conveyal/gtfs/model/Route.java index 31288cafa..a55ea29fc 100644 --- a/src/main/java/com/conveyal/gtfs/model/Route.java +++ b/src/main/java/com/conveyal/gtfs/model/Route.java @@ -36,6 +36,9 @@ public class Route extends Entity { // implements Entity.Factory public String feed_id; public int continuous_pickup = INT_MISSING; public int continuous_drop_off = INT_MISSING; + public String network_id; + + public static final String TABLE_NAME = "routes"; @Override public String getId () { @@ -68,12 +71,13 @@ public void setStatementParameters(PreparedStatement statement, boolean setDefau setIntParameter(statement, oneBasedIndex++, 0); setIntParameter(statement, oneBasedIndex++, continuous_pickup); setIntParameter(statement, oneBasedIndex++, continuous_drop_off); + statement.setString(oneBasedIndex, network_id); } public static class Loader extends Entity.Loader { public Loader(GTFSFeed feed) { - super(feed, "routes"); + super(feed, TABLE_NAME); } @Override @@ -110,6 +114,7 @@ public void loadOneRow() throws IOException { r.route_branding_url = getUrlField("route_branding_url", false); r.continuous_pickup = getIntField("continuous_pickup", false, 0, 3, INT_MISSING); r.continuous_drop_off = getIntField("continuous_drop_off", false, 0, 3, INT_MISSING); + r.network_id = getStringField("network_id", false); r.feed = feed; r.feed_id = feed.feedId; // Attempting to put a null key or value will cause an NPE in BTreeMap @@ -120,7 +125,7 @@ public void loadOneRow() throws IOException { public static class Writer extends Entity.Writer { public Writer (GTFSFeed feed) { - super(feed, "routes"); + super(feed, TABLE_NAME); } @Override @@ -138,6 +143,7 @@ public void writeHeaders() throws IOException { writeStringField("route_sort_order"); writeStringField("continuous_pickup"); writeStringField("continuous_drop_off"); + writeStringField("network_id"); endRecord(); } @@ -156,6 +162,7 @@ public void writeOneRow(Route r) throws IOException { writeIntField(r.route_sort_order); writeIntField(r.continuous_pickup); writeIntField(r.continuous_drop_off); + writeStringField(r.network_id); endRecord(); } diff --git a/src/main/java/com/conveyal/gtfs/model/RouteNetwork.java b/src/main/java/com/conveyal/gtfs/model/RouteNetwork.java new file mode 100644 index 000000000..4cdf17025 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/RouteNetwork.java @@ -0,0 +1,88 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class RouteNetwork extends Entity { + + private static final long serialVersionUID = -4739475958736362940L; + public String network_id; + public String route_id; + public String feed_id; + + public static final String TABLE_NAME = "route_networks"; + public static final String NETWORK_ID_NAME = "network_id"; + public static final String ROUTE_ID_NAME = "route_id"; + + @Override + public String getId () { + return route_id; + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#ROUTE_NETWORKS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, network_id); + statement.setString(oneBasedIndex, route_id); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + RouteNetwork routeNetwork = new RouteNetwork(); + routeNetwork.id = row + 1; // offset line number by 1 to account for 0-based row index + routeNetwork.network_id = getStringField(NETWORK_ID_NAME, true); + routeNetwork.route_id = getStringField(ROUTE_ID_NAME, true); + routeNetwork.feed = feed; + routeNetwork.feed_id = feed.feedId; + feed.route_networks.put(routeNetwork.getId(), routeNetwork); + getRefField(NETWORK_ID_NAME, true, feed.networks); + getRefField(ROUTE_ID_NAME, true, feed.routes); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] {NETWORK_ID_NAME, ROUTE_ID_NAME}); + } + + @Override + public void writeOneRow(RouteNetwork routeNetwork) throws IOException { + writeStringField(routeNetwork.network_id); + writeStringField(routeNetwork.route_id); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.route_networks.values().iterator(); + } + } + +} + diff --git a/src/main/java/com/conveyal/gtfs/model/StopArea.java b/src/main/java/com/conveyal/gtfs/model/StopArea.java new file mode 100644 index 000000000..04f0df456 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/StopArea.java @@ -0,0 +1,93 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class StopArea extends Entity { + + private static final long serialVersionUID = -2825890165823575940L; + public String area_id; + public String stop_id; + public String feed_id; + + public static final String TABLE_NAME = "stop_areas"; + public static final String AREA_ID_NAME = "area_id"; + public static final String STOP_ID_NAME = "stop_id"; + + @Override + public String getId () { + return createPrimaryKey(area_id, stop_id); + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#STOP_AREAS}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, area_id); + statement.setString(oneBasedIndex, stop_id); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + StopArea stopArea = new StopArea(); + stopArea.id = row + 1; // offset line number by 1 to account for 0-based row index + stopArea.area_id = getStringField(AREA_ID_NAME, true); + stopArea.stop_id = getStringField(STOP_ID_NAME, true); + stopArea.feed = feed; + stopArea.feed_id = feed.feedId; + feed.stop_areas.put(stopArea.getId(), stopArea); + + /* + Check referential integrity without storing references. + */ + getRefField(AREA_ID_NAME, true, feed.areas); + getRefField(STOP_ID_NAME, true, feed.stops); + + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] {AREA_ID_NAME, STOP_ID_NAME}); + } + + @Override + public void writeOneRow(StopArea stopArea) throws IOException { + writeStringField(stopArea.area_id); + writeStringField(stopArea.stop_id); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.stop_areas.values().iterator(); + } + } +} + + diff --git a/src/main/java/com/conveyal/gtfs/model/TimeFrame.java b/src/main/java/com/conveyal/gtfs/model/TimeFrame.java new file mode 100644 index 000000000..20a1b7cb2 --- /dev/null +++ b/src/main/java/com/conveyal/gtfs/model/TimeFrame.java @@ -0,0 +1,110 @@ +package com.conveyal.gtfs.model; + +import com.conveyal.gtfs.GTFSFeed; + +import java.io.IOException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Iterator; + +public class TimeFrame extends Entity { + + private static final long serialVersionUID = -194783727784855940L; + + public String timeframe_group_id; + public int start_time = INT_MISSING; + public int end_time = INT_MISSING; + public String service_id; + public String feed_id; + + public static final String TABLE_NAME = "timeframes"; + public static final String TIME_FRAME_GROUP_ID_NAME = "timeframe_group_id"; + public static final String START_TIME_NAME = "start_time"; + public static final String END_TIME_NAME = "end_time"; + public static final String SERVICE_ID_NAME = "service_id"; + + @Override + public String getId () { + return createPrimaryKey(timeframe_group_id, start_time, end_time, service_id); + } + + /** + * Sets the parameters for a prepared statement following the parameter order defined in + * {@link com.conveyal.gtfs.loader.Table#TIME_FRAMES}. JDBC prepared statement parameters use a one-based index. + */ + @Override + public void setStatementParameters(PreparedStatement statement, boolean setDefaultId) throws SQLException { + int oneBasedIndex = 1; + if (!setDefaultId) statement.setInt(oneBasedIndex++, id); + statement.setString(oneBasedIndex++, timeframe_group_id); + setIntParameter(statement, oneBasedIndex++, start_time); + setIntParameter(statement, oneBasedIndex++, end_time); + statement.setString(oneBasedIndex, service_id); + } + + public static class Loader extends Entity.Loader { + + public Loader(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + protected boolean isRequired() { + return false; + } + + @Override + public void loadOneRow() throws IOException { + TimeFrame timeFrame = new TimeFrame(); + timeFrame.id = row + 1; // offset line number by 1 to account for 0-based row index + timeFrame.timeframe_group_id = getStringField(TIME_FRAME_GROUP_ID_NAME, true); + timeFrame.start_time = getTimeField(START_TIME_NAME, false); + if (timeFrame.start_time == INT_MISSING) { + // An empty value is considered the start of the day (00:00:00). + timeFrame.start_time = 0; + } + timeFrame.end_time = getTimeField(END_TIME_NAME, false); + if (timeFrame.end_time == INT_MISSING) { + // An empty value is considered the end of the day (24:00:00). + timeFrame.end_time = 86400; + } + timeFrame.service_id = getStringField(SERVICE_ID_NAME, true); + timeFrame.feed = feed; + timeFrame.feed_id = feed.feedId; + feed.time_frames.put(timeFrame.getId(), timeFrame); + } + + } + + public static class Writer extends Entity.Writer { + public Writer(GTFSFeed feed) { + super(feed, TABLE_NAME); + } + + @Override + public void writeHeaders() throws IOException { + writer.writeRecord(new String[] { + TIME_FRAME_GROUP_ID_NAME, + START_TIME_NAME, + END_TIME_NAME, + SERVICE_ID_NAME + }); + } + + @Override + public void writeOneRow(TimeFrame timeFrame) throws IOException { + writeStringField(timeFrame.timeframe_group_id); + writeTimeField(timeFrame.start_time); + writeTimeField(timeFrame.end_time); + writeStringField(timeFrame.service_id); + endRecord(); + } + + @Override + public Iterator iterator() { + return this.feed.time_frames.values().iterator(); + } + } +} + + diff --git a/src/test/java/com/conveyal/gtfs/GTFSFaresV2Test.java b/src/test/java/com/conveyal/gtfs/GTFSFaresV2Test.java new file mode 100644 index 000000000..d24829ae7 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/GTFSFaresV2Test.java @@ -0,0 +1,596 @@ +package com.conveyal.gtfs; + +import com.conveyal.gtfs.dto.AreaDTO; +import com.conveyal.gtfs.dto.FareLegRuleDTO; +import com.conveyal.gtfs.dto.FareMediaDTO; +import com.conveyal.gtfs.dto.FareProductDTO; +import com.conveyal.gtfs.dto.FareTransferRuleDTO; +import com.conveyal.gtfs.dto.NetworkDTO; +import com.conveyal.gtfs.dto.RouteNetworkDTO; +import com.conveyal.gtfs.dto.StopAreaDTO; +import com.conveyal.gtfs.dto.TimeFrameDTO; +import com.conveyal.gtfs.loader.FeedLoadResult; +import com.conveyal.gtfs.loader.JdbcTableWriter; +import com.conveyal.gtfs.loader.Table; +import com.conveyal.gtfs.model.Area; +import com.conveyal.gtfs.model.FareLegRule; +import com.conveyal.gtfs.model.FareMedia; +import com.conveyal.gtfs.model.FareProduct; +import com.conveyal.gtfs.model.FareTransferRule; +import com.conveyal.gtfs.model.Network; +import com.conveyal.gtfs.model.RouteNetwork; +import com.conveyal.gtfs.model.StopArea; +import com.conveyal.gtfs.model.TimeFrame; +import com.conveyal.gtfs.util.InvalidNamespaceException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.zip.ZipFile; + +import static com.conveyal.gtfs.GTFS.load; +import static com.conveyal.gtfs.GTFS.makeSnapshot; +import static com.conveyal.gtfs.GTFS.validate; +import static com.conveyal.gtfs.TestUtils.assertResultValue; +import static com.conveyal.gtfs.TestUtils.assertThatSqlQueryYieldsZeroRows; +import static com.conveyal.gtfs.TestUtils.checkFileTestCases; +import static com.conveyal.gtfs.TestUtils.getColumnsForId; +import static com.conveyal.gtfs.TestUtils.getResultSetForId; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GTFSFaresV2Test { + + private static final Logger LOG = LoggerFactory.getLogger(GTFSFaresV2Test.class); + private static String faresZipFileName; + public static String faresDBName; + private static DataSource faresDataSource; + private static String faresNamespace; + private static final ObjectMapper mapper = new ObjectMapper(); + + private static JdbcTableWriter createTestTableWriter (Table table) throws InvalidNamespaceException { + return new JdbcTableWriter(table, faresDataSource, faresNamespace); + } + + @BeforeAll + public static void setUpClass() throws IOException { + String folderName = "fake-agency-with-fares-v2"; + faresZipFileName = TestUtils.zipFolderFiles(folderName, true); + // create a new database + faresDBName = TestUtils.generateNewDB(); + String dbConnectionUrl = String.format("jdbc:postgresql://localhost/%s", faresDBName); + faresDataSource = TestUtils.createTestDataSource(dbConnectionUrl); + // load feed into db + FeedLoadResult feedLoadResult = load(faresZipFileName, faresDataSource); + faresNamespace = feedLoadResult.uniqueIdentifier; + // validate feed to create additional tables + validate(faresNamespace, faresDataSource); + // Create an empty snapshot to create a new namespace and all the tables + FeedLoadResult result = makeSnapshot(null, faresDataSource, false); + faresNamespace = result.uniqueIdentifier; + } + + @AfterAll + public static void tearDownClass() { + TestUtils.dropDB(faresDBName); + } + + /** + * Make sure a round-trip of loading fares v2 data and then writing this to another zip file can be performed. + */ + @Test + void canDoRoundTripLoadAndWriteToZipFile() throws IOException { + // create a temp file for this test + File outZip = File.createTempFile("fares-v2-output", ".zip"); + + // delete file to make sure we can assert that this program created the file + outZip.delete(); + + GTFSFeed feed = GTFSFeed.fromFile(faresZipFileName); + feed.toFile(outZip.getAbsolutePath()); + feed.close(); + assertTrue(outZip.exists()); + + // assert that rows of data were written to files within the zipfile + ZipFile zip = new ZipFile(outZip); + + TestUtils.FileTestCase[] fileTestCases = { + new TestUtils.FileTestCase( + "areas.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("area_id", "area_bl"), + new TestUtils.DataExpectation("area_name", "Blue Line") + } + ), + new TestUtils.FileTestCase( + "fare_leg_rules.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("leg_group_id", "leg_airport_rapid_transit_quick_subway"), + new TestUtils.DataExpectation("network_id", "rapid_transit"), + new TestUtils.DataExpectation("from_area_id", "area_bl_airport") + } + ), + new TestUtils.FileTestCase( + "fare_media.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("fare_media_id", "cash"), + new TestUtils.DataExpectation("fare_media_name", "Cash"), + new TestUtils.DataExpectation("fare_media_type", "0") + } + ), + new TestUtils.FileTestCase( + "fare_products.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("fare_product_id", "prod_boat_zone_1"), + new TestUtils.DataExpectation("fare_product_name", "Ferry Zone 1 one-way fare"), + new TestUtils.DataExpectation("fare_media_id", "cash"), + new TestUtils.DataExpectation("amount", "6.5000000"), + new TestUtils.DataExpectation("currency", "USD") + } + ), + new TestUtils.FileTestCase( + "fare_transfer_rules.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("from_leg_group_id", "leg_airport_rapid_transit_quick_subway"), + new TestUtils.DataExpectation("to_leg_group_id", "leg_local_bus_quick_subway"), + new TestUtils.DataExpectation("transfer_count", ""), + new TestUtils.DataExpectation("duration_limit", "7200"), + new TestUtils.DataExpectation("duration_limit_type", "1"), + new TestUtils.DataExpectation("fare_transfer_type", "0"), + new TestUtils.DataExpectation("fare_product_id", "prod_rapid_transit_quick_subway") + } + ), + new TestUtils.FileTestCase( + "networks.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("network_id", "1"), + new TestUtils.DataExpectation("network_name", "Forbidden because network id is defined in routes") + } + ), + new TestUtils.FileTestCase( + "route_networks.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("network_id", "1"), + new TestUtils.DataExpectation("route_id", "1") + } + ), + new TestUtils.FileTestCase( + "stop_areas.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("stop_id", "4u6g"), + new TestUtils.DataExpectation("area_id", "area_route_426_downtown") + } + ), + new TestUtils.FileTestCase( + "timeframes.txt", + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("timeframe_group_id", "timeframe_sumner_tunnel_closure"), + new TestUtils.DataExpectation("start_time", "00:00:00"), + new TestUtils.DataExpectation("end_time", "02:30:00"), + new TestUtils.DataExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57") + } + ) + }; + checkFileTestCases(zip, fileTestCases); + } + + @Test + void canCreateUpdateAndDeleteAreas() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String areaId = "area-id-1"; + AreaDTO createdArea = createArea(areaId); + assertEquals(createdArea.area_id, areaId); + + // Update. + String updatedAreaId = "area-id-2"; + createdArea.area_id = updatedAreaId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.AREAS); + String updateOutput = updateTableWriter.update( + createdArea.id, + mapper.writeValueAsString(createdArea), + true + ); + AreaDTO updatedAreaDTO = mapper.readValue(updateOutput, AreaDTO.class); + assertEquals(updatedAreaDTO.area_id, updatedAreaId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedAreaDTO.id, Table.AREAS); + while (resultSet.next()) { + assertResultValue(resultSet, Area.AREA_ID_NAME, equalTo(createdArea.area_id)); + assertResultValue(resultSet, Area.AREA_NAME_NAME,equalTo(createdArea.area_name)); + } + + // Delete. + deleteRecord(Table.AREAS, createdArea.id); + } + + @Test + void canCreateUpdateAndDeleteStopAreas() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String areaId = "area-id-1"; + StopAreaDTO createdStopArea = createStopArea(areaId); + assertEquals(createdStopArea.area_id, areaId); + + // Update. + String updatedAreaId = "area-id-2"; + createdStopArea.area_id = updatedAreaId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.STOP_AREAS); + String updateOutput = updateTableWriter.update( + createdStopArea.id, + mapper.writeValueAsString(createdStopArea), + true + ); + StopAreaDTO updatedStopAreaDTO = mapper.readValue(updateOutput, StopAreaDTO.class); + assertEquals(updatedStopAreaDTO.area_id, updatedAreaId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedStopAreaDTO.id, Table.STOP_AREAS); + while (resultSet.next()) { + assertResultValue(resultSet, StopArea.AREA_ID_NAME, equalTo(createdStopArea.area_id)); + assertResultValue(resultSet, StopArea.STOP_ID_NAME,equalTo(createdStopArea.stop_id)); + } + + // Delete. + deleteRecord(Table.STOP_AREAS, createdStopArea.id); + } + + @Test + void canCreateUpdateAndDeleteTimeFrames() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String timeFrameGroupId = "time-frame-group-id-1"; + TimeFrameDTO createdTimeFrame = createTimeFrame(timeFrameGroupId); + assertEquals(createdTimeFrame.timeframe_group_id, timeFrameGroupId); + + // Update. + String updatedTimeFrameGroupId = "time-frame-group-id-2"; + createdTimeFrame.timeframe_group_id = updatedTimeFrameGroupId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.TIME_FRAMES); + String updateOutput = updateTableWriter.update( + createdTimeFrame.id, + mapper.writeValueAsString(createdTimeFrame), + true + ); + TimeFrameDTO updatedTimeFrameDTO = mapper.readValue(updateOutput, TimeFrameDTO.class); + assertEquals(updatedTimeFrameDTO.timeframe_group_id, updatedTimeFrameGroupId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedTimeFrameDTO.id, Table.TIME_FRAMES); + while (resultSet.next()) { + assertResultValue(resultSet, TimeFrame.TIME_FRAME_GROUP_ID_NAME, equalTo(createdTimeFrame.timeframe_group_id)); + assertResultValue(resultSet, TimeFrame.START_TIME_NAME, equalTo(createdTimeFrame.start_time)); + assertResultValue(resultSet, TimeFrame.END_TIME_NAME, equalTo(createdTimeFrame.end_time)); + assertResultValue(resultSet, TimeFrame.SERVICE_ID_NAME, equalTo(createdTimeFrame.service_id)); + } + + // Delete. + deleteRecord(Table.TIME_FRAMES, createdTimeFrame.id); + } + + @Test + void canCreateUpdateAndDeleteNetworks() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String networkId = "network-id-1"; + NetworkDTO createdNetwork = createNetwork(networkId); + assertEquals(createdNetwork.network_id, networkId); + + // Update. + String updatedNetworkId = "network-id-2"; + createdNetwork.network_id = updatedNetworkId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.NETWORKS); + String updateOutput = updateTableWriter.update( + createdNetwork.id, + mapper.writeValueAsString(createdNetwork), + true + ); + NetworkDTO updatedNetworkDTO = mapper.readValue(updateOutput, NetworkDTO.class); + assertEquals(updatedNetworkDTO.network_id, updatedNetworkId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedNetworkDTO.id, Table.NETWORKS); + while (resultSet.next()) { + assertResultValue(resultSet, Network.NETWORK_ID_NAME, equalTo(createdNetwork.network_id)); + assertResultValue(resultSet, Network.NETWORK_NAME_NAME, equalTo(createdNetwork.network_name)); + } + + // Delete. + deleteRecord(Table.NETWORKS, createdNetwork.id); + } + + @Test + void canCreateUpdateAndDeleteRouteNetworks() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String networkId = "network-id-1"; + RouteNetworkDTO createdRouteNetwork = createRouteNetwork(networkId); + assertEquals(createdRouteNetwork.network_id, networkId); + + // Update. + String updatedRouteNetworkId = "network-id-2"; + createdRouteNetwork.network_id = updatedRouteNetworkId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.ROUTE_NETWORKS); + String updateOutput = updateTableWriter.update( + createdRouteNetwork.id, + mapper.writeValueAsString(createdRouteNetwork), + true + ); + RouteNetworkDTO updatedNetworkDTO = mapper.readValue(updateOutput, RouteNetworkDTO.class); + assertEquals(updatedNetworkDTO.network_id, updatedRouteNetworkId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedNetworkDTO.id, Table.ROUTE_NETWORKS); + while (resultSet.next()) { + assertResultValue(resultSet, RouteNetwork.NETWORK_ID_NAME, equalTo(createdRouteNetwork.network_id)); + assertResultValue(resultSet, RouteNetwork.ROUTE_ID_NAME, equalTo(createdRouteNetwork.route_id)); + } + + // Delete. + deleteRecord(Table.ROUTE_NETWORKS, createdRouteNetwork.id); + } + + @Test + void canCreateUpdateAndDeleteFareLegRules() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String legGroupId = "leg-group-id-1"; + FareLegRuleDTO createdFareLegRule = createFareLegRule(legGroupId); + assertEquals(createdFareLegRule.leg_group_id, legGroupId); + + // Update. + String updatedLegGroupId = "leg-group-id-2"; + createdFareLegRule.leg_group_id = updatedLegGroupId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.FARE_LEG_RULES); + String updateOutput = updateTableWriter.update( + createdFareLegRule.id, + mapper.writeValueAsString(createdFareLegRule), + true + ); + FareLegRuleDTO updatedFareLegRuleDTO = mapper.readValue(updateOutput, FareLegRuleDTO.class); + assertEquals(updatedFareLegRuleDTO.leg_group_id, updatedLegGroupId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedFareLegRuleDTO.id, Table.FARE_LEG_RULES); + while (resultSet.next()) { + assertResultValue(resultSet, FareLegRule.LEG_GROUP_ID_NAME, equalTo(createdFareLegRule.leg_group_id)); + assertResultValue(resultSet, FareLegRule.NETWORK_ID_NAME, equalTo(createdFareLegRule.network_id)); + assertResultValue(resultSet, FareLegRule.FROM_AREA_ID_NAME, equalTo(createdFareLegRule.from_area_id)); + assertResultValue(resultSet, FareLegRule.TO_AREA_ID_NAME, equalTo(createdFareLegRule.to_area_id)); + assertResultValue(resultSet, FareLegRule.FROM_TIMEFRAME_GROUP_ID_NAME, equalTo(createdFareLegRule.from_timeframe_group_id)); + assertResultValue(resultSet, FareLegRule.FARE_PRODUCT_ID_NAME, equalTo(createdFareLegRule.fare_product_id)); + assertResultValue(resultSet, FareLegRule.RULE_PRIORITY_NAME, equalTo(createdFareLegRule.rule_priority)); + } + + // Delete. + deleteRecord(Table.FARE_LEG_RULES, createdFareLegRule.id); + } + + @Test + void canCreateUpdateAndDeleteFareMedia() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String fareMediaId = "fare-media-id-1"; + FareMediaDTO createdFareMedia = createFareMedia(fareMediaId); + assertEquals(createdFareMedia.fare_media_id, fareMediaId); + + // Update. + String updatedFareMediaId = "fare-media-id-2"; + createdFareMedia.fare_media_id = updatedFareMediaId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.FARE_MEDIAS); + String updateOutput = updateTableWriter.update( + createdFareMedia.id, + mapper.writeValueAsString(createdFareMedia), + true + ); + FareMediaDTO updatedFareMediaDTO = mapper.readValue(updateOutput, FareMediaDTO.class); + assertEquals(updatedFareMediaDTO.fare_media_id, updatedFareMediaId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, updatedFareMediaDTO.id, Table.FARE_MEDIAS); + while (resultSet.next()) { + assertResultValue(resultSet, FareMedia.FARE_MEDIA_ID_NAME, equalTo(createdFareMedia.fare_media_id)); + assertResultValue(resultSet, FareMedia.FARE_MEDIA_NAME_NAME, equalTo(createdFareMedia.fare_media_name)); + assertResultValue(resultSet, FareMedia.FARE_MEDIA_TYPE_NAME, equalTo(createdFareMedia.fare_media_type)); + } + + // Delete. + deleteRecord(Table.FARE_MEDIAS, createdFareMedia.id); + } + + @Test + void canCreateUpdateAndDeleteFareProduct() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String fareProductId = "fare-product-id-1"; + FareProductDTO createdFareProduct = createFareProduct(fareProductId); + assertEquals(createdFareProduct.fare_product_id, fareProductId); + + // Update. + String updatedFareProductId = "fare-product-id-2"; + createdFareProduct.fare_product_id = updatedFareProductId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.FARE_PRODUCTS); + String updateOutput = updateTableWriter.update( + createdFareProduct.id, + mapper.writeValueAsString(createdFareProduct), + true + ); + FareProductDTO fareProductDTO = mapper.readValue(updateOutput, FareProductDTO.class); + assertEquals(fareProductDTO.fare_product_id, updatedFareProductId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, fareProductDTO.id, Table.FARE_PRODUCTS); + while (resultSet.next()) { + assertResultValue(resultSet, FareProduct.FARE_PRODUCT_ID_NAME, equalTo(createdFareProduct.fare_product_id)); + assertResultValue(resultSet, FareProduct.FARE_PRODUCT_NAME_NAME, equalTo(createdFareProduct.fare_product_name)); + assertResultValue(resultSet, FareProduct.FARE_MEDIA_ID_NAME, equalTo(createdFareProduct.fare_media_id)); + } + + // Delete. + deleteRecord(Table.FARE_PRODUCTS, createdFareProduct.id); + } + + @Test + void canCreateUpdateAndDeleteFareTransferRule() throws IOException, SQLException, InvalidNamespaceException { + // Create. + String fromLegGroupId = "from-leg-group-id-1"; + String toLegGroupId = "to-leg-group-id-1"; + FareTransferRuleDTO fareTransferRules = createFareTransferRules(fromLegGroupId, toLegGroupId); + assertEquals(fareTransferRules.from_leg_group_id, fromLegGroupId); + assertEquals(fareTransferRules.to_leg_group_id, toLegGroupId); + + // Update. + String updatedFromLegGroupId = "from-leg-group-id-2"; + String updatedToLegGroupId = "to-leg-group-id-2"; + fareTransferRules.from_leg_group_id = updatedFromLegGroupId; + fareTransferRules.to_leg_group_id = updatedToLegGroupId; + JdbcTableWriter updateTableWriter = createTestTableWriter(Table.FARE_TRANSFER_RULES); + String updateOutput = updateTableWriter.update( + fareTransferRules.id, + mapper.writeValueAsString(fareTransferRules), + true + ); + FareTransferRuleDTO fareTransferRuleDTO = mapper.readValue(updateOutput, FareTransferRuleDTO.class); + assertEquals(fareTransferRuleDTO.from_leg_group_id, updatedFromLegGroupId); + assertEquals(fareTransferRuleDTO.to_leg_group_id, updatedToLegGroupId); + + ResultSet resultSet = getResultSetForId(faresDataSource, faresNamespace, fareTransferRuleDTO.id, Table.FARE_TRANSFER_RULES); + while (resultSet.next()) { + assertResultValue(resultSet, FareTransferRule.FROM_LEG_GROUP_ID_NAME, equalTo(fareTransferRules.from_leg_group_id)); + assertResultValue(resultSet, FareTransferRule.TO_LEG_GROUP_ID_NAME, equalTo(fareTransferRules.to_leg_group_id)); + assertResultValue(resultSet, FareTransferRule.TRANSFER_COUNT_NAME, equalTo(fareTransferRules.transfer_count)); + assertResultValue(resultSet, FareTransferRule.DURATION_LIMIT_NAME, equalTo(fareTransferRules.duration_limit)); + assertResultValue(resultSet, FareTransferRule.FARE_TRANSFER_TYPE_NAME, equalTo(fareTransferRules.fare_transfer_type)); + assertResultValue(resultSet, FareTransferRule.FARE_PRODUCT_ID_NAME, equalTo(fareTransferRules.fare_product_id)); + } + + // Delete. + deleteRecord(Table.FARE_TRANSFER_RULES, fareTransferRules.id); + } + + private void deleteRecord(Table table, Integer id) throws InvalidNamespaceException, SQLException { + JdbcTableWriter deleteTableWriter = createTestTableWriter(table); + int deleteOutput = deleteTableWriter.delete(id, true); + LOG.info("deleted {} records from {}", deleteOutput, table.name); + assertThatSqlQueryYieldsZeroRows(faresDataSource, getColumnsForId(faresNamespace, id, table)); + } + + /** + * Create and store an area for testing. + */ + private static AreaDTO createArea(String areaId) throws InvalidNamespaceException, IOException, SQLException { + AreaDTO input = new AreaDTO(); + input.area_id = areaId; + input.area_name = "area-name"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.AREAS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, AreaDTO.class); + } + + /** + * Create and store a stop area for testing. + */ + private static StopAreaDTO createStopArea(String areaId) throws InvalidNamespaceException, IOException, SQLException { + StopAreaDTO input = new StopAreaDTO(); + input.area_id = areaId; + input.stop_id = "stop-id-1"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.STOP_AREAS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, StopAreaDTO.class); + } + + /** + * Create and store a time frame for testing. + */ + private static TimeFrameDTO createTimeFrame(String timeframeGroupId) throws InvalidNamespaceException, IOException, SQLException { + TimeFrameDTO input = new TimeFrameDTO(); + input.timeframe_group_id = timeframeGroupId; + input.start_time = 0; + input.end_time = 2600; + input.service_id = "service-id-1"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.TIME_FRAMES); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, TimeFrameDTO.class); + } + + /** + * Create and store a network for testing. + */ + private static NetworkDTO createNetwork(String networkId) throws InvalidNamespaceException, IOException, SQLException { + NetworkDTO input = new NetworkDTO(); + input.network_id = networkId; + input.network_name = "network-name-1"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.NETWORKS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, NetworkDTO.class); + } + + /** + * Create and store a route network for testing. + */ + private static RouteNetworkDTO createRouteNetwork(String networkId) throws InvalidNamespaceException, IOException, SQLException { + RouteNetworkDTO input = new RouteNetworkDTO(); + input.network_id = networkId; + input.route_id = "route-id-1"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.ROUTE_NETWORKS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, RouteNetworkDTO.class); + } + + /** + * Create and store a fare leg rule for testing. + */ + private static FareLegRuleDTO createFareLegRule(String legGroupId) throws InvalidNamespaceException, IOException, SQLException { + FareLegRuleDTO input = new FareLegRuleDTO(); + input.leg_group_id = legGroupId; + input.network_id = "network-id-1"; + input.from_area_id = "from-area-id-1"; + input.to_area_id = "to-area-id-1"; + input.from_timeframe_group_id = "from-timeframe-group-id-1"; + input.to_timeframe_group_id = "to-timeframe-group-id-1"; + input.fare_product_id = "fare-product-id-1"; + input.rule_priority = 1; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.FARE_LEG_RULES); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, FareLegRuleDTO.class); + } + + /** + * Create and store a fare media for testing. + */ + private static FareMediaDTO createFareMedia(String fareMediaId) throws InvalidNamespaceException, IOException, SQLException { + FareMediaDTO input = new FareMediaDTO(); + input.fare_media_id = fareMediaId; + input.fare_media_name = "fare-media-name"; + input.fare_media_type = 1; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.FARE_MEDIAS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, FareMediaDTO.class); + } + + /** + * Create and store a fare product for testing. + */ + private static FareProductDTO createFareProduct(String fareProductId) throws InvalidNamespaceException, IOException, SQLException { + FareProductDTO input = new FareProductDTO(); + input.fare_product_id = fareProductId; + input.fare_product_name = "fare-product-name"; + input.fare_media_id = "fare-media-id"; + input.amount = 6.25; + input.currency = "USD"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.FARE_PRODUCTS); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, FareProductDTO.class); + } + + /** + * Create and store a fare transfer rules for testing. + */ + private static FareTransferRuleDTO createFareTransferRules(String fromLegGroupId, String toLegGroupId) throws InvalidNamespaceException, IOException, SQLException { + FareTransferRuleDTO input = new FareTransferRuleDTO(); + input.from_leg_group_id = fromLegGroupId; + input.to_leg_group_id = toLegGroupId; + input.transfer_count = -1; + input.duration_limit = 1; + input.duration_limit_type = 1; + input.fare_transfer_type = 2; + input.fare_product_id = "fare-product-id-1"; + JdbcTableWriter createTableWriter = createTestTableWriter(Table.FARE_TRANSFER_RULES); + String output = createTableWriter.create(mapper.writeValueAsString(input), true); + return mapper.readValue(output, FareTransferRuleDTO.class); + } +} diff --git a/src/test/java/com/conveyal/gtfs/GTFSFeedTest.java b/src/test/java/com/conveyal/gtfs/GTFSFeedTest.java index 7afd7189b..d842b3bcc 100644 --- a/src/test/java/com/conveyal/gtfs/GTFSFeedTest.java +++ b/src/test/java/com/conveyal/gtfs/GTFSFeedTest.java @@ -1,24 +1,17 @@ package com.conveyal.gtfs; import com.conveyal.gtfs.model.StopTime; -import com.csvreader.CsvReader; -import org.apache.commons.io.input.BOMInputStream; import org.hamcrest.comparator.ComparatorMatcherBuilder; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import static com.conveyal.gtfs.TestUtils.checkFileTestCases; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.number.IsCloseTo.closeTo; @@ -26,30 +19,8 @@ * Test suite for the GTFSFeed class. */ public class GTFSFeedTest { - - private static final Logger LOG = LoggerFactory.getLogger(GTFSFeedTest.class); private static String simpleGtfsZipFileName; - private static class FileTestCase { - public String filename; - public DataExpectation[] expectedColumnData; - - public FileTestCase(String filename, DataExpectation[] expectedColumnData) { - this.filename = filename; - this.expectedColumnData = expectedColumnData; - } - } - - private static class DataExpectation { - public String columnName; - public String expectedValue; - - public DataExpectation(String columnName, String expectedValue) { - this.columnName = columnName; - this.expectedValue = expectedValue; - } - } - @BeforeAll public static void setUpClass() { //executed only once, before the first test @@ -65,7 +36,7 @@ public static void setUpClass() { * Make sure a round-trip of loading a GTFS zip file and then writing another zip file can be performed. */ @Test - public void canDoRoundTripLoadAndWriteToZipFile() throws IOException { + void canDoRoundTripLoadAndWriteToZipFile() throws IOException { // create a temp file for this test File outZip = File.createTempFile("fake-agency-output", ".zip"); @@ -80,119 +51,84 @@ public void canDoRoundTripLoadAndWriteToZipFile() throws IOException { // assert that rows of data were written to files within the zipfile ZipFile zip = new ZipFile(outZip); - FileTestCase[] fileTestCases = { + TestUtils.FileTestCase[] fileTestCases = { // agency.txt - new FileTestCase( + new TestUtils.FileTestCase( "agency.txt", - new DataExpectation[]{ - new DataExpectation("agency_id", "1"), - new DataExpectation("agency_name", "Fake Transit") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("agency_id", "1"), + new TestUtils.DataExpectation("agency_name", "Fake Transit") } ), - new FileTestCase( + new TestUtils.FileTestCase( "calendar.txt", - new DataExpectation[]{ - new DataExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57"), - new DataExpectation("start_date", "20170915"), - new DataExpectation("end_date", "20170917") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57"), + new TestUtils.DataExpectation("start_date", "20170915"), + new TestUtils.DataExpectation("end_date", "20170917") } ), - new FileTestCase( + new TestUtils.FileTestCase( "calendar_dates.txt", - new DataExpectation[]{ - new DataExpectation("service_id", "calendar-date-service"), - new DataExpectation("date", "20170917"), - new DataExpectation("exception_type", "1") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("service_id", "calendar-date-service"), + new TestUtils.DataExpectation("date", "20170917"), + new TestUtils.DataExpectation("exception_type", "1") } ), - new FileTestCase( + new TestUtils.FileTestCase( "routes.txt", - new DataExpectation[]{ - new DataExpectation("agency_id", "1"), - new DataExpectation("route_id", "1"), - new DataExpectation("route_long_name", "Route 1") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("agency_id", "1"), + new TestUtils.DataExpectation("route_id", "1"), + new TestUtils.DataExpectation("route_long_name", "Route 1") } ), - new FileTestCase( + new TestUtils.FileTestCase( "shapes.txt", - new DataExpectation[]{ - new DataExpectation("shape_id", "5820f377-f947-4728-ac29-ac0102cbc34e"), - new DataExpectation("shape_pt_lat", "37.0612132"), - new DataExpectation("shape_pt_lon", "-122.0074332") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("shape_id", "5820f377-f947-4728-ac29-ac0102cbc34e"), + new TestUtils.DataExpectation("shape_pt_lat", "37.0612132"), + new TestUtils.DataExpectation("shape_pt_lon", "-122.0074332") } ), - new FileTestCase( + new TestUtils.FileTestCase( "stop_times.txt", - new DataExpectation[]{ - new DataExpectation("trip_id", "a30277f8-e50a-4a85-9141-b1e0da9d429d"), - new DataExpectation("departure_time", "07:00:00"), - new DataExpectation("stop_id", "4u6g") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("trip_id", "a30277f8-e50a-4a85-9141-b1e0da9d429d"), + new TestUtils.DataExpectation("departure_time", "07:00:00"), + new TestUtils.DataExpectation("stop_id", "4u6g") } ), - new FileTestCase( + new TestUtils.FileTestCase( "trips.txt", - new DataExpectation[]{ - new DataExpectation("route_id", "1"), - new DataExpectation("trip_id", "a30277f8-e50a-4a85-9141-b1e0da9d429d"), - new DataExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("route_id", "1"), + new TestUtils.DataExpectation("trip_id", "a30277f8-e50a-4a85-9141-b1e0da9d429d"), + new TestUtils.DataExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57") } ), - new FileTestCase( + new TestUtils.FileTestCase( "datatools_patterns.txt", - new DataExpectation[]{ - new DataExpectation("pattern_id", "1"), - new DataExpectation("route_id", "1"), - new DataExpectation("name", "2 stops from Butler Ln to Scotts Valley Dr & Victor Sq (1 trips)"), - new DataExpectation("direction_id", "0"), - new DataExpectation("shape_id", "5820f377-f947-4728-ac29-ac0102cbc34e") + new TestUtils.DataExpectation[] { + new TestUtils.DataExpectation("pattern_id", "1"), + new TestUtils.DataExpectation("route_id", "1"), + new TestUtils.DataExpectation("name", "2 stops from Butler Ln to Scotts Valley Dr & Victor Sq (1 trips)"), + new TestUtils.DataExpectation("direction_id", "0"), + new TestUtils.DataExpectation("shape_id", "5820f377-f947-4728-ac29-ac0102cbc34e") } ) }; - - // look through all written files in the zipfile - for (FileTestCase fileTestCase: fileTestCases) { - ZipEntry entry = zip.getEntry(fileTestCase.filename); - - // make sure the file exists within the zipfile - assertThat(entry, notNullValue()); - - // create csv reader for file - InputStream zis = zip.getInputStream(entry); - InputStream bis = new BOMInputStream(zis); - CsvReader reader = new CsvReader(bis, ',', Charset.forName("UTF8")); - - // make sure the file has headers - boolean hasHeaders = reader.readHeaders(); - assertThat(hasHeaders, is(true)); - - // make sure that the a record matching the expected row exists in this table - boolean recordFound = false; - while (reader.readRecord() && !recordFound) { - boolean allExpectationsMetForThisRecord = true; - for (DataExpectation dataExpectation : fileTestCase.expectedColumnData) { - if(!reader.get(dataExpectation.columnName).equals(dataExpectation.expectedValue)) { - allExpectationsMetForThisRecord = false; - break; - } - } - if (allExpectationsMetForThisRecord) { - recordFound = true; - } - } - assertThat( - String.format("Data Expectation record not found in %s", fileTestCase.filename), - recordFound, - is(true) - ); - } + checkFileTestCases(zip, fileTestCases); } /** * Make sure that a GTFS feed with interpolated stop times have calculated times after feed processing + * * @throws GTFSFeed.FirstAndLastStopsDoNotHaveTimes */ @Test - public void canGetInterpolatedTimes() throws GTFSFeed.FirstAndLastStopsDoNotHaveTimes, IOException { + void canGetInterpolatedTimes() throws GTFSFeed.FirstAndLastStopsDoNotHaveTimes, IOException { String tripId = "a30277f8-e50a-4a85-9141-b1e0da9d429d"; String gtfsZipFileName = TestUtils.zipFolderFiles("fake-agency-interpolated-stop-times", true); @@ -241,7 +177,7 @@ public void canGetInterpolatedTimes() throws GTFSFeed.FirstAndLastStopsDoNotHave * Make sure a spatial index of stops can be calculated */ @Test - public void canGetSpatialIndex() { + void canGetSpatialIndex() { GTFSFeed feed = GTFSFeed.fromFile(simpleGtfsZipFileName); assertThat( feed.getSpatialIndex().size(), @@ -254,7 +190,7 @@ public void canGetSpatialIndex() { * Make sure trip speed can be calculated using trip's shape. */ @Test - public void canGetTripSpeedUsingShape() { + void canGetTripSpeedUsingShape() { GTFSFeed feed = GTFSFeed.fromFile(simpleGtfsZipFileName); assertThat( feed.getTripSpeed("a30277f8-e50a-4a85-9141-b1e0da9d429d"), @@ -266,7 +202,7 @@ public void canGetTripSpeedUsingShape() { * Make sure trip speed can be calculated using trip's shape. */ @Test - public void canGetTripSpeedUsingStraightLine() { + void canGetTripSpeedUsingStraightLine() { GTFSFeed feed = GTFSFeed.fromFile(simpleGtfsZipFileName); assertThat( feed.getTripSpeed("a30277f8-e50a-4a85-9141-b1e0da9d429d", true), diff --git a/src/test/java/com/conveyal/gtfs/GTFSTest.java b/src/test/java/com/conveyal/gtfs/GTFSTest.java index 5eacb02a9..3da9a8841 100644 --- a/src/test/java/com/conveyal/gtfs/GTFSTest.java +++ b/src/test/java/com/conveyal/gtfs/GTFSTest.java @@ -6,6 +6,7 @@ import com.conveyal.gtfs.loader.JdbcGtfsExporter; import com.conveyal.gtfs.loader.SnapshotResult; import com.conveyal.gtfs.loader.Table; +import com.conveyal.gtfs.loader.TableLoadResult; import com.conveyal.gtfs.storage.ErrorExpectation; import com.conveyal.gtfs.storage.ExpectedFieldType; import com.conveyal.gtfs.storage.PersistenceExpectation; @@ -41,15 +42,20 @@ import java.util.Arrays; import java.util.Collection; import java.util.Iterator; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import static com.conveyal.gtfs.TestUtils.getResourceFileName; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.core.IsNull.nullValue; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -300,6 +306,128 @@ public void canLoadAndExportSimpleAgencyInSubDirectory() throws IOException { ); } + + /** + * Tests whether "fake-agency-with-fares-v2" GTFS can be placed in a zipped subdirectory and loaded/exported + * successfully. + */ + @Test + void canLoadAndExportFaresV2Feed() throws IOException { + String resourceFolder = TestUtils.getResourceFileName("fake-agency-with-fares-v2"); + String zipFileName = TestUtils.zipFolderFiles(resourceFolder, false); + ErrorExpectation[] errorExpectations = ErrorExpectation.list( + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.TABLE_IN_SUBDIRECTORY), + new ErrorExpectation(NewGTFSErrorType.FEED_TRAVEL_TIMES_ROUNDED), + new ErrorExpectation(NewGTFSErrorType.DATE_NO_SERVICE) + ); + assertTrue( + runIntegrationTestOnZipFile(zipFileName, nullValue(), faresV2PersistenceExpectations, errorExpectations) + ); + } + + /** + * Persistence expectations for use with the GTFS contained within the "fake-agency-with-fares-v2" feed. + */ + private final PersistenceExpectation[] faresV2PersistenceExpectations = new PersistenceExpectation[]{ + new PersistenceExpectation( + "areas", + new RecordExpectation[]{ + new RecordExpectation("area_id", "area_bl"), + new RecordExpectation("area_name", "Blue Line") + } + ), + new PersistenceExpectation( + "fare_leg_rules", + new RecordExpectation[]{ + new RecordExpectation("leg_group_id", "leg_airport_rapid_transit_quick_subway"), + new RecordExpectation("network_id", "rapid_transit"), + new RecordExpectation("from_area_id", "area_bl_airport"), + new RecordExpectation("to_area_id", null), + new RecordExpectation("fare_product_id", "prod_rapid_transit_quick_subway"), + new RecordExpectation("from_timeframe_group_id", "timeframe_regular"), + new RecordExpectation("to_timeframe_group_id", null), + new RecordExpectation("transfer_only", null) + } + ), + new PersistenceExpectation( + "fare_media", + new RecordExpectation[]{ + new RecordExpectation("fare_media_id", "credit_debit"), + new RecordExpectation("fare_media_name", "Credit/debit card"), + new RecordExpectation("fare_media_type", "0"), + } + ), + new PersistenceExpectation( + "fare_products", + new RecordExpectation[]{ + new RecordExpectation("fare_product_id", "prod_cr_inter_4"), + new RecordExpectation("fare_product_name", "Commuter Rail Interzone 4 one-way fare"), + new RecordExpectation("fare_media_id", "credit_debit"), + new RecordExpectation("amount", "4.25"), + new RecordExpectation("currency", "USD"), + } + ), + new PersistenceExpectation( + "fare_transfer_rules", + new RecordExpectation[]{ + new RecordExpectation("from_leg_group_id", "leg_mattapan_rapid_transit_quick_subway"), + new RecordExpectation("to_leg_group_id", "leg_local_bus_quick_subway"), + new RecordExpectation("transfer_count", null), + new RecordExpectation("duration_limit", "7200"), + new RecordExpectation("duration_limit_type", "1"), + new RecordExpectation("fare_transfer_type", "0"), + new RecordExpectation("fare_product_id", "prod_rapid_transit_quick_subway") + } + ), + new PersistenceExpectation( + "networks", + new RecordExpectation[]{ + new RecordExpectation("network_id", "1"), + new RecordExpectation("network_name", "Forbidden because network id is defined in routes") + } + ), + new PersistenceExpectation( + "route_networks", + new RecordExpectation[]{ + new RecordExpectation("network_id", "1"), + new RecordExpectation("route_id", "1") + } + ), + new PersistenceExpectation( + "stop_areas", + new RecordExpectation[]{ + new RecordExpectation("stop_id", "4u6g"), + new RecordExpectation("area_id", "area_route_426_downtown") + } + ), + new PersistenceExpectation( + "timeframes", + new RecordExpectation[]{ + new RecordExpectation("timeframe_group_id", "timeframe_regular"), + new RecordExpectation("service_id", "04100312-8fe1-46a5-a9f2-556f39478f57") + } + ) + }; + + /** * Tests whether the simple gtfs can be loaded and exported if it has only calendar_dates.txt */ @@ -834,20 +962,29 @@ private boolean runIntegrationTestOnZipFile( } private void assertThatLoadIsErrorFree(FeedLoadResult loadResult) { - assertThat(loadResult.fatalException, is(nullValue())); - assertThat(loadResult.agency.fatalException, is(nullValue())); - assertThat(loadResult.calendar.fatalException, is(nullValue())); - assertThat(loadResult.calendarDates.fatalException, is(nullValue())); - assertThat(loadResult.fareAttributes.fatalException, is(nullValue())); - assertThat(loadResult.fareRules.fatalException, is(nullValue())); - assertThat(loadResult.feedInfo.fatalException, is(nullValue())); - assertThat(loadResult.frequencies.fatalException, is(nullValue())); - assertThat(loadResult.routes.fatalException, is(nullValue())); - assertThat(loadResult.shapes.fatalException, is(nullValue())); - assertThat(loadResult.stops.fatalException, is(nullValue())); - assertThat(loadResult.stopTimes.fatalException, is(nullValue())); - assertThat(loadResult.transfers.fatalException, is(nullValue())); - assertThat(loadResult.trips.fatalException, is(nullValue())); + assertNull(loadResult.fatalException); + assertNull(loadResult.agency.fatalException); + assertNull(loadResult.calendar.fatalException); + assertNull(loadResult.calendarDates.fatalException); + assertNull(loadResult.fareAttributes.fatalException); + assertNull(loadResult.fareRules.fatalException); + assertNull(loadResult.feedInfo.fatalException); + assertNull(loadResult.frequencies.fatalException); + assertNull(loadResult.routes.fatalException); + assertNull(loadResult.shapes.fatalException); + assertNull(loadResult.stops.fatalException); + assertNull(loadResult.stopTimes.fatalException); + assertNull(loadResult.transfers.fatalException); + assertNull(loadResult.trips.fatalException); + assertNull(loadResult.areas.fatalException); + assertNull(loadResult.fareLegRules.fatalException); + assertNull(loadResult.fareMedias.fatalException); + assertNull(loadResult.fareProducts.fatalException); + assertNull(loadResult.fareTransferRules.fatalException); + assertNull(loadResult.networks.fatalException); + assertNull(loadResult.routeNetworks.fatalException); + assertNull(loadResult.stopAreas.fatalException); + assertNull(loadResult.timeFrames.fatalException); } private void assertThatSnapshotIsErrorFree(SnapshotResult snapshotResult) { @@ -858,11 +995,6 @@ private void assertThatSnapshotIsErrorFree(SnapshotResult snapshotResult) { /** * Helper function to export a GTFS from the database to a temporary zip file. */ -// private File exportGtfs(String namespace, DataSource dataSource, boolean fromEditor) throws IOException { -// File tempFile = File.createTempFile("snapshot", ".zip"); -// GTFS.export(namespace, tempFile.getAbsolutePath(), dataSource, fromEditor, false); -// return tempFile; -// } private File exportGtfs(String namespace, DataSource dataSource, boolean fromEditor, boolean publishProprietaryFiles) throws IOException { File tempFile = File.createTempFile("snapshot", ".zip"); GTFS.export(namespace, tempFile.getAbsolutePath(), dataSource, fromEditor, publishProprietaryFiles); diff --git a/src/test/java/com/conveyal/gtfs/TestUtils.java b/src/test/java/com/conveyal/gtfs/TestUtils.java index ca06de25d..ff18e59e8 100644 --- a/src/test/java/com/conveyal/gtfs/TestUtils.java +++ b/src/test/java/com/conveyal/gtfs/TestUtils.java @@ -1,7 +1,11 @@ package com.conveyal.gtfs; import com.conveyal.gtfs.error.NewGTFSErrorType; +import com.conveyal.gtfs.loader.Table; +import com.csvreader.CsvReader; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.hamcrest.Matcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,16 +14,21 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import static com.conveyal.gtfs.util.Util.randomIdString; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertTrue; public class TestUtils { @@ -28,6 +37,10 @@ public class TestUtils { public static final String PG_TEST_USER = "postgres"; public static final String PG_TEST_PASSWORD = "postgres"; + private static final String JDBC_URL = "jdbc:postgresql://localhost"; + + static final String TEST_RESOURCE_PATH = "src/test/resources/"; + /** * Forcefully drops a database even if other users are connected to it. * @@ -42,7 +55,7 @@ public static void dropDB(String dbName) { )); // drop the db if(executeAndClose(String.format("DROP DATABASE %s", dbName))) { - LOG.error(String.format("Successfully dropped database: %s", dbName)); + LOG.info(String.format("Successfully dropped database: %s", dbName)); } else { LOG.error(String.format("Failed to drop database: %s", dbName)); } @@ -104,7 +117,7 @@ public static String generateNewDB() { } /** - * Helper to return the relative path to a test resource file + * Helper to return the relative path to a test resource file. * * @param fileName * @return @@ -113,6 +126,19 @@ public static String getResourceFileName(String fileName) { return String.format("./src/test/resources/%s", fileName); } + public static String getTestResourceAsString(String resourcePathName) throws IOException { + return getFileContents(TEST_RESOURCE_PATH + resourcePathName); + } + + /** + * Extract the file contents from the provided path and file name. + */ + public static String getFileContents(String pathAndFileName) throws IOException { + FileInputStream fileInputStream = new FileInputStream(pathAndFileName); + return IOUtils.toString(fileInputStream, StandardCharsets.UTF_8); + } + + /** * Zip files in a folder into a temporary zip file */ @@ -211,5 +237,107 @@ public static void checkFeedHasExpectedNumberOfErrors( assertThatSqlCountQueryYieldsExpectedCount(testDataSource, sql, expectedNumberOfErrors); } + public static class FileTestCase { + public String filename; + public TestUtils.DataExpectation[] expectedColumnData; + + public FileTestCase(String filename, TestUtils.DataExpectation[] expectedColumnData) { + this.filename = filename; + this.expectedColumnData = expectedColumnData; + } + } + public static class DataExpectation { + public String columnName; + public String expectedValue; + + public DataExpectation(String columnName, String expectedValue) { + this.columnName = columnName; + this.expectedValue = expectedValue; + } + } + + /** + * Look through all written files and confirm that the record matching the expected row exists in appropriate file. + */ + public static void checkFileTestCases(ZipFile zip, FileTestCase[] fileTestCases) throws IOException { + // Look through all written files in the zip file. + for (TestUtils.FileTestCase fileTestCase : fileTestCases) { + ZipEntry entry = zip.getEntry(fileTestCase.filename); + + // make sure the file exists within the zip file. + assertThat(entry, notNullValue()); + + // create csv reader for file + InputStream zis = zip.getInputStream(entry); + InputStream bis = new BOMInputStream(zis); + CsvReader reader = new CsvReader(bis, ',', StandardCharsets.UTF_8); + + // make sure the file has headers + boolean hasHeaders = reader.readHeaders(); + assertTrue(hasHeaders); + + // make sure that the record matching the expected row exists in this table. + boolean recordFound = false; + while (reader.readRecord() && !recordFound) { + boolean allExpectationsMetForThisRecord = true; + for (TestUtils.DataExpectation dataExpectation : fileTestCase.expectedColumnData) { + if (!reader.get(dataExpectation.columnName).equals(dataExpectation.expectedValue)) { + allExpectationsMetForThisRecord = false; + break; + } + } + if (allExpectationsMetForThisRecord) { + recordFound = true; + } + } + assertTrue( + recordFound, + String.format("Data Expectation record not found in %s", fileTestCase.filename) + ); + } + } + + + /** + * Asserts that a given value for the specified field in result set matches provided matcher. + */ + public static void assertResultValue(ResultSet resultSet, String field, Matcher matcher) throws SQLException { + assertThat(resultSet.getObject(field), matcher); + } + + /** + * Executes SQL query for the specified ID and columns and returns the resulting result set. + */ + public static ResultSet getResultSetForId(DataSource dataSource, String namespace, int id, Table table, String... columns) throws SQLException { + String sql = getColumnsForId(namespace, id, table, columns); + return dataSource.getConnection().prepareStatement(sql).executeQuery(); + } + + /** + * Constructs SQL query for the specified ID and columns and returns the resulting result set. + */ + public static String getColumnsForId(String namespace, int id, Table table, String... columns) { + String sql = String.format( + "select %s from %s.%s where id=%d", + columns.length > 0 ? String.join(", ", columns) : "*", + namespace, + table.name, + id + ); + LOG.info(sql); + return sql; + } + + public static void assertThatSqlQueryYieldsRowCount(DataSource dataSource, String sql, int expectedRowCount) throws SQLException { + LOG.info(sql); + int recordCount = 0; + ResultSet rs = dataSource.getConnection().prepareStatement(sql).executeQuery(); + while (rs.next()) recordCount++; + assertThat("Records matching query should equal expected count.", recordCount, equalTo(expectedRowCount)); + } + + public static void assertThatSqlQueryYieldsZeroRows(DataSource dataSource, String sql) throws SQLException { + assertThatSqlQueryYieldsRowCount(dataSource, sql, 0); + } } diff --git a/src/test/java/com/conveyal/gtfs/dto/AreaDTO.java b/src/test/java/com/conveyal/gtfs/dto/AreaDTO.java new file mode 100644 index 000000000..0ea3b0e52 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/AreaDTO.java @@ -0,0 +1,14 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.Area} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class AreaDTO { + public Integer id; + public String area_id; + public String area_name; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/FareLegRuleDTO.java b/src/test/java/com/conveyal/gtfs/dto/FareLegRuleDTO.java new file mode 100644 index 000000000..457d375ab --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/FareLegRuleDTO.java @@ -0,0 +1,20 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.FareLegRule} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class FareLegRuleDTO { + public Integer id; + public String leg_group_id; + public String network_id; + public String from_area_id; + public String to_area_id; + public String from_timeframe_group_id; + public String to_timeframe_group_id; + public String fare_product_id; + public int rule_priority; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/FareMediaDTO.java b/src/test/java/com/conveyal/gtfs/dto/FareMediaDTO.java new file mode 100644 index 000000000..23b47d51b --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/FareMediaDTO.java @@ -0,0 +1,15 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.FareMedia} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class FareMediaDTO { + public Integer id; + public String fare_media_id; + public String fare_media_name; + public int fare_media_type; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/FareProductDTO.java b/src/test/java/com/conveyal/gtfs/dto/FareProductDTO.java new file mode 100644 index 000000000..57cecd2ee --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/FareProductDTO.java @@ -0,0 +1,17 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.FareProduct} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class FareProductDTO { + public Integer id; + public String fare_product_id; + public String fare_product_name; + public String fare_media_id; + public double amount; + public String currency; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/FareTransferRuleDTO.java b/src/test/java/com/conveyal/gtfs/dto/FareTransferRuleDTO.java new file mode 100644 index 000000000..738132cb8 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/FareTransferRuleDTO.java @@ -0,0 +1,19 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.FareTransferRule} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class FareTransferRuleDTO { + public Integer id; + public String from_leg_group_id; + public String to_leg_group_id; + public int transfer_count; + public int duration_limit; + public int duration_limit_type; + public int fare_transfer_type; + public String fare_product_id; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/NetworkDTO.java b/src/test/java/com/conveyal/gtfs/dto/NetworkDTO.java new file mode 100644 index 000000000..3e34c7d34 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/NetworkDTO.java @@ -0,0 +1,14 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.Network} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class NetworkDTO { + public Integer id; + public String network_id; + public String network_name; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/RouteDTO.java b/src/test/java/com/conveyal/gtfs/dto/RouteDTO.java index ec15aca45..6b8cf8444 100644 --- a/src/test/java/com/conveyal/gtfs/dto/RouteDTO.java +++ b/src/test/java/com/conveyal/gtfs/dto/RouteDTO.java @@ -26,4 +26,6 @@ public class RouteDTO { public Integer status; public int continuous_pickup; public int continuous_drop_off; + public String network_id; + } diff --git a/src/test/java/com/conveyal/gtfs/dto/RouteNetworkDTO.java b/src/test/java/com/conveyal/gtfs/dto/RouteNetworkDTO.java new file mode 100644 index 000000000..1ad077c5e --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/RouteNetworkDTO.java @@ -0,0 +1,14 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.RouteNetwork} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RouteNetworkDTO { + public Integer id; + public String network_id; + public String route_id; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/StopAreaDTO.java b/src/test/java/com/conveyal/gtfs/dto/StopAreaDTO.java new file mode 100644 index 000000000..66003fca6 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/StopAreaDTO.java @@ -0,0 +1,14 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.StopArea} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class StopAreaDTO { + public Integer id; + public String area_id; + public String stop_id; +} diff --git a/src/test/java/com/conveyal/gtfs/dto/TimeFrameDTO.java b/src/test/java/com/conveyal/gtfs/dto/TimeFrameDTO.java new file mode 100644 index 000000000..c1df7d7da --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/dto/TimeFrameDTO.java @@ -0,0 +1,16 @@ +package com.conveyal.gtfs.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * DTO used to model expected {@link com.conveyal.gtfs.model.TimeFrame} JSON structure for the editor. NOTE: reference types + * (e.g., Integer and Double) are used here in order to model null/empty values in JSON object. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TimeFrameDTO { + public Integer id; + public String timeframe_group_id; + public Integer start_time; + public Integer end_time; + public String service_id; +} diff --git a/src/test/java/com/conveyal/gtfs/graphql/GTFSGraphQLTest.java b/src/test/java/com/conveyal/gtfs/graphql/GTFSGraphQLTest.java index 240683306..d9c0e85c1 100644 --- a/src/test/java/com/conveyal/gtfs/graphql/GTFSGraphQLTest.java +++ b/src/test/java/com/conveyal/gtfs/graphql/GTFSGraphQLTest.java @@ -40,6 +40,11 @@ public class GTFSGraphQLTest { private static DataSource testInjectionDataSource; private static String testInjectionNamespace; private static String badCalendarDateNamespace; + + public static String faresDBName; + private static DataSource faresDataSource; + private static String faresNamespace; + private static final int TEST_TIMEOUT = 5000; @BeforeAll @@ -75,17 +80,30 @@ public static void setUpClass() throws IOException { testInjectionNamespace = injectionFeedLoadResult.uniqueIdentifier; // validate feed to create additional tables validate(testInjectionNamespace, testInjectionDataSource); + + String folderName = "fake-agency-with-fares-v2"; + String faresZipFileName = TestUtils.zipFolderFiles(folderName, true); + // create a new database + faresDBName = TestUtils.generateNewDB(); + String faresConnectionUrl = String.format("jdbc:postgresql://localhost/%s", faresDBName); + faresDataSource = TestUtils.createTestDataSource(faresConnectionUrl); + // load feed into db + FeedLoadResult faresFeedLoadResult = load(faresZipFileName, faresDataSource); + faresNamespace = faresFeedLoadResult.uniqueIdentifier; + // validate feed to create additional tables + validate(faresNamespace, faresDataSource); } @AfterAll public static void tearDownClass() { TestUtils.dropDB(testDBName); TestUtils.dropDB(testInjectionDBName); + TestUtils.dropDB(faresDBName); } /** Tests that the graphQL schema can initialize. */ @Test - public void canInitialize() { + void canInitialize() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { GTFSGraphQL.initialize(testDataSource); GTFSGraphQL.getGraphQl(); @@ -94,7 +112,7 @@ public void canInitialize() { /** Tests that the root element of a feed can be fetched. */ @Test - public void canFetchFeed() { + void canFetchFeed() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feed.txt"), matchesSnapshot()); }); @@ -102,7 +120,7 @@ public void canFetchFeed() { /** Tests that the row counts of a feed can be fetched. */ @Test - public void canFetchFeedRowCounts() { + void canFetchFeedRowCounts() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedRowCounts.txt"), matchesSnapshot()); }); @@ -110,7 +128,7 @@ public void canFetchFeedRowCounts() { /** Tests that the errors of a feed can be fetched. */ @Test - public void canFetchErrors() { + void canFetchErrors() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedErrors.txt"), matchesSnapshot()); }); @@ -118,7 +136,7 @@ public void canFetchErrors() { /** Tests that the feed_info of a feed can be fetched. */ @Test - public void canFetchFeedInfo() { + void canFetchFeedInfo() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedFeedInfo.txt"), matchesSnapshot()); }); @@ -126,15 +144,15 @@ public void canFetchFeedInfo() { /** Tests that the patterns of a feed can be fetched. */ @Test - public void canFetchPatterns() { + void canFetchPatterns() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedPatterns.txt"), matchesSnapshot()); }); } - /** Tests that the patterns of a feed can be fetched. */ + /** Tests that the poly lines of a feed can be fetched. */ @Test - public void canFetchPolylines() { + void canFetchPolylines() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedPolylines.txt"), matchesSnapshot()); }); @@ -142,7 +160,7 @@ public void canFetchPolylines() { /** Tests that the agencies of a feed can be fetched. */ @Test - public void canFetchAgencies() { + void canFetchAgencies() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedAgencies.txt"), matchesSnapshot()); }); @@ -150,7 +168,7 @@ public void canFetchAgencies() { /** Tests that the attributions of a feed can be fetched. */ @Test - public void canFetchATtributions() { + void canFetchAttributions() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedAttributions.txt"), matchesSnapshot()); }); @@ -158,7 +176,7 @@ public void canFetchATtributions() { /** Tests that the calendars of a feed can be fetched. */ @Test - public void canFetchCalendars() { + void canFetchCalendars() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedCalendars.txt"), matchesSnapshot()); }); @@ -166,7 +184,7 @@ public void canFetchCalendars() { /** Tests that the fares of a feed can be fetched. */ @Test - public void canFetchFares() { + void canFetchFares() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedFares.txt"), matchesSnapshot()); }); @@ -174,7 +192,7 @@ public void canFetchFares() { /** Tests that the routes of a feed can be fetched. */ @Test - public void canFetchRoutes() { + void canFetchRoutes() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedRoutes.txt"), matchesSnapshot()); }); @@ -182,15 +200,15 @@ public void canFetchRoutes() { /** Tests that the stops of a feed can be fetched. */ @Test - public void canFetchStops() { + void canFetchStops() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedStops.txt"), matchesSnapshot()); }); } - /** Tests that the stops of a feed can be fetched. */ + /** Tests that stops with children can be fetched. */ @Test - public void canFetchStopWithChildren() { + void canFetchStopWithChildren() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedStopWithChildren.txt"), matchesSnapshot()); }); @@ -198,7 +216,7 @@ public void canFetchStopWithChildren() { /** Tests that the trips of a feed can be fetched. */ @Test - public void canFetchTrips() { + void canFetchTrips() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedTrips.txt"), matchesSnapshot()); }); @@ -206,7 +224,7 @@ public void canFetchTrips() { /** Tests that the translations of a feed can be fetched. */ @Test - public void canFetchTranslations() { + void canFetchTranslations() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedTranslations.txt"), matchesSnapshot()); }); @@ -216,23 +234,85 @@ public void canFetchTranslations() { /** Tests that the stop times of a feed can be fetched. */ @Test - public void canFetchStopTimes() { + void canFetchStopTimes() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedStopTimes.txt"), matchesSnapshot()); }); } - /** Tests that the stop times of a feed can be fetched. */ + /** Tests that the services of a feed can be fetched. */ @Test - public void canFetchServices() { + void canFetchServices() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedServices.txt"), matchesSnapshot()); }); } - /** Tests that the stop times of a feed can be fetched. */ @Test - public void canFetchRoutesAndFilterTripsByDateAndTime() { + void canFetchAreas() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedAreas.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchStopAreas() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedStopAreas.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchFareTransferRules() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedFareTransferRules.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchFareProducts() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedFareProducts.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchFareMedias() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedFareMedias.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchFareLegRules() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedFareLegRules.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchTimeFrames() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedTimeFrames.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchNetworks() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedNetworks.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchRouteNetworks() { + assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { + MatcherAssert.assertThat(queryFaresV2GraphQL("feedRouteNetworks.txt"), matchesSnapshot()); + }); + } + + @Test + void canFetchRoutesAndFilterTripsByDateAndTime() { Map variables = new HashMap<>(); variables.put("namespace", testNamespace); variables.put("date", "20170915"); @@ -246,30 +326,30 @@ public void canFetchRoutesAndFilterTripsByDateAndTime() { }); } - /** Tests that the limit argument applies properly to a fetcher defined with autolimit set to false. */ + /** Tests that the limit argument applies properly to a fetcher defined with auto limit set to false. */ @Test - public void canFetchNestedEntityWithLimit() { + void canFetchNestedEntityWithLimit() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("feedStopsStopTimeLimit.txt"), matchesSnapshot()); }); } - /** Tests whether a graphQL query that has superflous and redundant nesting can find the right result. */ + /** Tests whether a graphQL query that has superfluous and redundant nesting can find the right result. */ // if the graphQL dataloader is enabled correctly, there will not be any repeating sql queries in the logs @Test - public void canFetchMultiNestedEntities() { + void canFetchMultiNestedEntities() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("superNested.txt"), matchesSnapshot()); }); } /** - * Tests whether a graphQL query that has superflous and redundant nesting can find the right result. + * Tests whether a graphQL query that has superfluous and redundant nesting can find the right result. * If the graphQL dataloader is enabled correctly, there will not be any repeating sql queries in the logs. - * Furthermore, some queries should have been combined together. + * Furthermore, some queries should have been combined. */ @Test - public void canFetchMultiNestedEntitiesWithoutLimits() { + void canFetchMultiNestedEntitiesWithoutLimits() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { MatcherAssert.assertThat(queryGraphQL("superNestedNoLimits.txt"), matchesSnapshot()); }); @@ -279,7 +359,7 @@ public void canFetchMultiNestedEntitiesWithoutLimits() { * parent_station column in the imported stops table. */ @Test - public void canFetchStopsWithoutParentStationColumn() { + void canFetchStopsWithoutParentStationColumn() { Map variables = new HashMap<>(); variables.put("namespace", badCalendarDateNamespace); assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { @@ -299,7 +379,7 @@ public void canFetchStopsWithoutParentStationColumn() { * The graphql library should properly escape the string and return 0 results for stops. */ @Test - public void canSanitizeSQLInjectionSentAsInput() { + void canSanitizeSQLInjectionSentAsInput() { Map variables = new HashMap<>(); variables.put("namespace", testInjectionNamespace); variables.put("stop_id", Arrays.asList("' OR 1=1;")); @@ -320,7 +400,7 @@ public void canSanitizeSQLInjectionSentAsInput() { * The graphql library should properly escape the string and complete the queries. */ @Test - public void canSanitizeSQLInjectionSentAsKeyValue() { + void canSanitizeSQLInjectionSentAsKeyValue() { assertTimeout(Duration.ofMillis(TEST_TIMEOUT), () -> { // manually update the route_id key in routes and patterns String injection = "'' OR 1=1; Select ''99"; @@ -352,6 +432,18 @@ private Map queryGraphQL(String queryFilename) throws IOExceptio return queryGraphQL(queryFilename, variables, testDataSource); } + /** + * Helper method to make a fares V2 query with default variables. + * + * @param queryFilename the filename that should be used to generate the GraphQL query. This file must be present + * in the `src/test/resources/graphql` folder + */ + private Map queryFaresV2GraphQL(String queryFilename) throws IOException { + Map variables = new HashMap<>(); + variables.put("namespace", faresNamespace); + return queryGraphQL(queryFilename, variables, faresDataSource); + } + /** * Helper method to execute a GraphQL query and return the result. * diff --git a/src/test/java/com/conveyal/gtfs/loader/JDBCTableWriterFaresV2Test.java b/src/test/java/com/conveyal/gtfs/loader/JDBCTableWriterFaresV2Test.java new file mode 100644 index 000000000..80d328be9 --- /dev/null +++ b/src/test/java/com/conveyal/gtfs/loader/JDBCTableWriterFaresV2Test.java @@ -0,0 +1,165 @@ +package com.conveyal.gtfs.loader; + +import com.conveyal.gtfs.TestUtils; +import com.conveyal.gtfs.util.InvalidNamespaceException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.sql.DataSource; +import java.io.IOException; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.stream.Stream; + +import static com.conveyal.gtfs.GTFS.makeSnapshot; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * This class contains CRUD tests for {@link JdbcTableWriter} (i.e., editing GTFS entities in the RDBMS). Set up + * consists of creating a scratch database and an empty feed snapshot, which is the necessary starting condition + * for building a GTFS feed from scratch. It then runs the various CRUD tests and finishes by dropping the database + * (even if tests fail). + */ +public class JDBCTableWriterFaresV2Test { + + private static final Logger LOG = LoggerFactory.getLogger(JDBCTableWriterFaresV2Test.class); + + private static String testDBName; + private static DataSource testDataSource; + private static String testNamespace; + + private static JdbcTableWriter createTestTableWriter(Table table) throws InvalidNamespaceException { + return new JdbcTableWriter(table, testDataSource, testNamespace); + } + + @BeforeAll + public static void setUpClass() throws SQLException { + testDBName = TestUtils.generateNewDB(); + String dbConnectionUrl = String.format("jdbc:postgresql://localhost/%s", testDBName); + testDataSource = TestUtils.createTestDataSource(dbConnectionUrl); + LOG.info("creating feeds table because it isn't automatically generated unless you import a feed"); + Connection connection = testDataSource.getConnection(); + connection.createStatement().execute(JdbcGtfsLoader.getCreateFeedRegistrySQL()); + connection.commit(); + LOG.info("feeds table created"); + // Create an empty snapshot to create a new namespace and all the tables. + FeedLoadResult result = makeSnapshot(null, testDataSource, false); + testNamespace = result.uniqueIdentifier; + } + + @AfterAll + public static void tearDownClass() { + TestUtils.dropDB(testDBName); + } + + @ParameterizedTest + @MethodSource("createEntityInput") + void canCreateUpdateAndDeleteEntity( + Table table, + String entity, + String entityUpdated + ) throws IOException, SQLException, InvalidNamespaceException { + assertEquals(entity, createTestTableWriter(table).create(entity, true)); + + JdbcTableWriter updateTableWriter = createTestTableWriter(table); + assertEquals(entityUpdated, updateTableWriter.update(1, entityUpdated, true)); + + deleteEntity(table); + } + + /** + * Define JSON payload. ID must be set as 1. + */ + private static Stream createEntityInput() throws IOException { + return Stream.of( + Arguments.of( + Table.FARE_PRODUCTS, + getEntityFromFile("fare_product.json"), + getEntityFromFile("fare_product_updated.json") + ), + Arguments.of( + Table.FARE_MEDIAS, + getEntityFromFile("fare_media.json"), + getEntityFromFile("fare_media_updated.json") + ), + Arguments.of( + Table.FARE_LEG_RULES, + getEntityFromFile("fare_leg_rules.json"), + getEntityFromFile("fare_leg_rules_updated.json") + ), + Arguments.of( + Table.FARE_TRANSFER_RULES, + getEntityFromFile("fare_transfer_rules.json"), + getEntityFromFile("fare_transfer_rules_updated.json") + ), + Arguments.of( + Table.ROUTE_NETWORKS, + getEntityFromFile("route_networks.json"), + getEntityFromFile("route_networks_updated.json") + ), + Arguments.of( + Table.NETWORKS, + getEntityFromFile("networks.json"), + getEntityFromFile("networks_updated.json") + ), + Arguments.of( + Table.AREAS, + getEntityFromFile("areas.json"), + getEntityFromFile("areas_updated.json") + ), + Arguments.of( + Table.STOP_AREAS, + getEntityFromFile("stop_areas.json"), + getEntityFromFile("stop_areas_updated.json") + ), + Arguments.of( + Table.TIME_FRAMES, + getEntityFromFile("time_frames.json"), + getEntityFromFile("time_frames_updated.json") + ) + ); + } + + /** + * Get an entity from file which is expected to be in JSON and remove any formatting. + */ + private static String getEntityFromFile(String fileName) throws IOException { + return TestUtils + .getTestResourceAsString("fares-v2-json-entities/" + fileName) + .replace(System.lineSeparator(), "") + .replace(" ", "") + .replace("\"\"", "null"); + } + + private static void deleteEntity(Table table) throws InvalidNamespaceException, SQLException { + JdbcTableWriter deleteTableWriter = createTestTableWriter(table); + deleteTableWriter.delete(1, true); + assertThatSqlQueryYieldsRowCount(getColumnsForId(1, table)); + } + + /** + * Constructs SQL query for the specified ID and columns and returns the resulting result set. + */ + private static String getColumnsForId(int id, Table table, String... columns) { + return String.format( + "select %s from %s.%s where id=%d", + columns.length > 0 ? String.join(", ", columns) : "*", + testNamespace, + table.name, + id + ); + } + + private static void assertThatSqlQueryYieldsRowCount(String sql) throws SQLException { + int recordCount = 0; + ResultSet rs = testDataSource.getConnection().prepareStatement(sql).executeQuery(); + while (rs.next()) recordCount++; + assertEquals(0, recordCount, "Records matching query should equal expected count."); + } +} diff --git a/src/test/resources/fake-agency-with-fares-v2/agency.txt b/src/test/resources/fake-agency-with-fares-v2/agency.txt new file mode 100644 index 000000000..5cb6afa42 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/agency.txt @@ -0,0 +1,2 @@ +agency_id,agency_name,agency_url,agency_lang,agency_phone,agency_email,agency_timezone,agency_fare_url,agency_branding_url +1,Fake Transit,http://www.fake-agency.com,,,,America/Los_Angeles,, diff --git a/src/test/resources/fake-agency-with-fares-v2/areas.txt b/src/test/resources/fake-agency-with-fares-v2/areas.txt new file mode 100644 index 000000000..73b6734b6 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/areas.txt @@ -0,0 +1,40 @@ +area_id,area_name +area_bl,Blue Line +area_bl_airport,Blue Line - Airport Station +area_cf_zone_buzzards,CapeFLYER - Wareham/Buzzards Bay/Bourne +area_cf_zone_hyannis,CapeFLYER - Hyannis +area_commuter_rail_porter_zone_1a,Commuter Rail Zone 1A +area_commuter_rail_sumner_tunnel_zone_1a,Commuter Rail Zone 1A +area_commuter_rail_zone_1,Commuter Rail Zone 1 +area_commuter_rail_zone_10,Commuter Rail Zone 10 +area_commuter_rail_zone_1a,Commuter Rail Zone 1A +area_commuter_rail_zone_2,Commuter Rail Zone 2 +area_commuter_rail_zone_3,Commuter Rail Zone 3 +area_commuter_rail_zone_4,Commuter Rail Zone 4 +area_commuter_rail_zone_5,Commuter Rail Zone 5 +area_commuter_rail_zone_6,Commuter Rail Zone 6 +area_commuter_rail_zone_7,Commuter Rail Zone 7 +area_commuter_rail_zone_8,Commuter Rail Zone 8 +area_commuter_rail_zone_9,Commuter Rail Zone 9 +area_fairmount_line_zone_1a,Commuter Rail Zone 1A - Fairmount Line +area_gl_govt_ctr,Green Line - Government Center +area_green_b_west_of_kenmore,Green Line B - West of Kenmore +area_green_c_west_of_kenmore,Green Line C - West of Kenmore +area_green_e_west_of_symphony,Green Line E - West of Symphony +area_m_ashmont_mattapan,Mattapan Trolley - Ashmont and Mattapan +area_ol_state,Orange Line - State +area_red_south_station,Red Line - South Station +area_route_354_downtown,Route 354 - Downtown +area_route_354_outside_downtown,Route 354 - Outside Downtown +area_route_426_downtown,Route 426 - Downtown +area_route_426_outside_downtown,Route 426 - Outside Downtown +area_route_450_downtown,Route 450 - Downtown +area_route_450_outside_downtown,Route 450 - Outside Downtown +area_sl3_north_of_airport,Silver Line - North of Airport Station +area_sl_airport,Silver Line - Airport Station +area_sl_courthouse,Silver Line - Courthouse +area_sl_logan_terminal,Silver Line - Airport Terminals +area_sl_silver_line_way,Silver Line - Silver Line Way +area_sl_south_station,Silver Line - South Station +area_sl_world_trade_center,Silver Line - World Trade Center +area_ss_commuter_rail_zone_1a,Commuter Rail Zone 1A - South Station diff --git a/src/test/resources/fake-agency-with-fares-v2/calendar.txt b/src/test/resources/fake-agency-with-fares-v2/calendar.txt new file mode 100644 index 000000000..1bf13678e --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/calendar.txt @@ -0,0 +1,2 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +04100312-8fe1-46a5-a9f2-556f39478f57,1,1,1,1,1,1,1,20170915,20170917 \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/calendar_dates.txt b/src/test/resources/fake-agency-with-fares-v2/calendar_dates.txt new file mode 100644 index 000000000..5d0a31806 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/calendar_dates.txt @@ -0,0 +1,3 @@ +service_id,date,exception_type +04100312-8fe1-46a5-a9f2-556f39478f57,20170916,2 +calendar-date-service,20170917,1 \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/fare_leg_rules.txt b/src/test/resources/fake-agency-with-fares-v2/fare_leg_rules.txt new file mode 100644 index 000000000..37b60d472 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/fare_leg_rules.txt @@ -0,0 +1,697 @@ +leg_group_id,network_id,from_area_id,to_area_id,fare_product_id,from_timeframe_group_id,to_timeframe_group_id,transfer_only +leg_airport_rapid_transit_quick_subway,rapid_transit,area_bl_airport,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_cape_buzzards_hyannis_cash,cape_flyer,area_cf_zone_buzzards,area_cf_zone_hyannis,prod_cape_buzzards_hyannis_fare,,, +leg_cape_buzzards_hyannis_cash,cape_flyer,area_cf_zone_hyannis,area_cf_zone_buzzards,prod_cape_buzzards_hyannis_fare,,, +leg_cape_buzzards_hyannis_cash,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_8,prod_cape_buzzards_hyannis_fare,,, +leg_cape_buzzards_hyannis_cash,cape_flyer,area_commuter_rail_zone_8,area_cf_zone_hyannis,prod_cape_buzzards_hyannis_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_1a,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_2,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_4,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_commuter_rail_zone_1a,area_cf_zone_buzzards,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_commuter_rail_zone_2,area_cf_zone_buzzards,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_buzzards_cash,cape_flyer,area_commuter_rail_zone_4,area_cf_zone_buzzards,prod_cape_sbb_buzzards_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_1a,prod_cape_sbb_hyannis_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_2,prod_cape_sbb_hyannis_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_4,prod_cape_sbb_hyannis_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_commuter_rail_zone_1a,area_cf_zone_hyannis,prod_cape_sbb_hyannis_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_commuter_rail_zone_2,area_cf_zone_hyannis,prod_cape_sbb_hyannis_fare,,, +leg_cape_sbb_hyannis_cash,cape_flyer,area_commuter_rail_zone_4,area_cf_zone_hyannis,prod_cape_sbb_hyannis_fare,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_sumner_tunnel_zone_1a,area_commuter_rail_sumner_tunnel_zone_1a,prod_cr_zone_1a,timeframe_sumner_tunnel_closure,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_sumner_tunnel_zone_1a,area_commuter_rail_zone_1a,prod_cr_zone_1a,timeframe_sumner_tunnel_closure,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_1,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_10,prod_cr_inter_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_1a,prod_cr_zone_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_2,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_3,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_4,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_5,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_6,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_7,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_8,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_9,prod_cr_inter_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_fairmount_line_zone_1a,prod_cr_zone_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1,area_ss_commuter_rail_zone_1a,prod_cr_zone_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_1,prod_cr_inter_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_10,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_1a,prod_cr_zone_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_2,prod_cr_inter_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_3,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_4,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_5,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_6,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_7,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_8,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_9,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_fairmount_line_zone_1a,prod_cr_zone_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_10,area_ss_commuter_rail_zone_1a,prod_cr_zone_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_sumner_tunnel_zone_1a,prod_cr_zone_1a,timeframe_sumner_tunnel_closure,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_1,prod_cr_zone_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_10,prod_cr_zone_10,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_1a,prod_cr_zone_1a,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_2,prod_cr_zone_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_3,prod_cr_zone_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_4,prod_cr_zone_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_5,prod_cr_zone_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_6,prod_cr_zone_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_7,prod_cr_zone_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_8,prod_cr_zone_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_9,prod_cr_zone_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_fairmount_line_zone_1a,prod_cr_zone_1a,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_1a,area_ss_commuter_rail_zone_1a,prod_cr_zone_1a,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_1,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_10,prod_cr_inter_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_1a,prod_cr_zone_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_2,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_3,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_4,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_5,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_6,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_7,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_8,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_9,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_fairmount_line_zone_1a,prod_cr_zone_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_2,area_ss_commuter_rail_zone_1a,prod_cr_zone_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_1,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_10,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_1a,prod_cr_zone_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_2,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_3,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_4,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_5,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_6,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_7,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_8,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_9,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_fairmount_line_zone_1a,prod_cr_zone_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_3,area_ss_commuter_rail_zone_1a,prod_cr_zone_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_1,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_10,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_1a,prod_cr_zone_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_2,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_3,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_4,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_5,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_6,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_7,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_8,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_9,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_fairmount_line_zone_1a,prod_cr_zone_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_4,area_ss_commuter_rail_zone_1a,prod_cr_zone_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_1,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_10,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_1a,prod_cr_zone_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_2,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_3,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_4,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_5,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_6,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_7,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_8,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_9,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_fairmount_line_zone_1a,prod_cr_zone_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_5,area_ss_commuter_rail_zone_1a,prod_cr_zone_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_1,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_10,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_1a,prod_cr_zone_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_2,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_3,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_4,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_5,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_6,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_7,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_8,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_9,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_fairmount_line_zone_1a,prod_cr_zone_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_6,area_ss_commuter_rail_zone_1a,prod_cr_zone_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_1,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_10,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_1a,prod_cr_zone_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_2,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_3,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_4,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_5,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_6,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_7,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_8,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_9,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_fairmount_line_zone_1a,prod_cr_zone_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_7,area_ss_commuter_rail_zone_1a,prod_cr_zone_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_1,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_10,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_1a,prod_cr_zone_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_2,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_3,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_4,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_5,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_6,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_7,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_8,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_9,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_fairmount_line_zone_1a,prod_cr_zone_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_8,area_ss_commuter_rail_zone_1a,prod_cr_zone_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_1,prod_cr_inter_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_10,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_1a,prod_cr_zone_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_2,prod_cr_inter_8,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_3,prod_cr_inter_7,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_4,prod_cr_inter_6,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_5,prod_cr_inter_5,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_6,prod_cr_inter_4,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_7,prod_cr_inter_3,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_8,prod_cr_inter_2,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_9,prod_cr_inter_1,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_fairmount_line_zone_1a,prod_cr_zone_9,,, +leg_commuter_rail_cash,commuter_rail,area_commuter_rail_zone_9,area_ss_commuter_rail_zone_1a,prod_cr_zone_9,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_1,prod_cr_zone_1,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_10,prod_cr_zone_10,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_1a,prod_cr_zone_1a,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_2,prod_cr_zone_2,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_3,prod_cr_zone_3,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_4,prod_cr_zone_4,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_5,prod_cr_zone_5,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_6,prod_cr_zone_6,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_7,prod_cr_zone_7,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_8,prod_cr_zone_8,,, +leg_commuter_rail_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_9,prod_cr_zone_9,,, +leg_commuter_rail_free,commuter_rail,area_commuter_rail_porter_zone_1a,area_commuter_rail_zone_1a,prod_free_fare,timeframe_alewife_kendall_surge,, +leg_commuter_rail_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_porter_zone_1a,prod_free_fare,timeframe_alewife_kendall_surge,, +leg_express_bus_cash,express_bus,area_route_354_downtown,area_route_354_outside_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus,area_route_354_outside_downtown,area_route_354_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus,area_route_426_downtown,area_route_426_outside_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus,area_route_426_outside_downtown,area_route_426_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus,area_route_450_downtown,area_route_450_outside_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus,area_route_450_outside_downtown,area_route_450_downtown,prod_express_bus,,, +leg_express_bus_cash,express_bus_special,,,prod_express_bus,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_1,prod_cr_zone_1,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_10,prod_cr_zone_10,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_1a,prod_cr_zone_1a,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_2,prod_cr_zone_2,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_3,prod_cr_zone_3,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_4,prod_cr_zone_4,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_5,prod_cr_zone_5,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_6,prod_cr_zone_6,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_7,prod_cr_zone_7,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_8,prod_cr_zone_8,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_9,prod_cr_zone_9,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_fairmount_line_zone_1a,prod_cr_zone_1a,,, +leg_fairmount_line_cash,commuter_rail,area_fairmount_line_zone_1a,area_ss_commuter_rail_zone_1a,prod_cr_zone_1a,,, +leg_ferry_east_boston_free,ferry_east_boston,,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_ferry_east_boston_free,ferry_east_boston,,,prod_boat_zone_1a,timeframe_regular,, +leg_ferry_f1_cash,ferry_f1,,,prod_ferry_f1,,, +leg_ferry_f4_cash,ferry_f4,,,prod_ferry_f4,,, +leg_ferry_f6_cash,ferry_f6,,,prod_boat_zone_1a,timeframe_sumner_tunnel_closure,, +leg_ferry_f6_cash,ferry_f6,,,prod_boat_zone_1,timeframe_regular,, +leg_ferry_lynn_cash,ferry_lynn,,,prod_boat_zone_1a,timeframe_sumner_tunnel_closure,, +leg_ferry_lynn_cash,ferry_lynn,,,prod_boat_zone_2,timeframe_regular,, +leg_foxboro_event_cash,cr_foxboro,,,prod_foxboro_event_fare,,, +leg_local_bus_cash,express_bus,area_route_354_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,express_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,express_bus,area_route_426_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,express_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,express_bus,area_route_450_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,express_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_354_outside_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_426_outside_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,area_route_450_outside_downtown,,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_354_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_354_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_426_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_426_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_450_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,area_route_450_outside_downtown,prod_local_bus,,, +leg_local_bus_cash,local_bus,,,prod_local_bus,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_354_outside_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_426_outside_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,area_route_450_outside_downtown,,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_354_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_354_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_426_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_426_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_450_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,area_route_450_outside_downtown,prod_free_fare,,, +leg_local_bus_free,local_bus_free,,,prod_free_fare,,, +leg_local_bus_quick_subway,express_bus,area_route_354_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,express_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,express_bus,area_route_426_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,express_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,express_bus,area_route_450_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,express_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_354_outside_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_426_outside_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,area_route_450_outside_downtown,,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_354_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_426_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_450_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,, +leg_local_bus_quick_subway,local_bus,,,prod_rapid_transit_quick_subway,,, +leg_local_bus_restricted_cash,local_bus_restricted,,,prod_local_bus,,, +leg_mattapan_rapid_transit_cash,m_rapid_transit,,,prod_rapid_transit_cash,,, +leg_mattapan_rapid_transit_quick_subway,m_rapid_transit,area_m_ashmont_mattapan,,prod_rapid_transit_quick_subway,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_354_outside_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_426_outside_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,area_route_450_outside_downtown,,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_354_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_354_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_426_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_426_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_450_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,area_route_450_outside_downtown,prod_free_fare,,, +leg_rail_replacement_free,rail_replacement_bus,,,prod_free_fare,,, +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_354_outside_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_426_outside_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,area_route_450_outside_downtown,,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_354_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_354_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_426_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_426_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_450_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,area_route_450_outside_downtown,prod_rapid_transit_quick_subway,,,1 +leg_rail_replacement_quick_subway,rail_replacement_bus,,,prod_rapid_transit_quick_subway,,,1 +leg_rapid_transit_cash,rapid_transit,area_green_b_west_of_kenmore,,prod_rapid_transit_cash,,, +leg_rapid_transit_cash,rapid_transit,area_green_c_west_of_kenmore,,prod_rapid_transit_cash,,, +leg_rapid_transit_cash,rapid_transit,area_green_e_west_of_symphony,,prod_rapid_transit_cash,,, +leg_rapid_transit_cash,rapid_transit,,,prod_rapid_transit_cash,,,1 +leg_rapid_transit_free,rapid_transit,area_bl,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_rapid_transit_free,rapid_transit,area_bl_airport,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_rapid_transit_free,rapid_transit,area_gl_govt_ctr,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_rapid_transit_free,rapid_transit,area_ol_state,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_rapid_transit_free,rapid_transit,,,prod_free_fare,,,1 +leg_rapid_transit_quick_subway,rapid_transit,area_bl,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_rapid_transit_quick_subway,rapid_transit,area_gl_govt_ctr,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_rapid_transit_quick_subway,rapid_transit,area_ol_state,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_rapid_transit_quick_subway,rapid_transit,,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl3_north_of_airport,area_sl3_north_of_airport,prod_rapid_transit_cash,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl3_north_of_airport,area_sl_airport,prod_rapid_transit_cash,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl3_north_of_airport,,prod_rapid_transit_cash,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_silver_line_way,area_sl3_north_of_airport,prod_rapid_transit_cash,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_silver_line_way,area_sl_airport,prod_rapid_transit_cash,timeframe_regular,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_silver_line_way,,prod_rapid_transit_cash,,, +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_south_station,area_sl3_north_of_airport,prod_rapid_transit_cash,,,1 +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_south_station,area_sl_airport,prod_rapid_transit_cash,,,1 +leg_sl_rapid_transit_cash,sl_rapid_transit,area_sl_south_station,,prod_rapid_transit_cash,,,1 +leg_sl_rapid_transit_cash,sl_rapid_transit,,,prod_rapid_transit_cash,,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl3_north_of_airport,area_sl3_north_of_airport,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl3_north_of_airport,area_sl_airport,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl3_north_of_airport,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl_airport,area_sl3_north_of_airport,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl_airport,,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl_logan_terminal,,prod_free_fare,,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl_silver_line_way,area_sl3_north_of_airport,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_free,sl_rapid_transit,area_sl_silver_line_way,area_sl_airport,prod_free_fare,timeframe_sumner_tunnel_closure,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_airport,area_sl3_north_of_airport,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_airport,,prod_rapid_transit_quick_subway,timeframe_regular,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_courthouse,area_sl3_north_of_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_courthouse,area_sl_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_courthouse,,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_south_station,area_sl3_north_of_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_south_station,area_sl_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_south_station,,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_world_trade_center,area_sl3_north_of_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_world_trade_center,area_sl_airport,prod_rapid_transit_quick_subway,,, +leg_sl_rapid_transit_quick_subway,sl_rapid_transit,area_sl_world_trade_center,,prod_rapid_transit_quick_subway,,, +leg_ss_fairmount_line_zone_1a_cash,commuter_rail,area_ss_commuter_rail_zone_1a,area_fairmount_line_zone_1a,prod_cr_zone_1a,,, +leg_ss_rapid_transit_cash,rapid_transit,area_red_south_station,,prod_rapid_transit_cash,,,1 +leg_ss_rapid_transit_free,rapid_transit,area_red_south_station,,prod_free_fare,,,1 +leg_ss_rapid_transit_quick_subway,rapid_transit,area_red_south_station,,prod_rapid_transit_quick_subway,,, +leg_systemwide_free,cape_flyer,area_cf_zone_buzzards,area_cf_zone_hyannis,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_buzzards,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_hyannis,area_cf_zone_buzzards,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_cf_zone_hyannis,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_1a,area_cf_zone_buzzards,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_1a,area_cf_zone_hyannis,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_2,area_cf_zone_buzzards,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_2,area_cf_zone_hyannis,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_4,area_cf_zone_buzzards,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_4,area_cf_zone_hyannis,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cape_flyer,area_commuter_rail_zone_8,area_cf_zone_hyannis,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_10,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_1a,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_2,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_3,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_4,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_5,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_6,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_7,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_8,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_commuter_rail_zone_9,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_fairmount_line_zone_1a,area_ss_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_1,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_10,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_2,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_3,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_4,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_5,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_6,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_7,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_8,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_commuter_rail_zone_9,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,commuter_rail,area_ss_commuter_rail_zone_1a,area_fairmount_line_zone_1a,prod_free_fare,timeframe_systemwide_free,, +leg_systemwide_free,cr_foxboro,,,prod_free_fare,timeframe_systemwide_free,, diff --git a/src/test/resources/fake-agency-with-fares-v2/fare_media.txt b/src/test/resources/fake-agency-with-fares-v2/fare_media.txt new file mode 100644 index 000000000..a695de634 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/fare_media.txt @@ -0,0 +1,5 @@ +fare_media_id,fare_media_name,fare_media_type +cash,Cash,0 +credit_debit,Credit/debit card,0 +charlieticket,CharlieTicket,1 +mticket,mTicket app,4 diff --git a/src/test/resources/fake-agency-with-fares-v2/fare_products.txt b/src/test/resources/fake-agency-with-fares-v2/fare_products.txt new file mode 100644 index 000000000..1138f4591 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/fare_products.txt @@ -0,0 +1,97 @@ +fare_product_id,fare_product_name,fare_media_id,amount,currency +prod_boat_zone_1,Ferry Zone 1 one-way fare,cash,6.50,USD +prod_boat_zone_1,Ferry Zone 1 one-way fare,credit_debit,6.50,USD +prod_boat_zone_1,Ferry Zone 1 one-way fare,mticket,6.50,USD +prod_boat_zone_1a,Ferry Zone 1A one-way fare,cash,2.40,USD +prod_boat_zone_1a,Ferry Zone 1A one-way fare,credit_debit,2.40,USD +prod_boat_zone_1a,Ferry Zone 1A one-way fare,mticket,2.40,USD +prod_boat_zone_2,Ferry Zone 2 one-way fare,cash,7.00,USD +prod_boat_zone_2,Ferry Zone 2 one-way fare,credit_debit,7.00,USD +prod_boat_zone_2,Ferry Zone 2 one-way fare,mticket,7.00,USD +prod_cape_buzzards_hyannis_fare,CapeFLYER Middleborough/Lakeville one-way fare,cash,5.00,USD +prod_cape_buzzards_hyannis_fare,CapeFLYER Middleborough/Lakeville one-way fare,credit_debit,5.00,USD +prod_cape_buzzards_hyannis_fare,CapeFLYER Middleborough/Lakeville one-way fare,mticket,5.00,USD +prod_cape_sbb_buzzards_fare,CapeFLYER Bourne one-way fare,cash,20.00,USD +prod_cape_sbb_buzzards_fare,CapeFLYER Bourne one-way fare,credit_debit,20.00,USD +prod_cape_sbb_buzzards_fare,CapeFLYER Bourne one-way fare,mticket,20.00,USD +prod_cape_sbb_hyannis_fare,CapeFLYER Hyannis one-way fare,cash,22.00,USD +prod_cape_sbb_hyannis_fare,CapeFLYER Hyannis one-way fare,credit_debit,22.00,USD +prod_cape_sbb_hyannis_fare,CapeFLYER Hyannis one-way fare,mticket,22.00,USD +prod_cr_inter_1,Commuter Rail Interzone 1 one-way fare,cash,2.75,USD +prod_cr_inter_1,Commuter Rail Interzone 1 one-way fare,credit_debit,2.75,USD +prod_cr_inter_1,Commuter Rail Interzone 1 one-way fare,mticket,2.75,USD +prod_cr_inter_10,Commuter Rail Interzone 10 one-way fare,cash,7.25,USD +prod_cr_inter_10,Commuter Rail Interzone 10 one-way fare,credit_debit,7.25,USD +prod_cr_inter_10,Commuter Rail Interzone 10 one-way fare,mticket,7.25,USD +prod_cr_inter_2,Commuter Rail Interzone 2 one-way fare,cash,3.25,USD +prod_cr_inter_2,Commuter Rail Interzone 2 one-way fare,credit_debit,3.25,USD +prod_cr_inter_2,Commuter Rail Interzone 2 one-way fare,mticket,3.25,USD +prod_cr_inter_3,Commuter Rail Interzone 3 one-way fare,cash,3.50,USD +prod_cr_inter_3,Commuter Rail Interzone 3 one-way fare,credit_debit,3.50,USD +prod_cr_inter_3,Commuter Rail Interzone 3 one-way fare,mticket,3.50,USD +prod_cr_inter_4,Commuter Rail Interzone 4 one-way fare,cash,4.25,USD +prod_cr_inter_4,Commuter Rail Interzone 4 one-way fare,credit_debit,4.25,USD +prod_cr_inter_4,Commuter Rail Interzone 4 one-way fare,mticket,4.25,USD +prod_cr_inter_5,Commuter Rail Interzone 5 one-way fare,cash,4.75,USD +prod_cr_inter_5,Commuter Rail Interzone 5 one-way fare,credit_debit,4.75,USD +prod_cr_inter_5,Commuter Rail Interzone 5 one-way fare,mticket,4.75,USD +prod_cr_inter_6,Commuter Rail Interzone 6 one-way fare,cash,5.25,USD +prod_cr_inter_6,Commuter Rail Interzone 6 one-way fare,credit_debit,5.25,USD +prod_cr_inter_6,Commuter Rail Interzone 6 one-way fare,mticket,5.25,USD +prod_cr_inter_7,Commuter Rail Interzone 7 one-way fare,cash,5.75,USD +prod_cr_inter_7,Commuter Rail Interzone 7 one-way fare,credit_debit,5.75,USD +prod_cr_inter_7,Commuter Rail Interzone 7 one-way fare,mticket,5.75,USD +prod_cr_inter_8,Commuter Rail Interzone 8 one-way fare,cash,6.25,USD +prod_cr_inter_8,Commuter Rail Interzone 8 one-way fare,credit_debit,6.25,USD +prod_cr_inter_8,Commuter Rail Interzone 8 one-way fare,mticket,6.25,USD +prod_cr_inter_9,Commuter Rail Interzone 9 one-way fare,cash,6.75,USD +prod_cr_inter_9,Commuter Rail Interzone 9 one-way fare,credit_debit,6.75,USD +prod_cr_inter_9,Commuter Rail Interzone 9 one-way fare,mticket,6.75,USD +prod_cr_zone_1,Commuter Rail Zone 1 one-way fare,cash,6.50,USD +prod_cr_zone_1,Commuter Rail Zone 1 one-way fare,credit_debit,6.50,USD +prod_cr_zone_1,Commuter Rail Zone 1 one-way fare,mticket,6.50,USD +prod_cr_zone_10,Commuter Rail Zone 10 one-way fare,cash,13.25,USD +prod_cr_zone_10,Commuter Rail Zone 10 one-way fare,credit_debit,13.25,USD +prod_cr_zone_10,Commuter Rail Zone 10 one-way fare,mticket,13.25,USD +prod_cr_zone_1a,Commuter Rail Zone 1A one-way fare,cash,2.40,USD +prod_cr_zone_1a,Commuter Rail Zone 1A one-way fare,credit_debit,2.40,USD +prod_cr_zone_1a,Commuter Rail Zone 1A one-way fare,mticket,2.40,USD +prod_cr_zone_2,Commuter Rail Zone 2 one-way fare,cash,7.00,USD +prod_cr_zone_2,Commuter Rail Zone 2 one-way fare,credit_debit,7.00,USD +prod_cr_zone_2,Commuter Rail Zone 2 one-way fare,mticket,7.00,USD +prod_cr_zone_3,Commuter Rail Zone 3 one-way fare,cash,8.00,USD +prod_cr_zone_3,Commuter Rail Zone 3 one-way fare,credit_debit,8.00,USD +prod_cr_zone_3,Commuter Rail Zone 3 one-way fare,mticket,8.00,USD +prod_cr_zone_4,Commuter Rail Zone 4 one-way fare,cash,8.75,USD +prod_cr_zone_4,Commuter Rail Zone 4 one-way fare,credit_debit,8.75,USD +prod_cr_zone_4,Commuter Rail Zone 4 one-way fare,mticket,8.75,USD +prod_cr_zone_5,Commuter Rail Zone 5 one-way fare,cash,9.75,USD +prod_cr_zone_5,Commuter Rail Zone 5 one-way fare,credit_debit,9.75,USD +prod_cr_zone_5,Commuter Rail Zone 5 one-way fare,mticket,9.75,USD +prod_cr_zone_6,Commuter Rail Zone 6 one-way fare,cash,10.50,USD +prod_cr_zone_6,Commuter Rail Zone 6 one-way fare,credit_debit,10.50,USD +prod_cr_zone_6,Commuter Rail Zone 6 one-way fare,mticket,10.50,USD +prod_cr_zone_7,Commuter Rail Zone 7 one-way fare,cash,11.00,USD +prod_cr_zone_7,Commuter Rail Zone 7 one-way fare,credit_debit,11.00,USD +prod_cr_zone_7,Commuter Rail Zone 7 one-way fare,mticket,11.00,USD +prod_cr_zone_8,Commuter Rail Zone 8 one-way fare,cash,12.25,USD +prod_cr_zone_8,Commuter Rail Zone 8 one-way fare,credit_debit,12.25,USD +prod_cr_zone_8,Commuter Rail Zone 8 one-way fare,mticket,12.25,USD +prod_cr_zone_9,Commuter Rail Zone 9 one-way fare,cash,12.75,USD +prod_cr_zone_9,Commuter Rail Zone 9 one-way fare,credit_debit,12.75,USD +prod_cr_zone_9,Commuter Rail Zone 9 one-way fare,mticket,12.75,USD +prod_express_bus,Express Bus cash fare,cash,4.25,USD +prod_ferry_east_boston,East Boston Ferry one-way fare,mticket,2.40,USD +prod_ferry_f1,Hingham/Hull Ferry one-way fare,cash,9.75,USD +prod_ferry_f1,Hingham/Hull Ferry one-way fare,credit_debit,9.75,USD +prod_ferry_f1,Hingham/Hull Ferry one-way fare,mticket,9.75,USD +prod_ferry_f4,Charlestown Ferry one-way fare,cash,3.70,USD +prod_ferry_f4,Charlestown Ferry one-way fare,credit_debit,3.70,USD +prod_ferry_f4,Charlestown Ferry one-way fare,mticket,3.70,USD +prod_foxboro_event_fare,Foxboro Event Service round-trip fare,cash,20.00,USD +prod_foxboro_event_fare,Foxboro Event Service round-trip fare,credit_debit,20.00,USD +prod_foxboro_event_fare,Foxboro Event Service round-trip fare,mticket,20.00,USD +prod_free_fare,Free fare,,0.00,USD +prod_local_bus,Local Bus cash fare,cash,1.70,USD +prod_rapid_transit_cash,Subway cash fare,cash,2.40,USD +prod_rapid_transit_quick_subway,Subway Quick Ticket,charlieticket,2.40,USD diff --git a/src/test/resources/fake-agency-with-fares-v2/fare_transfer_rules.txt b/src/test/resources/fake-agency-with-fares-v2/fare_transfer_rules.txt new file mode 100644 index 000000000..0286fa015 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/fare_transfer_rules.txt @@ -0,0 +1,38 @@ +from_leg_group_id,to_leg_group_id,transfer_count,duration_limit,duration_limit_type,fare_transfer_type,fare_product_id,filter_fare_product_id,fare_media_behavior,fare_product_behavior +leg_airport_rapid_transit_quick_subway,leg_local_bus_quick_subway,,7200,1,0,prod_rapid_transit_quick_subway,prod_rapid_transit_quick_subway,0,1 +leg_airport_rapid_transit_quick_subway,leg_rapid_transit_quick_subway,,,,0,prod_rapid_transit_quick_subway,prod_rapid_transit_quick_subway,0,1 +leg_mattapan_rapid_transit_quick_subway,leg_local_bus_quick_subway,,7200,1,0,prod_rapid_transit_quick_subway,prod_rapid_transit_quick_subway,0,1 +leg_mattapan_rapid_transit_quick_subway,leg_rail_replacement_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_mattapan_rapid_transit_quick_subway,leg_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_mattapan_rapid_transit_quick_subway,leg_ss_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_airport_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_local_bus_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_mattapan_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_rail_replacement_quick_subway,-1,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_sl_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rail_replacement_quick_subway,leg_ss_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_cash,leg_rapid_transit_cash,-1,,,0,,prod_rapid_transit_cash,0,1 +leg_rapid_transit_cash,leg_sl_rapid_transit_cash,,,,0,,prod_rapid_transit_cash,0,1 +leg_rapid_transit_free,leg_rapid_transit_free,-1,,,0,,prod_free_fare,0,1 +leg_rapid_transit_free,leg_sl_rapid_transit_free,,,,0,,prod_free_fare,0,1 +leg_rapid_transit_quick_subway,leg_airport_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_local_bus_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_mattapan_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_rail_replacement_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_rapid_transit_quick_subway,-1,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_sl_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_rapid_transit_quick_subway,leg_ss_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_sl_rapid_transit_cash,leg_ss_rapid_transit_cash,,,,0,,prod_rapid_transit_cash,0,1 +leg_sl_rapid_transit_free,leg_ss_rapid_transit_free,,,,0,,prod_free_fare,0,1 +leg_sl_rapid_transit_quick_subway,leg_airport_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_sl_rapid_transit_quick_subway,leg_local_bus_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_sl_rapid_transit_quick_subway,leg_mattapan_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_sl_rapid_transit_quick_subway,leg_rail_replacement_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_sl_rapid_transit_quick_subway,leg_ss_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_ss_rapid_transit_cash,leg_rapid_transit_cash,,,,0,,prod_rapid_transit_cash,0,1 +leg_ss_rapid_transit_free,leg_rapid_transit_free,,,,0,,prod_free_fare,0,1 +leg_ss_rapid_transit_quick_subway,leg_local_bus_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_ss_rapid_transit_quick_subway,leg_mattapan_rapid_transit_quick_subway,,7200,1,0,,prod_rapid_transit_quick_subway,0,1 +leg_ss_rapid_transit_quick_subway,leg_rail_replacement_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 +leg_ss_rapid_transit_quick_subway,leg_rapid_transit_quick_subway,,,,0,,prod_rapid_transit_quick_subway,0,1 diff --git a/src/test/resources/fake-agency-with-fares-v2/feed_info.txt b/src/test/resources/fake-agency-with-fares-v2/feed_info.txt new file mode 100644 index 000000000..ceac60810 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/feed_info.txt @@ -0,0 +1,2 @@ +feed_id,feed_publisher_name,feed_publisher_url,feed_lang,feed_version +fake_transit,Conveyal,http://www.conveyal.com,en,1.0 \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/networks.txt b/src/test/resources/fake-agency-with-fares-v2/networks.txt new file mode 100644 index 000000000..47a67eaad --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/networks.txt @@ -0,0 +1,2 @@ +network_id,network_name +1,Forbidden because network id is defined in routes \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/route_networks.txt b/src/test/resources/fake-agency-with-fares-v2/route_networks.txt new file mode 100644 index 000000000..d2f8f9a9d --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/route_networks.txt @@ -0,0 +1,2 @@ +network_id,route_id +1,1 \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/routes.txt b/src/test/resources/fake-agency-with-fares-v2/routes.txt new file mode 100644 index 000000000..dd6a30eaf --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/routes.txt @@ -0,0 +1,2 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color,route_sort_order,route_fare_class,line_id,listed_route,network_id +1,1,,RL,RT,1,https://www.mbta.com/schedules/Red,DA291C,FFFFFF,10010,Rapid Transit,line-Red,,rapid_transit \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/shapes.txt b/src/test/resources/fake-agency-with-fares-v2/shapes.txt new file mode 100644 index 000000000..3f2e3fd13 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/shapes.txt @@ -0,0 +1,8 @@ +shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled +5820f377-f947-4728-ac29-ac0102cbc34e,37.0612132,-122.0074332,1,0.0000000 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0611720,-122.0075000,2,7.4997067 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0613590,-122.0076830,3,33.8739075 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0608780,-122.0082780,4,109.0402932 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0603590,-122.0088280,5,184.6078298 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0597610,-122.0093540,6,265.8053023 +5820f377-f947-4728-ac29-ac0102cbc34e,37.0590660,-122.0099190,7,357.8617018 diff --git a/src/test/resources/fake-agency-with-fares-v2/stop_areas.txt b/src/test/resources/fake-agency-with-fares-v2/stop_areas.txt new file mode 100644 index 000000000..85cbda304 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/stop_areas.txt @@ -0,0 +1,848 @@ +stop_id,area_id +4u6g,area_route_426_downtown +4u6g,area_route_450_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_sl_logan_terminal +4u6g,area_sl_logan_terminal +4u6g,area_sl_logan_terminal +4u6g,area_sl_logan_terminal +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_sl_logan_terminal +4u6g,area_route_426_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_downtown +4u6g,area_route_354_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_ol_state +4u6g,area_ol_state +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl_airport +4u6g,area_bl_airport +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_bl +4u6g,area_red_south_station +4u6g,area_red_south_station +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_green_b_west_of_kenmore +4u6g,area_gl_govt_ctr +4u6g,area_gl_govt_ctr +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_c_west_of_kenmore +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_green_e_west_of_symphony +4u6g,area_m_ashmont_mattapan +4u6g,area_m_ashmont_mattapan +4u6g,area_m_ashmont_mattapan +4u6g,area_bl +4u6g,area_sl_airport +4u6g,area_sl_airport +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_450_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_route_426_outside_downtown +4u6g,area_sl_south_station +4u6g,area_sl_courthouse +4u6g,area_sl_world_trade_center +4u6g,area_sl_silver_line_way +4u6g,area_sl_world_trade_center +4u6g,area_sl_courthouse +4u6g,area_sl_south_station +4u6g,area_sl_silver_line_way +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_sl3_north_of_airport +4u6g,area_route_426_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_route_426_downtown +4u6g,area_route_450_downtown +4u6g,area_route_354_outside_downtown +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_cf_zone_buzzards +4u6g,area_cf_zone_buzzards +4u6g,area_cf_zone_buzzards +4u6g,area_cf_zone_hyannis +4u6g,area_commuter_rail_zone_2 +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_fairmount_line_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_porter_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_porter_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_porter_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_sumner_tunnel_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_10 +4u6g,area_commuter_rail_zone_9 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_ss_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_8 +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1a +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_1 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_2 +4u6g,area_commuter_rail_zone_3 +4u6g,area_commuter_rail_zone_4 +4u6g,area_commuter_rail_zone_5 +4u6g,area_commuter_rail_zone_6 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 +4u6g,area_commuter_rail_zone_7 \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/stop_times.txt b/src/test/resources/fake-agency-with-fares-v2/stop_times.txt new file mode 100644 index 000000000..1cfefaa49 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/stop_times.txt @@ -0,0 +1,7 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled,timepoint +a30277f8-e50a-4a85-9141-b1e0da9d429d,07:00:00,07:00:00,4u6g,1,Test stop headsign,0,0,0.0000000, +a30277f8-e50a-4a85-9141-b1e0da9d429d,07:01:00,07:01:00,johv,2,Test stop headsign 2,0,0,341.4491961, +frequency-trip,08:00:00,08:00:00,4u6g,1,Test stop headsign frequency trip,0,0,0.0000000, +frequency-trip,08:29:00,08:29:00,1234,2,Test stop headsign frequency trip 2,0,0,341.4491961, +calendar-date-trip,08:00:00,08:00:00,4u6g,1,Test stop headsign calendar date trip,0,0,0.0000000, +calendar-date-trip,08:29:00,08:29:00,1234,2,Test stop headsign calendar date trip 2,0,0,341.4491961, \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/stops.txt b/src/test/resources/fake-agency-with-fares-v2/stops.txt new file mode 100644 index 000000000..ba368047e --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/stops.txt @@ -0,0 +1,5 @@ +stop_id,stop_code,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,stop_timezone,wheelchair_boarding +4u6g,,Butler Ln,,37.0612132,-122.0074332,,,0,,, +johv,,Scotts Valley Dr & Victor Sq,,37.0590172,-122.0096058,,,0,,, +123,,Parent Station,,37.0666,-122.0777,,,1,,, +1234,,Child Stop,,37.06662,-122.07772,,,0,123,, \ No newline at end of file diff --git a/src/test/resources/fake-agency-with-fares-v2/timeframes.txt b/src/test/resources/fake-agency-with-fares-v2/timeframes.txt new file mode 100644 index 000000000..52667abe1 --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/timeframes.txt @@ -0,0 +1,8 @@ +timeframe_group_id,start_time,end_time,service_id +timeframe_regular,,,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_systemwide_free,,,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_sumner_tunnel_closure,,,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_sumner_tunnel_closure,00:00:00,02:30:00,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_regular,02:30:00,24:00:00,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_alewife_kendall_surge,,,04100312-8fe1-46a5-a9f2-556f39478f57 +timeframe_alewife_kendall_surge,00:00:00,02:30:00,04100312-8fe1-46a5-a9f2-556f39478f57 diff --git a/src/test/resources/fake-agency-with-fares-v2/trips.txt b/src/test/resources/fake-agency-with-fares-v2/trips.txt new file mode 100644 index 000000000..982c01e0f --- /dev/null +++ b/src/test/resources/fake-agency-with-fares-v2/trips.txt @@ -0,0 +1,4 @@ +route_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,shape_id,bikes_allowed,wheelchair_accessible,service_id +1,a30277f8-e50a-4a85-9141-b1e0da9d429d,,,0,,5820f377-f947-4728-ac29-ac0102cbc34e,0,0,04100312-8fe1-46a5-a9f2-556f39478f57 +1,frequency-trip,,,0,,5820f377-f947-4728-ac29-ac0102cbc34e,0,0,04100312-8fe1-46a5-a9f2-556f39478f57 +1,calendar-date-trip,,,0,,5820f377-f947-4728-ac29-ac0102cbc34e,0,0,calendar-date-service \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/areas.json b/src/test/resources/fares-v2-json-entities/areas.json new file mode 100644 index 000000000..07fdcbac5 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/areas.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "area_id": "area_bl", + "area_name": "Blue Line" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/areas_updated.json b/src/test/resources/fares-v2-json-entities/areas_updated.json new file mode 100644 index 000000000..9d0c38432 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/areas_updated.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "area_id": "area_b2", + "area_name": "Blue Line updated" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_leg_rules.json b/src/test/resources/fares-v2-json-entities/fare_leg_rules.json new file mode 100644 index 000000000..ce0d2ff7a --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_leg_rules.json @@ -0,0 +1,12 @@ +{ + "id": 1, + "leg_group_id": "leg_airport_rapid_transit_quick_subway", + "network_id": "rapid_transit", + "from_area_id": "area_bl_airport", + "to_area_id": "", + "fare_product_id": "prod_rapid_transit_quick_subway", + "from_timeframe_group_id": "timeframe_regular", + "to_timeframe_group_id": "", + "transfer_only": "", + "rule_priority": "" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_leg_rules_updated.json b/src/test/resources/fares-v2-json-entities/fare_leg_rules_updated.json new file mode 100644 index 000000000..16b3c3d36 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_leg_rules_updated.json @@ -0,0 +1,12 @@ +{ + "id": 1, + "leg_group_id": "leg_airport_rapid_transit_quick_subway updated", + "network_id": "rapid_transit updated", + "from_area_id": "area_bl_airport updated", + "to_area_id": "", + "fare_product_id": "prod_rapid_transit_quick_subway updated", + "from_timeframe_group_id": "timeframe_regular updated", + "to_timeframe_group_id": "", + "transfer_only": "", + "rule_priority": "" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_media.json b/src/test/resources/fares-v2-json-entities/fare_media.json new file mode 100644 index 000000000..701b05698 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_media.json @@ -0,0 +1,6 @@ +{ + "id": 1, + "fare_media_id": "cash", + "fare_media_name": "Cash", + "fare_media_type": "0" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_media_updated.json b/src/test/resources/fares-v2-json-entities/fare_media_updated.json new file mode 100644 index 000000000..bac11d0f7 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_media_updated.json @@ -0,0 +1,6 @@ +{ + "id": 1, + "fare_media_id": "cash updated", + "fare_media_name": "Cash updated", + "fare_media_type": "1" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_product.json b/src/test/resources/fares-v2-json-entities/fare_product.json new file mode 100644 index 000000000..60fb64c8b --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_product.json @@ -0,0 +1,8 @@ +{ + "id": 1, + "fare_product_id": "AERIAL_TRAM_ROUND_TRIP", + "fare_product_name": "Portland Aerial Tram Single Round Trip", + "fare_media_id": "1", + "amount": "13.5", + "currency": "USD" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_product_updated.json b/src/test/resources/fares-v2-json-entities/fare_product_updated.json new file mode 100644 index 000000000..19510f60b --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_product_updated.json @@ -0,0 +1,8 @@ +{ + "id": 1, + "fare_product_id": "AERIAL_TRAM_ROUND_TRIP_UPDATED", + "fare_product_name": "Portland Aerial Tram Single Round Trip", + "fare_media_id": "1", + "amount": "14.5", + "currency": "USD" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_transfer_rules.json b/src/test/resources/fares-v2-json-entities/fare_transfer_rules.json new file mode 100644 index 000000000..fb4a94f1c --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_transfer_rules.json @@ -0,0 +1,13 @@ +{ + "id": 1, + "from_leg_group_id": "leg_airport_rapid_transit_quick_subway", + "to_leg_group_id": "leg_local_bus_quick_subway", + "transfer_count": "", + "duration_limit": "7200", + "duration_limit_type": "1", + "fare_transfer_type": "0", + "fare_product_id": "prod_rapid_transit_quick_subway", + "filter_fare_product_id": "prod_rapid_transit_quick_subway", + "fare_media_behavior": "0", + "fare_product_behavior": "1" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/fare_transfer_rules_updated.json b/src/test/resources/fares-v2-json-entities/fare_transfer_rules_updated.json new file mode 100644 index 000000000..5fcf6fc09 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/fare_transfer_rules_updated.json @@ -0,0 +1,13 @@ +{ + "id": 1, + "from_leg_group_id": "leg_airport_rapid_transit_quick_subway updated", + "to_leg_group_id": "leg_local_bus_quick_subway updated", + "transfer_count": "", + "duration_limit": "7300", + "duration_limit_type": "1", + "fare_transfer_type": "0", + "fare_product_id": "prod_rapid_transit_quick_subway updated", + "filter_fare_product_id": "prod_rapid_transit_quick_subway updated", + "fare_media_behavior": "0", + "fare_product_behavior": "1" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/networks.json b/src/test/resources/fares-v2-json-entities/networks.json new file mode 100644 index 000000000..a43f227a1 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/networks.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "network_id": 1, + "network_name": "This is the network name" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/networks_updated.json b/src/test/resources/fares-v2-json-entities/networks_updated.json new file mode 100644 index 000000000..fcef0ec98 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/networks_updated.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "network_id": 1, + "network_name": "This is the network name updated" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/route_networks.json b/src/test/resources/fares-v2-json-entities/route_networks.json new file mode 100644 index 000000000..d39e03a67 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/route_networks.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "network_id": 1, + "route_id": 1 +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/route_networks_updated.json b/src/test/resources/fares-v2-json-entities/route_networks_updated.json new file mode 100644 index 000000000..eb884d24d --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/route_networks_updated.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "network_id": "2", + "route_id": "2" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/stop_areas.json b/src/test/resources/fares-v2-json-entities/stop_areas.json new file mode 100644 index 000000000..c34f7538a --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/stop_areas.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "stop_id": "4u6g", + "area_id": "area_route_426_downtown" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/stop_areas_updated.json b/src/test/resources/fares-v2-json-entities/stop_areas_updated.json new file mode 100644 index 000000000..f84eb6955 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/stop_areas_updated.json @@ -0,0 +1,5 @@ +{ + "id": 1, + "stop_id": "4u6g updated", + "area_id": "area_route_426_downtown updated" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/time_frames.json b/src/test/resources/fares-v2-json-entities/time_frames.json new file mode 100644 index 000000000..157c1e2c5 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/time_frames.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "timeframe_group_id": "timeframe_sumner_tunnel_closure", + "start_time": "00:00:00", + "end_time": "02:30:00", + "service_id": "04100312-8fe1-46a5-a9f2-556f39478f57" +} \ No newline at end of file diff --git a/src/test/resources/fares-v2-json-entities/time_frames_updated.json b/src/test/resources/fares-v2-json-entities/time_frames_updated.json new file mode 100644 index 000000000..3ab456b36 --- /dev/null +++ b/src/test/resources/fares-v2-json-entities/time_frames_updated.json @@ -0,0 +1,7 @@ +{ + "id": 1, + "timeframe_group_id": "timeframe_sumner_tunnel_closure_updated", + "start_time": "01:30:00", + "end_time": "03:45:00", + "service_id": "04100312-8fe1-46a5-a9f2-556f39478f57updated" +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedAreas.txt b/src/test/resources/graphql/feedAreas.txt new file mode 100644 index 000000000..b2a19a710 --- /dev/null +++ b/src/test/resources/graphql/feedAreas.txt @@ -0,0 +1,19 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + area(limit: 2) { + area_id + area_name + id + stop_areas { + area_id + stop_id + id + stops { + stop_id + stop_name + } + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedFareLegRules.txt b/src/test/resources/graphql/feedFareLegRules.txt new file mode 100644 index 000000000..237782680 --- /dev/null +++ b/src/test/resources/graphql/feedFareLegRules.txt @@ -0,0 +1,51 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + fare_leg_rule(limit: 10) { + leg_group_id + network_id + from_area_id + to_area_id + from_timeframe_group_id + to_timeframe_group_id + fare_product_id + rule_priority + id + routes { + route_id + route_long_name + } + networks { + network_id + network_name + } + fare_products { + fare_product_id + fare_product_name + fare_media_id + amount + currency + } + from_time_frame { + timeframe_group_id + start_time + end_time + service_id + } + to_time_frame { + timeframe_group_id + start_time + end_time + service_id + } + to_area { + area_id + area_name + } + from_area { + area_id + area_name + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedFareMedias.txt b/src/test/resources/graphql/feedFareMedias.txt new file mode 100644 index 000000000..9d4d08e08 --- /dev/null +++ b/src/test/resources/graphql/feedFareMedias.txt @@ -0,0 +1,11 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + fare_media { + fare_media_id + fare_media_name + fare_media_type + id + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedFareProducts.txt b/src/test/resources/graphql/feedFareProducts.txt new file mode 100644 index 000000000..536ae1813 --- /dev/null +++ b/src/test/resources/graphql/feedFareProducts.txt @@ -0,0 +1,18 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + fare_product(limit: 10) { + fare_product_id + fare_product_name + fare_media_id + amount + currency + fare_media { + fare_media_id + fare_media_name + fare_media_type + id + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedFareTransferRules.txt b/src/test/resources/graphql/feedFareTransferRules.txt new file mode 100644 index 000000000..b8d5fc762 --- /dev/null +++ b/src/test/resources/graphql/feedFareTransferRules.txt @@ -0,0 +1,48 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + fare_transfer_rule (limit: 2) { + from_leg_group_id + to_leg_group_id + transfer_count + duration_limit + duration_limit_type + fare_product_id + fare_products { + fare_product_id + fare_product_name + fare_media_id + amount + currency + fare_media { + fare_media_id + fare_media_name + fare_media_type + id + } + } + from_fare_leg_rule { + leg_group_id + network_id + from_area_id + to_area_id + from_timeframe_group_id + to_timeframe_group_id + fare_product_id + rule_priority + id + } + to_fare_leg_rule { + leg_group_id + network_id + from_area_id + to_area_id + from_timeframe_group_id + to_timeframe_group_id + fare_product_id + rule_priority + id + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedNetworks.txt b/src/test/resources/graphql/feedNetworks.txt new file mode 100644 index 000000000..b0ebdeecc --- /dev/null +++ b/src/test/resources/graphql/feedNetworks.txt @@ -0,0 +1,9 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + network { + network_id + network_name + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedRouteNetworks.txt b/src/test/resources/graphql/feedRouteNetworks.txt new file mode 100644 index 000000000..19fb4eef0 --- /dev/null +++ b/src/test/resources/graphql/feedRouteNetworks.txt @@ -0,0 +1,9 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + route_network { + network_id + route_id + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedRoutes.txt b/src/test/resources/graphql/feedRoutes.txt index f2b74dfb6..053960dd1 100644 --- a/src/test/resources/graphql/feedRoutes.txt +++ b/src/test/resources/graphql/feedRoutes.txt @@ -28,6 +28,7 @@ query ($namespace: String) { trip_id } wheelchair_accessible + network_id } } } \ No newline at end of file diff --git a/src/test/resources/graphql/feedStopAreas.txt b/src/test/resources/graphql/feedStopAreas.txt new file mode 100644 index 000000000..5e455f11d --- /dev/null +++ b/src/test/resources/graphql/feedStopAreas.txt @@ -0,0 +1,14 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + stop_area { + area_id + stop_id + id + stops { + stop_id + stop_name + } + } + } +} \ No newline at end of file diff --git a/src/test/resources/graphql/feedTimeFrames.txt b/src/test/resources/graphql/feedTimeFrames.txt new file mode 100644 index 000000000..80331f449 --- /dev/null +++ b/src/test/resources/graphql/feedTimeFrames.txt @@ -0,0 +1,12 @@ +query ($namespace: String) { + feed(namespace: $namespace) { + feed_version + time_frame { + timeframe_group_id + start_time + end_time + service_id + id + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchAreas-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchAreas-0.json new file mode 100644 index 000000000..25b2ec25f --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchAreas-0.json @@ -0,0 +1,210 @@ +{ + "data" : { + "feed" : { + "area" : [ { + "area_id" : "area_bl", + "area_name" : "Blue Line", + "id" : 2, + "stop_areas" : [ { + "area_id" : "area_bl", + "id" : 229, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 230, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 231, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 232, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 233, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 234, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 235, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 236, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 237, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 240, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 241, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 242, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 243, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 244, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 245, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 246, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 247, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 248, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 249, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 250, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 251, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl", + "id" : 330, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + } ] + }, { + "area_id" : "area_bl_airport", + "area_name" : "Blue Line - Airport Station", + "id" : 3, + "stop_areas" : [ { + "area_id" : "area_bl_airport", + "id" : 238, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_bl_airport", + "id" : 239, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + } ] + } ], + "feed_version" : "1.0" + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareLegRules-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareLegRules-0.json new file mode 100644 index 000000000..db5a0cc32 --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareLegRules-0.json @@ -0,0 +1,416 @@ +{ + "data" : { + "feed" : { + "fare_leg_rule" : [ { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_products" : [ { + "amount" : "2.4", + "currency" : "USD", + "fare_media_id" : "charlieticket", + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_product_name" : "Subway Quick Ticket" + } ], + "from_area" : [ { + "area_id" : "area_bl_airport", + "area_name" : "Blue Line - Airport Station" + } ], + "from_area_id" : "area_bl_airport", + "from_time_frame" : [ { + "end_time" : null, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : null, + "timeframe_group_id" : "timeframe_regular" + }, { + "end_time" : "86400", + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : "9000", + "timeframe_group_id" : "timeframe_regular" + } ], + "from_timeframe_group_id" : "timeframe_regular", + "id" : 2, + "leg_group_id" : "leg_airport_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "networks" : [ ], + "routes" : [ { + "route_id" : "1", + "route_long_name" : "RL" + } ], + "rule_priority" : null, + "to_area" : [ ], + "to_area_id" : null, + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_products" : [ { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "from_area_id" : "area_cf_zone_buzzards", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 3, + "leg_group_id" : "leg_cape_buzzards_hyannis_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_cf_zone_hyannis", + "area_name" : "CapeFLYER - Hyannis" + } ], + "to_area_id" : "area_cf_zone_hyannis", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_products" : [ { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_hyannis", + "area_name" : "CapeFLYER - Hyannis" + } ], + "from_area_id" : "area_cf_zone_hyannis", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 4, + "leg_group_id" : "leg_cape_buzzards_hyannis_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "to_area_id" : "area_cf_zone_buzzards", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_products" : [ { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_hyannis", + "area_name" : "CapeFLYER - Hyannis" + } ], + "from_area_id" : "area_cf_zone_hyannis", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 5, + "leg_group_id" : "leg_cape_buzzards_hyannis_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_commuter_rail_zone_8", + "area_name" : "Commuter Rail Zone 8" + } ], + "to_area_id" : "area_commuter_rail_zone_8", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_products" : [ { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_commuter_rail_zone_8", + "area_name" : "Commuter Rail Zone 8" + } ], + "from_area_id" : "area_commuter_rail_zone_8", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 6, + "leg_group_id" : "leg_cape_buzzards_hyannis_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_cf_zone_hyannis", + "area_name" : "CapeFLYER - Hyannis" + } ], + "to_area_id" : "area_cf_zone_hyannis", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_products" : [ { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "from_area_id" : "area_cf_zone_buzzards", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 7, + "leg_group_id" : "leg_cape_sbb_buzzards_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_commuter_rail_zone_1a", + "area_name" : "Commuter Rail Zone 1A" + } ], + "to_area_id" : "area_commuter_rail_zone_1a", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_products" : [ { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "from_area_id" : "area_cf_zone_buzzards", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 8, + "leg_group_id" : "leg_cape_sbb_buzzards_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_commuter_rail_zone_2", + "area_name" : "Commuter Rail Zone 2" + } ], + "to_area_id" : "area_commuter_rail_zone_2", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_products" : [ { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "from_area_id" : "area_cf_zone_buzzards", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 9, + "leg_group_id" : "leg_cape_sbb_buzzards_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_commuter_rail_zone_4", + "area_name" : "Commuter Rail Zone 4" + } ], + "to_area_id" : "area_commuter_rail_zone_4", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_products" : [ { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_commuter_rail_zone_1a", + "area_name" : "Commuter Rail Zone 1A" + } ], + "from_area_id" : "area_commuter_rail_zone_1a", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 10, + "leg_group_id" : "leg_cape_sbb_buzzards_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "to_area_id" : "area_cf_zone_buzzards", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_products" : [ { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + }, { + "amount" : "20.0", + "currency" : "USD", + "fare_media_id" : "mticket", + "fare_product_id" : "prod_cape_sbb_buzzards_fare", + "fare_product_name" : "CapeFLYER Bourne one-way fare" + } ], + "from_area" : [ { + "area_id" : "area_commuter_rail_zone_2", + "area_name" : "Commuter Rail Zone 2" + } ], + "from_area_id" : "area_commuter_rail_zone_2", + "from_time_frame" : [ ], + "from_timeframe_group_id" : null, + "id" : 11, + "leg_group_id" : "leg_cape_sbb_buzzards_cash", + "network_id" : "cape_flyer", + "networks" : [ ], + "routes" : [ ], + "rule_priority" : null, + "to_area" : [ { + "area_id" : "area_cf_zone_buzzards", + "area_name" : "CapeFLYER - Wareham/Buzzards Bay/Bourne" + } ], + "to_area_id" : "area_cf_zone_buzzards", + "to_time_frame" : [ ], + "to_timeframe_group_id" : null + } ], + "feed_version" : "1.0" + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareMedias-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareMedias-0.json new file mode 100644 index 000000000..9995d7f81 --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareMedias-0.json @@ -0,0 +1,28 @@ +{ + "data" : { + "feed" : { + "fare_media" : [ { + "fare_media_id" : "cash", + "fare_media_name" : "Cash", + "fare_media_type" : "0", + "id" : 2 + }, { + "fare_media_id" : "credit_debit", + "fare_media_name" : "Credit/debit card", + "fare_media_type" : "0", + "id" : 3 + }, { + "fare_media_id" : "charlieticket", + "fare_media_name" : "CharlieTicket", + "fare_media_type" : "1", + "id" : 4 + }, { + "fare_media_id" : "mticket", + "fare_media_name" : "mTicket app", + "fare_media_type" : "4", + "id" : 5 + } ], + "feed_version" : "1.0" + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareProducts-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareProducts-0.json new file mode 100644 index 000000000..c9607e342 --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareProducts-0.json @@ -0,0 +1,128 @@ +{ + "data" : { + "feed" : { + "fare_product" : [ { + "amount" : "6.5", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "cash", + "fare_media_name" : "Cash", + "fare_media_type" : "0", + "id" : 2 + } ], + "fare_media_id" : "cash", + "fare_product_id" : "prod_boat_zone_1", + "fare_product_name" : "Ferry Zone 1 one-way fare" + }, { + "amount" : "6.5", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "credit_debit", + "fare_media_name" : "Credit/debit card", + "fare_media_type" : "0", + "id" : 3 + } ], + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_boat_zone_1", + "fare_product_name" : "Ferry Zone 1 one-way fare" + }, { + "amount" : "6.5", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "mticket", + "fare_media_name" : "mTicket app", + "fare_media_type" : "4", + "id" : 5 + } ], + "fare_media_id" : "mticket", + "fare_product_id" : "prod_boat_zone_1", + "fare_product_name" : "Ferry Zone 1 one-way fare" + }, { + "amount" : "2.4", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "cash", + "fare_media_name" : "Cash", + "fare_media_type" : "0", + "id" : 2 + } ], + "fare_media_id" : "cash", + "fare_product_id" : "prod_boat_zone_1a", + "fare_product_name" : "Ferry Zone 1A one-way fare" + }, { + "amount" : "2.4", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "credit_debit", + "fare_media_name" : "Credit/debit card", + "fare_media_type" : "0", + "id" : 3 + } ], + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_boat_zone_1a", + "fare_product_name" : "Ferry Zone 1A one-way fare" + }, { + "amount" : "2.4", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "mticket", + "fare_media_name" : "mTicket app", + "fare_media_type" : "4", + "id" : 5 + } ], + "fare_media_id" : "mticket", + "fare_product_id" : "prod_boat_zone_1a", + "fare_product_name" : "Ferry Zone 1A one-way fare" + }, { + "amount" : "7.0", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "cash", + "fare_media_name" : "Cash", + "fare_media_type" : "0", + "id" : 2 + } ], + "fare_media_id" : "cash", + "fare_product_id" : "prod_boat_zone_2", + "fare_product_name" : "Ferry Zone 2 one-way fare" + }, { + "amount" : "7.0", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "credit_debit", + "fare_media_name" : "Credit/debit card", + "fare_media_type" : "0", + "id" : 3 + } ], + "fare_media_id" : "credit_debit", + "fare_product_id" : "prod_boat_zone_2", + "fare_product_name" : "Ferry Zone 2 one-way fare" + }, { + "amount" : "7.0", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "mticket", + "fare_media_name" : "mTicket app", + "fare_media_type" : "4", + "id" : 5 + } ], + "fare_media_id" : "mticket", + "fare_product_id" : "prod_boat_zone_2", + "fare_product_name" : "Ferry Zone 2 one-way fare" + }, { + "amount" : "5.0", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "cash", + "fare_media_name" : "Cash", + "fare_media_type" : "0", + "id" : 2 + } ], + "fare_media_id" : "cash", + "fare_product_id" : "prod_cape_buzzards_hyannis_fare", + "fare_product_name" : "CapeFLYER Middleborough/Lakeville one-way fare" + } ], + "feed_version" : "1.0" + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareTransferRules-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareTransferRules-0.json new file mode 100644 index 000000000..d7bd4d3ba --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchFareTransferRules-0.json @@ -0,0 +1,662 @@ +{ + "data" : { + "feed" : { + "fare_transfer_rule" : [ { + "duration_limit" : "7200", + "duration_limit_type" : "1", + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_products" : [ { + "amount" : "2.4", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "charlieticket", + "fare_media_name" : "CharlieTicket", + "fare_media_type" : "1", + "id" : 4 + } ], + "fare_media_id" : "charlieticket", + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_product_name" : "Subway Quick Ticket" + } ], + "from_fare_leg_rule" : [ { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_bl_airport", + "from_timeframe_group_id" : "timeframe_regular", + "id" : 2, + "leg_group_id" : "leg_airport_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + } ], + "from_leg_group_id" : "leg_airport_rapid_transit_quick_subway", + "to_fare_leg_rule" : [ { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 311, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 312, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 313, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 314, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 315, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 316, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "express_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 317, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 318, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 319, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 320, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 321, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 322, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_downtown", + "from_timeframe_group_id" : null, + "id" : 323, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 324, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 325, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 326, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 327, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 328, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 329, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_354_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 330, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 331, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 332, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 333, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 334, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 335, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 336, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_downtown", + "from_timeframe_group_id" : null, + "id" : 337, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 338, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 339, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 340, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 341, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 342, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 343, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_426_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 344, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 345, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 346, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 347, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 348, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 349, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 350, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_downtown", + "from_timeframe_group_id" : null, + "id" : 351, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 352, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 353, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 354, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 355, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 356, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 357, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_route_450_outside_downtown", + "from_timeframe_group_id" : null, + "id" : 358, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 359, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 360, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_354_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 361, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 362, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_426_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 363, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 364, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : "area_route_450_outside_downtown", + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : null, + "id" : 365, + "leg_group_id" : "leg_local_bus_quick_subway", + "network_id" : "local_bus", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + } ], + "to_leg_group_id" : "leg_local_bus_quick_subway", + "transfer_count" : null + }, { + "duration_limit" : null, + "duration_limit_type" : null, + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_products" : [ { + "amount" : "2.4", + "currency" : "USD", + "fare_media" : [ { + "fare_media_id" : "charlieticket", + "fare_media_name" : "CharlieTicket", + "fare_media_type" : "1", + "id" : 4 + } ], + "fare_media_id" : "charlieticket", + "fare_product_id" : "prod_rapid_transit_quick_subway", + "fare_product_name" : "Subway Quick Ticket" + } ], + "from_fare_leg_rule" : [ { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_bl_airport", + "from_timeframe_group_id" : "timeframe_regular", + "id" : 2, + "leg_group_id" : "leg_airport_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + } ], + "from_leg_group_id" : "leg_airport_rapid_transit_quick_subway", + "to_fare_leg_rule" : [ { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_bl", + "from_timeframe_group_id" : "timeframe_regular", + "id" : 476, + "leg_group_id" : "leg_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_gl_govt_ctr", + "from_timeframe_group_id" : "timeframe_regular", + "id" : 477, + "leg_group_id" : "leg_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : "area_ol_state", + "from_timeframe_group_id" : "timeframe_regular", + "id" : 478, + "leg_group_id" : "leg_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + }, { + "fare_product_id" : "prod_rapid_transit_quick_subway", + "from_area_id" : null, + "from_timeframe_group_id" : "timeframe_regular", + "id" : 479, + "leg_group_id" : "leg_rapid_transit_quick_subway", + "network_id" : "rapid_transit", + "rule_priority" : null, + "to_area_id" : null, + "to_timeframe_group_id" : null + } ], + "to_leg_group_id" : "leg_rapid_transit_quick_subway", + "transfer_count" : null + } ], + "feed_version" : "1.0" + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchNetworks-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchNetworks-0.json new file mode 100644 index 000000000..0b3fa282d --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchNetworks-0.json @@ -0,0 +1,11 @@ +{ + "data" : { + "feed" : { + "feed_version" : "1.0", + "network" : [ { + "network_id" : "1", + "network_name" : "Forbidden because network id is defined in routes" + } ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRouteNetworks-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRouteNetworks-0.json new file mode 100644 index 000000000..a444e1bab --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRouteNetworks-0.json @@ -0,0 +1,11 @@ +{ + "data" : { + "feed" : { + "feed_version" : "1.0", + "route_network" : [ { + "network_id" : "1", + "route_id" : "1" + } ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRoutes-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRoutes-0.json index 1c1a10c26..b308a1582 100644 --- a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRoutes-0.json +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchRoutes-0.json @@ -6,6 +6,7 @@ "agency_id" : "1", "count" : 1, "id" : 2, + "network_id" : null, "pattern_count" : 2, "patterns" : [ { "pattern_id" : "1" diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchStopAreas-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchStopAreas-0.json new file mode 100644 index 000000000..1b2454ee1 --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchStopAreas-0.json @@ -0,0 +1,408 @@ +{ + "data" : { + "feed" : { + "feed_version" : "1.0", + "stop_area" : [ { + "area_id" : "area_route_426_downtown", + "id" : 2, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_downtown", + "id" : 3, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 4, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 5, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 6, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 7, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 8, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 9, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 10, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 11, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 12, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 13, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 14, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 15, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 16, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 17, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 18, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 19, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 20, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 21, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 22, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 23, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 24, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 25, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 26, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 27, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 28, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 29, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 30, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 31, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 32, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 33, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_green_b_west_of_kenmore", + "id" : 34, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_green_b_west_of_kenmore", + "id" : 35, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_green_b_west_of_kenmore", + "id" : 36, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_green_b_west_of_kenmore", + "id" : 37, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_sl_logan_terminal", + "id" : 38, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_sl_logan_terminal", + "id" : 39, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_sl_logan_terminal", + "id" : 40, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_sl_logan_terminal", + "id" : 41, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 42, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 43, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 44, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 45, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 46, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_354_outside_downtown", + "id" : 47, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 48, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 49, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_426_outside_downtown", + "id" : 50, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + }, { + "area_id" : "area_route_450_outside_downtown", + "id" : 51, + "stop_id" : "4u6g", + "stops" : [ { + "stop_id" : "4u6g", + "stop_name" : "Butler Ln" + } ] + } ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchTimeFrames-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchTimeFrames-0.json new file mode 100644 index 000000000..ee6ae00bc --- /dev/null +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canFetchTimeFrames-0.json @@ -0,0 +1,50 @@ +{ + "data" : { + "feed" : { + "feed_version" : "1.0", + "time_frame" : [ { + "end_time" : null, + "id" : 2, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : null, + "timeframe_group_id" : "timeframe_regular" + }, { + "end_time" : null, + "id" : 3, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : null, + "timeframe_group_id" : "timeframe_systemwide_free" + }, { + "end_time" : null, + "id" : 4, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : null, + "timeframe_group_id" : "timeframe_sumner_tunnel_closure" + }, { + "end_time" : "9000", + "id" : 5, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : "0", + "timeframe_group_id" : "timeframe_sumner_tunnel_closure" + }, { + "end_time" : "86400", + "id" : 6, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : "9000", + "timeframe_group_id" : "timeframe_regular" + }, { + "end_time" : null, + "id" : 7, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : null, + "timeframe_group_id" : "timeframe_alewife_kendall_surge" + }, { + "end_time" : "9000", + "id" : 8, + "service_id" : "04100312-8fe1-46a5-a9f2-556f39478f57", + "start_time" : "0", + "timeframe_group_id" : "timeframe_alewife_kendall_surge" + } ] + } + } +} \ No newline at end of file diff --git a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canSanitizeSQLInjectionSentAsKeyValue-0.json b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canSanitizeSQLInjectionSentAsKeyValue-0.json index 04c98ecdc..b541c33a8 100644 --- a/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canSanitizeSQLInjectionSentAsKeyValue-0.json +++ b/src/test/resources/snapshots/com/conveyal/gtfs/graphql/GTFSGraphQLTest/canSanitizeSQLInjectionSentAsKeyValue-0.json @@ -6,6 +6,7 @@ "agency_id" : "1", "count" : 1, "id" : 2, + "network_id" : null, "pattern_count" : 2, "patterns" : [ { "pattern_id" : "1"