From 6273663e31d3602acd6b628d4732f1693114c6e7 Mon Sep 17 00:00:00 2001 From: Merlin Unterfinger Date: Fri, 3 May 2024 16:15:04 +0200 Subject: [PATCH] ENH: NAV-14 - Add support for extended route types - Introduce interface for route type. - Implement with two enums: DefaultRouteType and HierarchicalVehicleType - Parser decides depending on code which type to choose. --- .../gtfs/schedule/GtfsScheduleParser.java | 4 +- .../gtfs/schedule/type/DefaultRouteType.java | 38 ++++++ .../type/HierarchicalVehicleType.java | 117 ++++++++++++++++++ .../gtfs/schedule/type/RouteType.java | 66 +++++----- .../naviqore/raptor/model/RouteTraversal.java | 1 - .../gtfs/schedule/model/GtfsScheduleTest.java | 8 +- 6 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 src/main/java/ch/naviqore/gtfs/schedule/type/DefaultRouteType.java create mode 100644 src/main/java/ch/naviqore/gtfs/schedule/type/HierarchicalVehicleType.java diff --git a/src/main/java/ch/naviqore/gtfs/schedule/GtfsScheduleParser.java b/src/main/java/ch/naviqore/gtfs/schedule/GtfsScheduleParser.java index 16610e5a..e3ff25cd 100644 --- a/src/main/java/ch/naviqore/gtfs/schedule/GtfsScheduleParser.java +++ b/src/main/java/ch/naviqore/gtfs/schedule/GtfsScheduleParser.java @@ -85,10 +85,8 @@ private void parseStop(CSVRecord record) { } private void parseRoute(CSVRecord record) { - // TODO: Route types are not standardized in any way. - // RouteType.parse(record.get("route_type")) builder.addRoute(record.get("route_id"), record.get("agency_id"), record.get("route_short_name"), - record.get("route_long_name"), RouteType.RAIL); + record.get("route_long_name"), RouteType.parse(record.get("route_type"))); } private void parseTrips(CSVRecord record) { diff --git a/src/main/java/ch/naviqore/gtfs/schedule/type/DefaultRouteType.java b/src/main/java/ch/naviqore/gtfs/schedule/type/DefaultRouteType.java new file mode 100644 index 00000000..a97d1157 --- /dev/null +++ b/src/main/java/ch/naviqore/gtfs/schedule/type/DefaultRouteType.java @@ -0,0 +1,38 @@ +package ch.naviqore.gtfs.schedule.type; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum DefaultRouteType implements RouteType { + TRAM(0, "Tram, Streetcar, Light rail. Any light rail or street level system within a metropolitan area."), + SUBWAY(1, "Subway, Metro. Any underground rail system within a metropolitan area."), + RAIL(2, "Rail. Used for intercity or long-distance travel."), + BUS(3, "Bus. Used for short- and long-distance bus routes."), + FERRY(4, "Ferry. Used for short- and long-distance boat service."), + CABLE_TRAM(5, + "Cable tram. Used for street-level rail cars where the cable runs beneath the vehicle (e.g., cable car in San Francisco)."), + AERIAL_LIFT(6, + "Aerial lift, suspended cable car (e.g., gondola lift, aerial tramway). Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables."), + FUNICULAR(7, "Funicular. Any rail system designed for steep inclines."), + TROLLEYBUS(11, "Trolleybus. Electric buses that draw power from overhead wires using poles."), + MONORAIL(12, "Monorail. Railway in which the track consists of a single rail or a beam."); + + private final int code; + private final String description; + + public static DefaultRouteType parse(String code) { + return parse(Integer.parseInt(code)); + } + + public static DefaultRouteType parse(int code) { + for (DefaultRouteType type : DefaultRouteType.values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("No default route type with code " + code + " found"); + } +} diff --git a/src/main/java/ch/naviqore/gtfs/schedule/type/HierarchicalVehicleType.java b/src/main/java/ch/naviqore/gtfs/schedule/type/HierarchicalVehicleType.java new file mode 100644 index 00000000..648dae47 --- /dev/null +++ b/src/main/java/ch/naviqore/gtfs/schedule/type/HierarchicalVehicleType.java @@ -0,0 +1,117 @@ +package ch.naviqore.gtfs.schedule.type; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public enum HierarchicalVehicleType implements RouteType { + RAILWAY_SERVICE(100, "Railway Service", true), + HIGH_SPEED_RAIL_SERVICE(101, "High Speed Rail Service", true), + LONG_DISTANCE_TRAINS(102, "Long Distance Trains", true), + INTER_REGIONAL_RAIL_SERVICE(103, "Inter Regional Rail Service", true), + CAR_TRANSPORT_RAIL_SERVICE(104, "Car Transport Rail Service", false), + SLEEPER_RAIL_SERVICE(105, "Sleeper Rail Service", true), + REGIONAL_RAIL_SERVICE(106, "Regional Rail Service", true), + TOURIST_RAILWAY_SERVICE(107, "Tourist Railway Service", true), + RAIL_SHUTTLE_WITHIN_COMPLEX(108, "Rail Shuttle (Within Complex)", true), + SUBURBAN_RAILWAY(109, "Suburban Railway", true), + REPLACEMENT_RAIL_SERVICE(110, "Replacement Rail Service", false), + SPECIAL_RAIL_SERVICE(111, "Special Rail Service", false), + LORRY_TRANSPORT_RAIL_SERVICE(112, "Lorry Transport Rail Service", false), + ALL_RAIL_SERVICES(113, "All Rail Services", false), + CROSS_COUNTRY_RAIL_SERVICE(114, "Cross-Country Rail Service", false), + VEHICLE_TRANSPORT_RAIL_SERVICE(115, "Vehicle Transport Rail Service", false), + RACK_AND_PINION_RAILWAY(116, "Rack and Pinion Railway", false), + ADDITIONAL_RAIL_SERVICE(117, "Additional Rail Service", false), + + COACH_SERVICE(200, "Coach Service", true), + INTERNATIONAL_COACH_SERVICE(201, "International Coach Service", true), + NATIONAL_COACH_SERVICE(202, "National Coach Service", true), + SHUTTLE_COACH_SERVICE(203, "Shuttle Coach Service", false), + REGIONAL_COACH_SERVICE(204, "Regional Coach Service", true), + SPECIAL_COACH_SERVICE(205, "Special Coach Service", false), + SIGHTSEEING_COACH_SERVICE(206, "Sightseeing Coach Service", false), + TOURIST_COACH_SERVICE(207, "Tourist Coach Service", false), + COMMUTER_COACH_SERVICE(208, "Commuter Coach Service", false), + ALL_COACH_SERVICES(209, "All Coach Services", false), + + URBAN_RAILWAY_SERVICE(400, "Urban Railway Service", true), + METRO_SERVICE(401, "Metro Service", true), + UNDERGROUND_SERVICE(402, "Underground Service", true), + ALL_URBAN_RAILWAY_SERVICES(404, "All Urban Railway Services", false), + MONORAIL(405, "Monorail", true), + + BUS_SERVICE(700, "Bus Service", true), + REGIONAL_BUS_SERVICE(701, "Regional Bus Service", true), + EXPRESS_BUS_SERVICE(702, "Express Bus Service", true), + LOCAL_BUS_SERVICE(704, "Local Bus Service", true), + NIGHT_BUS_SERVICE(705, "Night Bus Service", false), + POST_BUS_SERVICE(706, "Post Bus Service", false), + SPECIAL_NEEDS_BUS(707, "Special Needs Bus", false), + MOBILITY_BUS_SERVICE(708, "Mobility Bus Service", false), + MOBILITY_BUS_FOR_REGISTERED_DISABLED(709, "Mobility Bus for Registered Disabled", false), + SIGHTSEEING_BUS(710, "Sightseeing Bus", false), + SHUTTLE_BUS(711, "Shuttle Bus", false), + SCHOOL_BUS(712, "School Bus", false), + SCHOOL_AND_PUBLIC_SERVICE_BUS(713, "School and Public Service Bus", false), + RAIL_REPLACEMENT_BUS_SERVICE(714, "Rail Replacement Bus Service", false), + DEMAND_AND_RESPONSE_BUS_SERVICE(715, "Demand and Response Bus Service", true), + ALL_BUS_SERVICES(716, "All Bus Services", false), + + TROLLEYBUS_SERVICE(800, "Trolleybus Service", true), + + TRAM_SERVICE(900, "Tram Service", true), + CITY_TRAM_SERVICE(901, "City Tram Service", false), + LOCAL_TRAM_SERVICE(902, "Local Tram Service", false), + REGIONAL_TRAM_SERVICE(903, "Regional Tram Service", false), + SIGHTSEEING_TRAM_SERVICE(904, "Sightseeing Tram Service", false), + SHUTTLE_TRAM_SERVICE(905, "Shuttle Tram Service", false), + ALL_TRAM_SERVICES(906, "All Tram Services", false), + + WATER_TRANSPORT_SERVICE(1000, "Water Transport Service", true), + AIR_SERVICE(1100, "Air Service", false), + + FERRY_SERVICE(1200, "Ferry Service", true), + + AERIAL_LIFT_SERVICE(1300, "Aerial Lift Service", true), + TELECABIN_SERVICE(1301, "Telecabin Service", true), + CABLE_CAR_SERVICE(1302, "Cable Car Service", false), + ELEVATOR_SERVICE(1303, "Elevator Service", false), + CHAIR_LIFT_SERVICE(1304, "Chair Lift Service", false), + DRAG_LIFT_SERVICE(1305, "Drag Lift Service", false), + SMALL_TELECABIN_SERVICE(1306, "Small Telecabin Service", false), + ALL_TELECABIN_SERVICES(1307, "All Telecabin Services", false), + + FUNICULAR_SERVICE(1400, "Funicular Service", true), + + TAXI_SERVICE(1500, "Taxi Service", false), + COMMUNAL_TAXI_SERVICE(1501, "Communal Taxi Service", true), + WATER_TAXI_SERVICE(1502, "Water Taxi Service", false), + RAIL_TAXI_SERVICE(1503, "Rail Taxi Service", false), + BIKE_TAXI_SERVICE(1504, "Bike Taxi Service", false), + LICENSED_TAXI_SERVICE(1505, "Licensed Taxi Service", false), + PRIVATE_HIRE_SERVICE_VEHICLE(1506, "Private Hire Service Vehicle", false), + ALL_TAXI_SERVICES(1507, "All Taxi Services", false), + + MISCELLANEOUS_SERVICE(1700, "Miscellaneous Service", true), + HORSE_DRAWN_CARRIAGE(1702, "Horse-drawn Carriage", true); + + private final int code; + private final String description; + private final boolean supported; + + public static HierarchicalVehicleType parse(String code) { + return parse(Integer.parseInt(code)); + } + + public static HierarchicalVehicleType parse(int code) { + for (HierarchicalVehicleType type : HierarchicalVehicleType.values()) { + if (type.code == code) { + return type; + } + } + throw new IllegalArgumentException("No hierarchical vehicle type with code " + code + " found"); + } +} diff --git a/src/main/java/ch/naviqore/gtfs/schedule/type/RouteType.java b/src/main/java/ch/naviqore/gtfs/schedule/type/RouteType.java index 35432d82..043fe6ee 100644 --- a/src/main/java/ch/naviqore/gtfs/schedule/type/RouteType.java +++ b/src/main/java/ch/naviqore/gtfs/schedule/type/RouteType.java @@ -1,38 +1,46 @@ package ch.naviqore.gtfs.schedule.type; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.RequiredArgsConstructor; +/** + * Provides a unified approach to handling different modes of transportation of routes within a GTFS feed. Implementing + * this interface allows for retrieval of both unique identifier codes and descriptions of transportation route types. + * + * @author munterfi + */ +public interface RouteType { -@RequiredArgsConstructor(access = AccessLevel.PRIVATE) -@Getter -public enum RouteType { - TRAM(0, "Tram, Streetcar, Light rail. Any light rail or street level system within a metropolitan area."), - SUBWAY(1, "Subway, Metro. Any underground rail system within a metropolitan area."), - RAIL(2, "Rail. Used for intercity or long-distance travel."), - BUS(3, "Bus. Used for short- and long-distance bus routes."), - FERRY(4, "Ferry. Used for short- and long-distance boat service."), - CABLE_TRAM(5, - "Cable tram. Used for street-level rail cars where the cable runs beneath the vehicle (e.g., cable car in San Francisco)."), - AERIAL_LIFT(6, - "Aerial lift, suspended cable car (e.g., gondola lift, aerial tramway). Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables."), - FUNICULAR(7, "Funicular. Any rail system designed for steep inclines."), - TROLLEYBUS(11, "Trolleybus. Electric buses that draw power from overhead wires using poles."), - MONORAIL(12, "Monorail. Railway in which the track consists of a single rail or a beam."); + /** + * Parses a string to the corresponding RouteType: Either default GTFS route type or Hierarchical Vehicle Type + * (HVT). + * + * @param code the string code to parse + * @return the corresponding RouteType + * @throws NumberFormatException if the code is not a valid integer + * @throws IllegalArgumentException if the code is negative or invalid + */ - private final int value; - private final String description; - - public static RouteType parse(String value) { - return parse(Integer.parseInt(value)); + static RouteType parse(String code) { + return parse(Integer.parseInt(code)); } - public static RouteType parse(int value) { - for (RouteType type : RouteType.values()) { - if (type.value == value) { - return type; - } + static RouteType parse(int code) { + if (code < 0) { + throw new IllegalArgumentException("Invalid negative RouteType code: " + code); + } + if (code <= 12) { + return DefaultRouteType.parse(code); + } else { + return HierarchicalVehicleType.parse(code); } - throw new IllegalArgumentException("No route type with value " + value + " found"); } + + /** + * Retrieves the code associated with the route type. + */ + int getCode(); + + /** + * Retrieves a description of the route type. + */ + String getDescription(); + } diff --git a/src/main/java/ch/naviqore/raptor/model/RouteTraversal.java b/src/main/java/ch/naviqore/raptor/model/RouteTraversal.java index adfcb9ca..3b2c3935 100644 --- a/src/main/java/ch/naviqore/raptor/model/RouteTraversal.java +++ b/src/main/java/ch/naviqore/raptor/model/RouteTraversal.java @@ -8,5 +8,4 @@ * @param routeStops route stops */ public record RouteTraversal(StopTime[] stopTimes, Route[] routes, RouteStop[] routeStops) { - } diff --git a/src/test/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleTest.java b/src/test/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleTest.java index 3791dcf0..fa60c1b9 100644 --- a/src/test/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleTest.java +++ b/src/test/java/ch/naviqore/gtfs/schedule/model/GtfsScheduleTest.java @@ -1,6 +1,6 @@ package ch.naviqore.gtfs.schedule.model; -import ch.naviqore.gtfs.schedule.type.RouteType; +import ch.naviqore.gtfs.schedule.type.DefaultRouteType; import ch.naviqore.gtfs.schedule.type.ServiceDayTime; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -34,9 +34,9 @@ void setUp() { .addStop("stop3", "Hill Valley", 47.3780, 8.5390) .addStop("stop4", "East Side", 47.3785, 8.5350) .addStop("stop5", "West End", 47.3750, 8.5300) - .addRoute("route1", "agency1", "101", "Main Line", RouteType.BUS) - .addRoute("route2", "agency1", "102", "Cross Town", RouteType.BUS) - .addRoute("route3", "agency1", "103", "Circulator", RouteType.BUS) + .addRoute("route1", "agency1", "101", "Main Line", DefaultRouteType.BUS) + .addRoute("route2", "agency1", "102", "Cross Town", DefaultRouteType.BUS) + .addRoute("route3", "agency1", "103", "Circulator", DefaultRouteType.BUS) .addCalendar("weekdays", EnumSet.range(DayOfWeek.MONDAY, DayOfWeek.FRIDAY), START_DATE, END_DATE) .addCalendar("weekends", EnumSet.of(DayOfWeek.SATURDAY), START_DATE, END_DATE) .addTrip("trip1", "route1", "weekdays")