/* *********************************************************************** * * project: org.matsim.* * *********************************************************************** * * * * copyright : (C) 2016 by the members listed in the COPYING, * * LICENSE and WARRANTY file. * * email : info at matsim dot org * * * * *********************************************************************** * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ package org.matsim.pt2matsim.gtfs; import org.apache.log4j.Logger; import org.matsim.api.core.v01.Id; import org.matsim.core.utils.misc.Time; import org.matsim.pt.transitSchedule.api.*; import org.matsim.pt2matsim.gtfs.lib.*; import org.matsim.pt2matsim.tools.GtfsTools; import org.matsim.pt2matsim.tools.ScheduleTools; import org.matsim.pt2matsim.tools.debug.ScheduleCleaner; import org.matsim.pt2matsim.tools.lib.RouteShape; import org.matsim.vehicles.*; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Converts a GTFS feed to a MATSim transit schedule * * @author polettif */ public class GtfsConverter { protected final boolean AWAIT_DEPARTURE_TIME_DEFAULT = true; protected final boolean BLOCKS_DEFAULT = false; public static final String ALL_SERVICE_IDS = "all"; public static final String DAY_WITH_MOST_TRIPS = "dayWithMostTrips"; public static final String DAY_WITH_MOST_SERVICES = "dayWithMostServices"; protected static Logger log = Logger.getLogger(GtfsConverter.class); protected final GtfsFeed feed; protected final TransitScheduleFactory scheduleFactory = ScheduleTools.createSchedule().getFactory(); protected TransitSchedule transitSchedule; protected Vehicles vehiclesContainer; protected int noStopTimeTrips; public GtfsConverter(GtfsFeed gtfsFeed) { this.feed = gtfsFeed; } /** * @return the converted schedule (field, see {@link #getSchedule()}} */ public TransitSchedule convert(String serviceIdsParam, String outputCoordinateSystem) { convert(serviceIdsParam, outputCoordinateSystem, ScheduleTools.createSchedule(), VehicleUtils.createVehiclesContainer()); return getSchedule(); } public TransitSchedule getSchedule() { return this.transitSchedule; } public Vehicles getVehicles() { return this.vehiclesContainer; } /** * Converts the loaded gtfs data to the given matsim transit schedule * <ol> * <li>generate transitStopFacilities from gtfsStops</li> * <li>Create a transitLine for each Route</li> * <li>Generate a transitRoute for each trip</li> * <li>Get the stop sequence of the trip</li> * <li>Calculate departures from stopTimes or frequencies</li> * <li>add transitRoute to the transitLine and thus to the schedule</li> * </ol> */ public void convert(String serviceIdsParam, String transformation, TransitSchedule schedule, Vehicles vehicles) { log.info("#####################################"); log.info("Converting to MATSim transit schedule"); // transform feed this.feed.transform(transformation); // get sample date LocalDate extractDate = getExtractDate(serviceIdsParam); if(extractDate != null) log.info(" Extracting schedule from date " + extractDate); // generate TransitStopFacilities from gtfsStops and add them to the schedule createStopFacilities(schedule); // create transfers createTransfers(schedule); // Creating TransitLines from routes and TransitRoutes from trips createTransitLines(schedule, extractDate); // combine TransitRoutes with identical stop/time sequences, add departures combineTransitRoutes(schedule); // clean the schedule cleanSchedule(schedule); // create default vehicles createVehicles(schedule, vehicles); // statistics int counterLines = 0; int counterRoutes = 0; for(TransitLine transitLine : schedule.getTransitLines().values()) { counterLines++; counterRoutes += transitLine.getRoutes().size(); } log.info(" Created " + counterRoutes + " routes on " + counterLines + " lines."); if(extractDate != null) log.info(" Day " + extractDate); log.info("... GTFS converted to an unmapped MATSIM Transit Schedule"); log.info("#########################################################"); this.transitSchedule = schedule; this.vehiclesContainer = vehicles; } protected void createStopFacilities(TransitSchedule schedule) { for(Stop stop : this.feed.getStops().values()) { TransitStopFacility stopFacility = createStopFacility(stop); if(stopFacility != null) { schedule.addStopFacility(stopFacility); } } } protected void createTransfers(TransitSchedule schedule) { MinimalTransferTimes minimalTransferTimes = schedule.getMinimalTransferTimes(); for(Transfer transfer : feed.getTransfers()) { if(!transfer.getTransferType().equals(GtfsDefinitions.TransferType.TRANSFER_NOT_POSSIBLE)) { Id<TransitStopFacility> fromStop = Id.create(transfer.getFromStopId(), TransitStopFacility.class); Id<TransitStopFacility> toStop = Id.create(transfer.getToStopId(), TransitStopFacility.class); // Note: Timed transfer points (type 1) cannot be represented with minimalTransferTimes only double minTransferTime = 0; if(transfer.getTransferType().equals(GtfsDefinitions.TransferType.REQUIRES_MIN_TRANSFER_TIME)) { minTransferTime = transfer.getMinTransferTime(); } minimalTransferTimes.set(fromStop, toStop, minTransferTime); } } } /** * @return null if stop should not be converted */ protected TransitStopFacility createStopFacility(Stop stop) { Id<TransitStopFacility> id = createStopFacilityId(stop); TransitStopFacility stopFacility = this.scheduleFactory.createTransitStopFacility(id, stop.getCoord(), BLOCKS_DEFAULT); stopFacility.setName(stop.getName()); if(stop.getParentStationId() != null) { stopFacility.setStopAreaId(Id.create(stop.getParentStationId(), TransitStopArea.class)); } return stopFacility; } protected void createTransitLines(TransitSchedule schedule, LocalDate extractDate) { // info log.info(" Creating TransitLines from routes and TransitRoutes from trips..."); for(Route gtfsRoute : this.feed.getRoutes().values()) { // create a MATSim TransitLine for each Route TransitLine newTransitLine = createTransitLine(gtfsRoute); if(newTransitLine != null) { schedule.addTransitLine(newTransitLine); // create TransitRoute for each trip for(Trip trip : gtfsRoute.getTrips().values()) { // check if the trip actually runs on the extract date if(trip.getService().runsOnDate(extractDate)) { TransitRoute transitRoute = createTransitRoute(trip, schedule.getFacilities()); if(transitRoute != null) { newTransitLine.addRoute(transitRoute); } } } } } if(noStopTimeTrips > 0) { log.warn(noStopTimeTrips + " trips without stop times were not converted"); } } /** * @return null if route should not be converted */ protected TransitLine createTransitLine(Route gtfsRoute) { Id<TransitLine> id = createTransitLineId(gtfsRoute); TransitLine line = this.scheduleFactory.createTransitLine(id); line.setName(gtfsRoute.getShortName()); return line; } /** * @return null if route should not be converted */ protected TransitRoute createTransitRoute(Trip trip, Map<Id<TransitStopFacility>, TransitStopFacility> stopFacilities) { Id<RouteShape> shapeId = trip.getShape() != null ? trip.getShape().getId() : null; if(trip.getStopTimes().size() == 0) { noStopTimeTrips++; return null; } // Get the stop sequence (with arrivalOffset and departureOffset) of the trip. List<TransitRouteStop> transitRouteStops = new ArrayList<>(); // create transit route stops for(StopTime stopTime : trip.getStopTimes()) { TransitRouteStop newTransitRouteStop = createTransitRouteStop(stopTime, trip, stopFacilities); transitRouteStops.add(newTransitRouteStop); } // Calculate departures from frequencies (if available) TransitRoute transitRoute; if(trip.getFrequencies().size() > 0) { transitRoute = this.scheduleFactory.createTransitRoute(createTransitRouteId(trip), null, transitRouteStops, trip.getRoute().getRouteType().name); for(Frequency frequency : trip.getFrequencies()) { for(int t = frequency.getStartTime(); t < frequency.getEndTime(); t += frequency.getHeadWaySecs()) { Departure newDeparture = this.scheduleFactory.createDeparture(createDepartureId(transitRoute, t), t); transitRoute.addDeparture(newDeparture); } } return transitRoute; } else { // Calculate departures from stopTimes int routeStartTime = trip.getStopTimes().first().getDepartureTime(); transitRoute = this.scheduleFactory.createTransitRoute(createTransitRouteId(trip), null, transitRouteStops, trip.getRoute().getRouteType().name); Departure newDeparture = this.scheduleFactory.createDeparture(createDepartureId(transitRoute, routeStartTime), routeStartTime); transitRoute.addDeparture(newDeparture); if(shapeId != null) ScheduleTools.setShapeId(transitRoute, trip.getShape().getId()); return transitRoute; } } protected TransitRouteStop createTransitRouteStop(StopTime stopTime, Trip trip, Map<Id<TransitStopFacility>, TransitStopFacility> stopFacilities) { double arrivalOffset = Time.UNDEFINED_TIME, departureOffset = Time.UNDEFINED_TIME; int routeStartTime = trip.getStopTimes().first().getArrivalTime(); int firstSequencePos = trip.getStopTimes().first().getSequencePosition(); int lastSequencePos = trip.getStopTimes().last().getSequencePosition(); // add arrivalOffset time if current stopTime is not on the first stop of the route if(!stopTime.getSequencePosition().equals(firstSequencePos)) { arrivalOffset = stopTime.getArrivalTime() - routeStartTime; } // add departure time if current stopTime is not on the last stop of the route if(!stopTime.getSequencePosition().equals(lastSequencePos)) { departureOffset = stopTime.getArrivalTime() - routeStartTime; } TransitStopFacility stopFacility = stopFacilities.get(createStopFacilityId(stopTime.getStop())); TransitRouteStop newTransitRouteStop = this.scheduleFactory.createTransitRouteStop(stopFacility, arrivalOffset, departureOffset); newTransitRouteStop.setAwaitDepartureTime(AWAIT_DEPARTURE_TIME_DEFAULT); return newTransitRouteStop; } protected void combineTransitRoutes(TransitSchedule schedule) { ScheduleCleaner.combineIdenticalTransitRoutes(schedule); } protected void cleanSchedule(TransitSchedule schedule) { ScheduleCleaner.removeNotUsedStopFacilities(schedule); ScheduleCleaner.removeNotUsedMinimalTransferTimes(schedule); } protected Id<TransitLine> createTransitLineId(Route gtfsRoute) { String id = gtfsRoute.getId(); return Id.create(id, TransitLine.class); } protected Id<TransitRoute> createTransitRouteId(Trip trip) { return Id.create(trip.getId(), TransitRoute.class); } protected Id<TransitStopFacility> createStopFacilityId(Stop stop) { return Id.create(stop.getId(), TransitStopFacility.class); } protected Id<Departure> createDepartureId(TransitRoute route, int time) { String str = route.getId().toString() + "_" + Time.writeTime(time, "HH:mm:ss"); return Id.create(str, Departure.class); } protected void createVehicles(TransitSchedule schedule, Vehicles vehicles) { VehiclesFactory vf = vehicles.getFactory(); Map<GtfsDefinitions.ExtendedRouteType, VehicleType> vehicleTypes = new HashMap<>(); long vehId = 0; for(TransitLine line : schedule.getTransitLines().values()) { // get extended route type Route gtfsRoute = feed.getRoutes().get(line.getId().toString()); GtfsDefinitions.ExtendedRouteType extType = gtfsRoute.getExtendedRouteType(); // create vehicle type for each extended route type if(!vehicleTypes.containsKey(extType)) { VehicleType defaultVehicleType = ScheduleTools.createDefaultVehicleType(extType.name, extType.routeType.name); vehicles.addVehicleType(defaultVehicleType); vehicleTypes.put(extType, defaultVehicleType); } VehicleType vehicleType = vehicleTypes.get(extType); for(TransitRoute route : line.getRoutes().values()) { // create a vehicle for each departure for(Departure departure : route.getDepartures().values()) { String vehicleId = "veh_" + Long.toString(vehId++) + "_" + route.getTransportMode().replace(" ", "_"); Vehicle veh = vf.createVehicle(Id.create(vehicleId, Vehicle.class), vehicleType); vehicles.addVehicle(veh); departure.setVehicleId(veh.getId()); } } } } /** * @return The date from which services and thus trips should be extracted */ protected LocalDate getExtractDate(String param) { switch(param) { case ALL_SERVICE_IDS: { log.warn(" Using all trips is not recommended"); log.info("... Using all service IDs"); return null; } case DAY_WITH_MOST_SERVICES: { log.info(" Using service IDs of the day with the most services (" + DAY_WITH_MOST_SERVICES + ")."); return GtfsTools.getDayWithMostServices(feed); } case DAY_WITH_MOST_TRIPS: { log.info(" Using service IDs of the day with the most trips (" + DAY_WITH_MOST_TRIPS + ")."); return GtfsTools.getDayWithMostTrips(feed); } default: { LocalDate date; try { date = LocalDate.of(Integer.parseInt(param.substring(0, 4)), Integer.parseInt(param.substring(4, 6)), Integer.parseInt(param.substring(6, 8))); } catch (NumberFormatException e) { throw new IllegalArgumentException("Extract param not recognized"); } return date; } } } }