/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (props, at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.routing.core; import static com.google.common.base.Preconditions.checkNotNull; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.Map.Entry; import org.onebusaway.gtfs.model.AgencyAndId; import org.onebusaway.gtfs.model.Route; import org.onebusaway.gtfs.model.Stop; import org.onebusaway.gtfs.model.Trip; import org.opentripplanner.common.model.P2; /** * This class represents all transfer information in the graph. Transfers are grouped * by stop-to-stop pairs. Each transfer may consist of multiple specific transfers. * See https://developers.google.com/transit/gtfs/reference#transfers_fields * and https://support.google.com/transitpartners/answer/2450962 (heading Route-to-route * and trip-to-trip transfers) for more details about the specifications. * @see StopTransfer, SpecificTransfer */ public class TransferTable implements Serializable { private static final long serialVersionUID = 9160765220742241406L; /** * Table which contains transfers between two stops */ protected HashMap<P2<AgencyAndId>, StopTransfer> table = new HashMap<P2<AgencyAndId>, StopTransfer>(); /** * Preferred transfers (or timed transfers, which are preferred as well) are present if true */ protected boolean preferredTransfers = false; public boolean hasPreferredTransfers() { return preferredTransfers; } /** * Get the transfer time that should be used when transferring from a trip to another trip. * Note that this function does not check whether another specific transfer exists with the * same specificity, what is forbidden by the specifications. * @param fromStop is the arriving stop * @param toStop is the departing stop * @param fromTrip is the arriving trip * @param toTrip is the departing trip * @param forwardInTime is true when moving forward in time; false when moving * backwards in time (usually this will be the variable "boarding") * @return the transfer time in seconds. May contain special (negative) values which meaning * can be found in the StopTransfer.*_TRANSFER constants. If no transfer is found, * StopTransfer.UNKNOWN_TRANSFER is returned. */ public int getTransferTime(Stop fromStop, Stop toStop, Trip fromTrip, Trip toTrip, boolean forwardInTime) { checkNotNull(fromStop); checkNotNull(toStop); // Reverse from and to if we are moving backwards in time if (!forwardInTime) { Stop tempStop = fromStop; fromStop = toStop; toStop = tempStop; Trip tempTrip = fromTrip; fromTrip = toTrip; toTrip = tempTrip; } // Get transfer time between the two stops int transferTime = getTransferTime(fromStop.getId(), toStop.getId(), fromTrip, toTrip); // Check parents of stops if no transfer was found if (transferTime == StopTransfer.UNKNOWN_TRANSFER) { // Find parent ids AgencyAndId fromStopParentId = null; AgencyAndId toStopParentId = null; if (fromStop.getParentStation() != null && !fromStop.getParentStation().isEmpty()) { // From stop has a parent fromStopParentId = new AgencyAndId(fromStop.getId().getAgencyId(), fromStop.getParentStation()); } if (toStop.getParentStation() != null && !toStop.getParentStation().isEmpty()) { // To stop has a parent toStopParentId = new AgencyAndId(toStop.getId().getAgencyId(), toStop.getParentStation()); } // Check parent of from stop if no transfer was found if (fromStopParentId != null) { transferTime = getTransferTime(fromStopParentId, toStop.getId(), fromTrip, toTrip); } // Check parent of to stop if still no transfer was found if (transferTime == StopTransfer.UNKNOWN_TRANSFER && toStopParentId != null) { transferTime = getTransferTime(fromStop.getId(), toStopParentId, fromTrip, toTrip); } // Check parents of both stops if still no transfer was found if (transferTime == StopTransfer.UNKNOWN_TRANSFER && fromStopParentId != null && toStopParentId != null) { transferTime = getTransferTime(fromStopParentId, toStopParentId, fromTrip, toTrip); } } return transferTime; } /** * Get the transfer time that should be used when transferring from a trip to another trip. * Note that this function does not check whether another specific transfer exists with the * same specificity, what is forbidden by the specifications. * @param fromStopId is the id of the arriving stop * @param toStopId is the id of the departing stop * @param fromTrip is the arriving trip * @param toTrip is the departing trip * @return the transfer time in seconds. May contain special (negative) values which meaning * can be found in the StopTransfer.*_TRANSFER constants. If no transfer is found, * StopTransfer.UNKNOWN_TRANSFER is returned. */ private int getTransferTime(AgencyAndId fromStopId, AgencyAndId toStopId, Trip fromTrip, Trip toTrip) { checkNotNull(fromStopId); checkNotNull(toStopId); // Define transfer time to return int transferTime = StopTransfer.UNKNOWN_TRANSFER; // Lookup transfer between two stops StopTransfer stopTransfer = table.get(new P2<AgencyAndId>(fromStopId, toStopId)); if (stopTransfer != null) { // Lookup correct transfer time between two stops and two trips transferTime = stopTransfer.getTransferTime(fromTrip, toTrip); } return transferTime; } /** * Add a transfer time to the transfer table. * @param fromStop is the arriving stop * @param toStop is the departing stop * @param fromRoute is the arriving route; is allowed to be null * @param toRoute is the departing route; is allowed to be null * @param fromTrip is the arriving trip; is allowed to be null * @param toTrip is the departing trip; is allowed to be null * @param transferTime is the transfer time in seconds. May contain special (negative) values * which meaning can be found in the StopTransfer.*_TRANSFER constants. If no transfer is found, * StopTransfer.UNKNOWN_TRANSFER is returned. */ public void addTransferTime(Stop fromStop, Stop toStop, Route fromRoute, Route toRoute, Trip fromTrip, Trip toTrip, int transferTime) { checkNotNull(fromStop); checkNotNull(toStop); // Check whether this transfer is preferred (or timed) if (transferTime == StopTransfer.PREFERRED_TRANSFER || transferTime == StopTransfer.TIMED_TRANSFER) { preferredTransfers = true; } // Lookup whether a transfer between the two stops already exists P2<AgencyAndId> stopIdPair = new P2<AgencyAndId>(fromStop.getId(), toStop.getId()); StopTransfer stopTransfer = table.get(stopIdPair); if (stopTransfer == null) { // If not, create one and add to table stopTransfer = new StopTransfer(); table.put(stopIdPair, stopTransfer); } assert(stopTransfer != null); // Create and add a specific transfer to the stop transfer SpecificTransfer specificTransfer = new SpecificTransfer(fromRoute, toRoute, fromTrip, toTrip, transferTime); stopTransfer.addSpecificTransfer(specificTransfer); } /** * Determines the transfer penalty given a transfer time and a penalty for non-preferred * transfers. * @param transferTime is the transfer time * @param nonpreferredTransferPenalty is the penalty for non-preferred transfers * @return the transfer penalty */ public int determineTransferPenalty(int transferTime, int nonpreferredTransferPenalty) { int transferPenalty = 0; if (hasPreferredTransfers()) { // Only penalize transfers if there are some that will be depenalized transferPenalty = nonpreferredTransferPenalty; if (transferTime == StopTransfer.PREFERRED_TRANSFER || transferTime == StopTransfer.TIMED_TRANSFER) { // Depenalize preferred transfers // Timed transfers are assumed to be preferred as well // TODO: verify correctness of this method (AMB) transferPenalty = 0; } } return transferPenalty; } /** * Internal class for testing purposes only. * @see TransferGraphLinker */ @Deprecated public static class Transfer { public AgencyAndId fromStopId, toStopId; public int seconds; public Transfer(AgencyAndId fromStopId, AgencyAndId toStopId, int seconds) { this.fromStopId = fromStopId; this.toStopId = toStopId; this.seconds = seconds; } } /** * Public function for testing purposes only. * Returns only the first specific transfers. * @see TransferGraphLinker */ @Deprecated public Iterable<Transfer> getAllFirstSpecificTransfers() { ArrayList<Transfer> transfers = new ArrayList<Transfer>(table.size()); for (Entry<P2<AgencyAndId>, StopTransfer> entry : table.entrySet()) { P2<AgencyAndId> p2 = entry.getKey(); int transferTime = entry.getValue().getFirstSpecificTransferTime(); if (transferTime != StopTransfer.UNKNOWN_TRANSFER) { transfers.add(new Transfer(p2.getFirst(), p2.getSecond(), transferTime)); } } return transfers; } }