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

org.opentripplanner.gtfs.mapping.TransferMapper Maven / Gradle / Ivy

package org.opentripplanner.gtfs.mapping;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import org.onebusaway.gtfs.model.Transfer;
import org.opentripplanner.graph_builder.DataImportIssueStore;
import org.opentripplanner.graph_builder.issues.IgnoredGtfsTransfer;
import org.opentripplanner.graph_builder.issues.InvalidGtfsTransfer;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.model.TripStopTimes;
import org.opentripplanner.model.transfer.ConstrainedTransfer;
import org.opentripplanner.model.transfer.RouteStationTransferPoint;
import org.opentripplanner.model.transfer.RouteStopTransferPoint;
import org.opentripplanner.model.transfer.StationTransferPoint;
import org.opentripplanner.model.transfer.StopTransferPoint;
import org.opentripplanner.model.transfer.TransferConstraint;
import org.opentripplanner.model.transfer.TransferPoint;
import org.opentripplanner.model.transfer.TransferPriority;
import org.opentripplanner.model.transfer.TripTransferPoint;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.Station;
import org.opentripplanner.transit.model.site.StopLocation;
import org.opentripplanner.transit.model.timetable.Trip;

/**
 * Responsible for mapping GTFS Transfer into the OTP model.
 *
 * 

This mapper is stateful and not thread safe. Create a new mapper for every set * of transfers you want to map. */ class TransferMapper { /** * This transfer is recommended over other transfers. The routing algorithm should prefer this * transfer compared to other transfers, for example by assigning a lower weight to it. */ private static final int RECOMMENDED = 0; /** * This means the departing vehicle will wait for the arriving one and leave sufficient time for a * rider to transfer between routes. */ private static final int GUARANTEED = 1; /** * This is a regular transfer that is defined in the transit data (as opposed to OpenStreetMap * data). In the case that both are present, this should take precedence. Because the the duration * of the transfer is given and not the distance, walk speed will have no effect on this. */ private static final int MIN_TIME = 2; /** * Transfers between these stops (and route/trip) is not possible (or not allowed), even if a * transfer is already defined via OpenStreetMap data or in transit data. */ private static final int FORBIDDEN = 3; /** * Passengers can transfer from one trip to another by staying onboard the same vehicle. * * @see GTFS proposal */ private static final int STAY_SEATED = 4; /** * In-seat transfers are not allowed between sequential trips. The passenger must alight from the * vehicle and re-board. * * @see GTFS proposal */ private static final int STAY_SEATED_NOT_ALLOWED = 5; private final RouteMapper routeMapper; private final StationMapper stationMapper; private final StopMapper stopMapper; private final TripMapper tripMapper; private final TripStopTimes stopTimesByTrip; private final DataImportIssueStore issueStore; private final Multimap tripsByRoute = ArrayListMultimap.create(); private final boolean discardMinTransferTimes; TransferMapper( RouteMapper routeMapper, StationMapper stationMapper, StopMapper stopMapper, TripMapper tripMapper, TripStopTimes stopTimesByTrip, boolean discardMinTransferTimes, DataImportIssueStore issueStore ) { this.routeMapper = routeMapper; this.stationMapper = stationMapper; this.stopMapper = stopMapper; this.tripMapper = tripMapper; this.stopTimesByTrip = stopTimesByTrip; this.discardMinTransferTimes = discardMinTransferTimes; this.issueStore = issueStore; } static TransferPriority mapTypeToPriority(int type) { switch (type) { case FORBIDDEN: return TransferPriority.NOT_ALLOWED; case GUARANTEED: case MIN_TIME: case STAY_SEATED: case STAY_SEATED_NOT_ALLOWED: return TransferPriority.ALLOWED; case RECOMMENDED: return TransferPriority.RECOMMENDED; } throw new IllegalArgumentException("Mapping missing for type: " + type); } TransferMappingResult map(Collection allTransfers) { setup(!allTransfers.isEmpty()); List constrainedTransfers = allTransfers .stream() .map(this::map) .filter(Objects::nonNull) .toList(); List staySeatedNotAllowed = allTransfers .stream() .map(this::toStaySeatedNotAllowed) .filter(Objects::nonNull) .toList(); return new TransferMappingResult(constrainedTransfers, staySeatedNotAllowed); } private StaySeatedNotAllowed toStaySeatedNotAllowed(Transfer t) { Trip fromTrip = tripMapper.map(t.getFromTrip()); Trip toTrip = tripMapper.map(t.getToTrip()); if (t.getTransferType() == STAY_SEATED_NOT_ALLOWED) { return new StaySeatedNotAllowed(fromTrip, toTrip); } else return null; } ConstrainedTransfer map(org.onebusaway.gtfs.model.Transfer rhs) { Trip fromTrip = tripMapper.map(rhs.getFromTrip()); Trip toTrip = tripMapper.map(rhs.getToTrip()); TransferConstraint constraint = mapConstraint(rhs); // If this transfer do not give any advantages in the routing, then drop it if (constraint.isRegularTransfer()) { issueStore.add(new IgnoredGtfsTransfer(rhs)); return null; } if (constraint.isStaySeated() && (fromTrip == null || toTrip == null)) { issueStore.add( new InvalidGtfsTransfer("from_trip_id and to_trip_id must exist for in-seat transfer", rhs) ); return null; } TransferPoint fromPoint = mapTransferPoint( rhs.getFromStop(), rhs.getFromRoute(), fromTrip, false ); TransferPoint toPoint = mapTransferPoint(rhs.getToStop(), rhs.getToRoute(), toTrip, true); if (fromPoint == null || toPoint == null) { issueStore.add(new InvalidGtfsTransfer("fromPoint / toPoint doesn't exist", rhs)); return null; } return new ConstrainedTransfer(null, fromPoint, toPoint, constraint); } private void setup(boolean run) { if (!run) { return; } for (Trip trip : tripMapper.getMappedTrips()) { tripsByRoute.put(trip.getRoute(), trip); } } private TransferConstraint mapConstraint(Transfer rhs) { var builder = TransferConstraint.create(); builder.guaranteed(rhs.getTransferType() == GUARANTEED); // A transfer is stay seated, if it is either explicitly mapped as such, or in the same block // and not explicitly disallowed. builder.staySeated(rhs.getTransferType() == STAY_SEATED); builder.priority(mapTypeToPriority(rhs.getTransferType())); if (!discardMinTransferTimes && rhs.isMinTransferTimeSet()) { builder.minTransferTime(rhs.getMinTransferTime()); } return builder.build(); } private TransferPoint mapTransferPoint( org.onebusaway.gtfs.model.Stop rhsStopOrStation, org.onebusaway.gtfs.model.Route rhsRoute, Trip trip, boolean boardTrip ) { Route route = routeMapper.map(rhsRoute); Station station = null; RegularStop stop = null; // A transfer is specified using Stops and/or Station, according to the GTFS specification: // // If the stop ID refers to a station that contains multiple stops, this transfer rule // applies to all stops in that station. // // Source: https://developers.google.com/transit/gtfs/reference/transfers-file if (rhsStopOrStation.getLocationType() == 0) { stop = stopMapper.map(rhsStopOrStation); } else { station = stationMapper.map(rhsStopOrStation); } if (trip != null) { // A trip may visit the same stop twice, but we ignore that and only add the first stop // we find. Pattern that start and end at the same stop is supported. int stopPositionInPattern = stopPosition(trip, stop, station, boardTrip); return stopPositionInPattern < 0 ? null : new TripTransferPoint(trip, stopPositionInPattern); } else if (route != null) { if (stop != null) { return new RouteStopTransferPoint(route, stop); } else if (station != null) { return new RouteStationTransferPoint(route, station); } } else if (stop != null) { return new StopTransferPoint(stop); } else if (station != null) { return new StationTransferPoint(station); } throw new IllegalStateException("Should not get here!"); } private int stopPosition(Trip trip, RegularStop stop, Station station, boolean boardTrip) { List stopTimes = stopTimesByTrip.get(trip); // We can board at the first stop, but not alight. final int firstStopPos = boardTrip ? 0 : 1; // We can alight at the last stop, but not board, the lastStopPos is exclusive final int lastStopPos = stopTimes.size() - (boardTrip ? 1 : 0); Predicate stopMatches = station != null ? s -> (s instanceof RegularStop && ((RegularStop) s).getParentStation() == station) : s -> s == stop; for (int i = firstStopPos; i < lastStopPos; i++) { StopTime stopTime = stopTimes.get(i); if (boardTrip && stopTime.getPickupType().isNotRoutable()) { continue; } if (!boardTrip && stopTime.getDropOffType().isNotRoutable()) { continue; } if (stopMatches.test(stopTime.getStop())) { return i; } } return -1; } private boolean sameBlockId(Trip a, Trip b) { if (a == null || b == null) { return false; } return a.getGtfsBlockId() != null && a.getGtfsBlockId().equals(b.getGtfsBlockId()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy