/* 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 (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.impl.raptor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.onebusaway.gtfs.model.AgencyAndId; import org.opentripplanner.common.pqueue.BinHeap; import org.opentripplanner.common.pqueue.OTPPriorityQueue; import org.opentripplanner.common.pqueue.OTPPriorityQueueFactory; import org.opentripplanner.routing.algorithm.GenericDijkstra; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.ServiceDay; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.core.StateEditor; import org.opentripplanner.routing.edgetype.TableTripPattern; import org.opentripplanner.routing.edgetype.TransitBoardAlight; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.spt.ShortestPathTree; import org.opentripplanner.routing.vertextype.OffboardVertex; import org.opentripplanner.routing.vertextype.TransitStop; import org.opentripplanner.routing.vertextype.TransitStopArrive; import org.opentripplanner.routing.vertextype.TransitStopDepart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RaptorSearch { private static final Logger log = LoggerFactory.getLogger(RaptorSearch.class); List<RaptorState>[] statesByStop; private List<RaptorState> targetStates = new ArrayList<RaptorState>(); Set<RaptorStop> visitedEver = new HashSet<RaptorStop>(); Set<RaptorStop> visitedLastRound = new HashSet<RaptorStop>(); HashMap<RaptorStop, StopNearTarget> stopsNearTarget = new HashMap<RaptorStop, StopNearTarget>(); public List<RaptorState> boundingStates = new ArrayList<RaptorState>(); public TargetBound bounder; public boolean stalling = false; public int maxTime = Integer.MAX_VALUE; public int maxTimeDayIndex; private RaptorData data; @SuppressWarnings("unchecked") RaptorSearch(RaptorData data, RoutingRequest options) { statesByStop = new List[data.stops.length]; bounder = new TargetBound(options); this.data = data; } public void addStates(int stop, List<RaptorState> list) { assert (statesByStop[stop] == null); statesByStop[stop] = list; } public void setStates(int stop, List<RaptorState> list) { statesByStop[stop] = list; } public int getNStops() { return statesByStop.length; } public void addTargetState(RaptorState state) { targetStates.add(state); } public List<RaptorState> getTargetStates() { return targetStates; } public void addStopNearTarget(RaptorStop stop, double walkDistance, int time) { stopsNearTarget.put(stop, new StopNearTarget(stop, walkDistance, time)); } public void removeTargetState(State toRemove) { for (Iterator<RaptorState> it = targetStates.iterator(); it.hasNext();) { RaptorState state = it.next(); if (state.walkPath == toRemove) { it.remove(); } } } public List<RaptorState> transitPhase(RoutingRequest options, int nBoardings) { Collection<RaptorRoute> routesToVisit = new HashSet<RaptorRoute>(); if (data.routesForStop == null) { Collection<RaptorRoute> routes = data.routes; for (RaptorStop stop : visitedLastRound) { for (RaptorRoute route : data.routesForStop[stop.index]) { if (routes.contains(route)) { routesToVisit.add(route); } } } } else { for (RaptorStop stop : visitedLastRound) { for (RaptorRoute route : data.routesForStop[stop.index]) { routesToVisit.add(route); } } } HashSet<RaptorStop> visitedThisRound = new HashSet<RaptorStop>(); List<RaptorState> createdStates = new ArrayList<RaptorState>(); int boardSlack; if (options.isArriveBy()) { boardSlack = nBoardings == 1 ? options.getAlightSlack() : (options.getTransferSlack() - options.getBoardSlack()); } else { boardSlack = nBoardings == 1 ? options.getBoardSlack() : (options.getTransferSlack() - options.getAlightSlack()); } for (RaptorRoute route : routesToVisit) { List<RaptorState> boardStates = new ArrayList<RaptorState>(); // not really states boolean started; int firstStop, lastStop, direction, lastBoardStop; if (options.isArriveBy()) { firstStop = route.getNStops() - 1; lastStop = -1; direction = -1; lastBoardStop = 0; //check for interlining on the first stop started = checkForInterliningArriveBy(options, nBoardings, route, boardStates); } else { firstStop = 0; lastStop = route.getNStops(); direction = 1; lastBoardStop = lastStop - 1; started = checkForInterliningDepartAt(options, nBoardings, route, boardStates); } for (int stopNo = firstStop; stopNo != lastStop; stopNo += direction) { // find the current time at this stop RaptorStop stop = route.stops[stopNo]; if (!started && !visitedLastRound.contains(stop)) continue; started = true; //skip stops which aren't in this set of data; //this is used for the rush ahead search if (!data.raptorStopsForStopId.containsKey(stop.stopVertex.getStopId())) { continue; } // Skip banned stops if (options.getBannedStops().matches(stop.stopVertex.getStop())) { continue; } List<RaptorState> states = statesByStop[stop.index]; List<RaptorState> newStates = new ArrayList<RaptorState>(); if (states == null) { states = new ArrayList<RaptorState>(); statesByStop[stop.index] = states; } // this checks the case of continuing on the current trips. CONTINUE: for (RaptorState boardState : boardStates) { if (boardState.boardStop == stop) { // this only happens due to interlines where // the last stop of the first route is equal to the first stop of the // subsequent route. continue; } RaptorState newState = new RaptorState(boardState.getParent()); ServiceDay sd = boardState.serviceDay; int travelTime; if (options.isArriveBy()) { if (!route.alights[0][boardState.patternIndex].getPattern().canBoard(stopNo)) continue; int boardTime = route.getBoardTime(boardState.tripTimes, stopNo); newState.arrivalTime = (int) sd.time(boardTime); // add in slack newState.arrivalTime -= options.getBoardSlack(); travelTime = newState.getParent().arrivalTime - newState.arrivalTime; } else { if (!route.boards[0][boardState.patternIndex].getPattern() .canAlight(stopNo)) continue; int alightTime = route.getAlightTime(boardState.tripTimes, stopNo); newState.arrivalTime = (int) sd.time(alightTime); // add in slack newState.arrivalTime += options.getAlightSlack(); travelTime = newState.arrivalTime - newState.getParent().arrivalTime; } newState.weight += travelTime; //TODO: consider transfer penalties newState.weight += boardState.weight; newState.boardStop = boardState.boardStop; newState.boardStopSequence = boardState.boardStopSequence; newState.route = route; newState.patternIndex = boardState.patternIndex; newState.tripTimes = boardState.tripTimes; newState.nBoardings = boardState.nBoardings; newState.walkDistance = boardState.walkDistance; newState.tripId = boardState.tripId; newState.stop = stop; newState.serviceDay = boardState.serviceDay; for (RaptorState oldState : states) { if (oldState.eDominates(newState)) { continue CONTINUE; } } for (RaptorState oldState : newStates) { if (oldState.eDominates(newState)) { continue CONTINUE; } } Iterator<RaptorState> it = states.iterator(); while (it.hasNext()) { RaptorState oldState = it.next(); if (newState.eDominates(oldState)) { it.remove(); } } it = newStates.iterator(); while (it.hasNext()) { RaptorState oldState = it.next(); if (newState.eDominates(oldState)) { it.remove(); } } visitedThisRound.add(stop); visitedEver.add(stop); newStates.add(newState); } if (stopNo != lastBoardStop) { if (stop.stopVertex.isLocal() && nBoardings > 1) { // cannot transfer at a local stop createdStates.addAll(newStates); states.addAll(newStates); continue; } // try boarding here TRYBOARD: for (RaptorState oldState : states) { if (oldState.nBoardings != nBoardings - 1) continue; if (oldState.getRoute() == route) continue; // we got here via this route, so no reason to transfer RaptorBoardSpec boardSpec; int waitTime; if (options.isArriveBy()) { int arrivalTime = oldState.arrivalTime - boardSlack; boardSpec = route.getTripIndexReverse(options, arrivalTime, stopNo); if (boardSpec == null) continue; waitTime = oldState.arrivalTime - boardSpec.departureTime; } else { int arrivalTime = oldState.arrivalTime + boardSlack; boardSpec = route.getTripIndex(options, arrivalTime, stopNo); if (boardSpec == null) continue; waitTime = boardSpec.departureTime - oldState.arrivalTime; } RaptorState boardState = new RaptorState(oldState); if (nBoardings == 1) { //do not count initial wait time, since it will be optimized away later boardState.initialWaitTime = waitTime; waitTime = 0; } boardState.weight = options.getBoardCost(route.mode) + waitTime; boardState.nBoardings = nBoardings; boardState.boardStop = stop; boardState.boardStopSequence = stopNo; boardState.arrivalTime = boardSpec.departureTime; boardState.patternIndex = boardSpec.patternIndex; boardState.tripTimes = boardSpec.tripTimes; boardState.serviceDay = boardSpec.serviceDay; boardState.route = route; boardState.walkDistance = oldState.walkDistance; boardState.tripId = boardSpec.tripId; for (RaptorState state : boardStates) { if (state.eDominates(boardState)) { continue TRYBOARD; } } for (RaptorState state : newStates) { if (state.eDominates(boardState)) { continue TRYBOARD; } } boardStates.add(boardState); } } createdStates.addAll(newStates); states.addAll(newStates); } } visitedLastRound = visitedThisRound; return createdStates; } private boolean checkForInterliningDepartAt(RoutingRequest options, int nBoardings, RaptorRoute route, List<RaptorState> boardStates) { int firstStop = 0; boolean started = false; final List<RaptorState> oldStates = statesByStop[route.stops[firstStop].index]; if (oldStates == null) return false; INTERLINE:for (RaptorState oldState : oldStates) { if (oldState.nBoardings != nBoardings - 1) { continue; } if (oldState.route == null) { continue; } if (oldState.route.stops[oldState.route.getNStops() - 1] != oldState.stop) { continue; } RaptorInterlineData interline = oldState.route.interlinesOut.get(oldState.tripId); if (interline == null || interline.toRoute != route) { continue; } RaptorState stayOn = oldState.clone(); stayOn.arrivalTime -= options.getAlightSlack(); // go backwards in time to erase unnecessary slack stayOn.interlining = true; // generate a board state for this interline RaptorState boardState = new RaptorState(stayOn); //we need to subtract out the slacks that we are about to mistakenly pay boardState.weight -= options.getBoardSlack() - options.getAlightSlack(); boardState.nBoardings = nBoardings - 1; boardState.boardStop = route.stops[firstStop]; boardState.boardStopSequence = firstStop; TransitBoardAlight board = route.boards[firstStop][interline.toPatternIndex]; TableTripPattern pattern = board.getPattern(); boardState.tripTimes = pattern.getTripTimes(interline.toTripIndex); final ServiceDay serviceDay = oldState.serviceDay; boardState.arrivalTime = (int) serviceDay.time(boardState.tripTimes.getDepartureTime(firstStop)); boardState.patternIndex = interline.toPatternIndex; boardState.tripId = interline.toTripId; boardState.serviceDay = serviceDay; boardState.route = route; boardState.walkDistance = oldState.walkDistance; for (RaptorState state : boardStates) { if (state.eDominates(boardState)) { continue INTERLINE; } } boardStates.add(boardState); started = true; } return started; } private boolean checkForInterliningArriveBy(RoutingRequest options, int nBoardings, RaptorRoute route, List<RaptorState> boardStates) { int firstStop = route.getNStops() - 1; boolean started = false; final List<RaptorState> states = statesByStop[route.stops[firstStop].index]; if (states == null) return false; INTERLINE: for (RaptorState oldState : states) { if (oldState.nBoardings != nBoardings - 1) { continue; } if (oldState.route == null) { continue; } if (oldState.route.stops[0] != oldState.stop) { continue; } RaptorInterlineData interline = oldState.route.interlinesIn.get(oldState.tripId); if (interline == null || interline.fromRoute != route) { continue; } RaptorState stayOn = oldState.clone(); stayOn.arrivalTime += options.getBoardSlack(); // go backwards in time stayOn.interlining = true; // generate a board state for this interline RaptorState boardState = new RaptorState(stayOn); //we need to subtract out the boardSlack that we are about to mistakenly pay boardState.weight = -options.getBoardSlack() - options.getAlightSlack(); boardState.nBoardings = boardState.nBoardings = nBoardings - 1; boardState.boardStop = route.stops[firstStop]; boardState.boardStopSequence = firstStop; TransitBoardAlight alight = route.alights[firstStop - 1][interline.fromPatternIndex]; TableTripPattern pattern = alight.getPattern(); boardState.tripTimes = pattern.getTripTimes(interline.fromTripIndex); final ServiceDay serviceDay = oldState.serviceDay; boardState.arrivalTime = (int) serviceDay.time(boardState.tripTimes .getArrivalTime(firstStop - 1)); boardState.patternIndex = interline.fromPatternIndex; boardState.tripId = interline.fromTripId; boardState.serviceDay = serviceDay; boardState.route = route; boardState.walkDistance = oldState.walkDistance; for (RaptorState state : boardStates) { if (state.eDominates(boardState)) { continue INTERLINE; } } boardStates.add(boardState); started = true; } return started; } /** * @param options * @param walkOptions * @param nBoardings * @param createdStates * @return whether search should continue */ public boolean walkPhase(RoutingRequest options, RoutingRequest walkOptions, int nBoardings, List<RaptorState> createdStates) { double distanceToNearestTransitStop = 0; if (options.rctx.target != null) { distanceToNearestTransitStop = options.rctx.target.getDistanceToNearestTransitStop(); } final int boardSlack = nBoardings == 1 ? options.getBoardSlack() : (options .getTransferSlack() - options.getAlightSlack()); ShortestPathTree spt; GenericDijkstra dijkstra = new GenericDijkstra(walkOptions); dijkstra.setShortestPathTreeFactory(bounder); List<State> transitStopStates = new ArrayList<State>(); if (nBoardings == 0) { //TODO: retry min-time bounding with this and with maxtime if (options.rctx.target != null && bounder.getTargetDistance(options.rctx.origin) < options .getMaxWalkDistance()) dijkstra.setHeuristic(bounder); MaxWalkState start = new MaxWalkState(options.rctx.origin, walkOptions); spt = dijkstra.getShortestPathTree(start); for (State state : spt.getAllStates()) { if (state.getVertex() instanceof TransitStop || state.getVertex() instanceof TransitStopArrive || state.getVertex() instanceof TransitStopDepart) transitStopStates.add(state); } // also, compute an initial spt from the target so that we can find out what transit // stops are nearby and what // the time is to them, so that we can start target bounding earlier if (maxTimeDayIndex > 0) { RoutingRequest reversedWalkOptions = walkOptions.clone(); reversedWalkOptions.setArriveBy(!walkOptions.isArriveBy()); GenericDijkstra destDijkstra = new GenericDijkstra(reversedWalkOptions); start = new MaxWalkState(options.rctx.target, reversedWalkOptions); ShortestPathTree targetSpt = destDijkstra.getShortestPathTree(start); for (State state : targetSpt.getAllStates()) { final Vertex vertex = state.getVertex(); if (!(vertex instanceof TransitStop)) continue; RaptorStop stop = data.raptorStopsForStopId.get(((TransitStop) vertex) .getStopId()); if (stop == null) { // we have found a stop is totally unused, so skip it continue; } // Skip banned stops if (options.getBannedStops().matches(stop.stopVertex.getStop())) { continue; } addStopNearTarget(stop, state.getWalkDistance(), (int) state.getElapsedTimeSeconds()); } } } else { final List<MaxWalkState> startPoints = new ArrayList<MaxWalkState>(); for (RaptorState state : createdStates) { // bounding states // this reduces the number of initial vertices // and the state space size Vertex stopVertex = options.isArriveBy() ? state.stop.departVertex : state.stop.arriveVertex; if (stopVertex == null) { stopVertex = state.stop.stopVertex; } if (options.rctx.target != null) { double minWalk = distanceToNearestTransitStop; double targetDistance = bounder.getTargetDistance(stopVertex); if (targetDistance + state.walkDistance > options.getMaxWalkDistance()) { // can't walk to destination, so we can't alight at a local vertex if (state.stop.stopVertex.isLocal()) continue; } if (minWalk + state.walkDistance > options.getMaxWalkDistance()) { continue; } } StateEditor dijkstraState = new MaxWalkState.MaxWalkStateEditor(walkOptions, stopVertex); dijkstraState.setInitialWaitTimeSeconds(state.initialWaitTime); dijkstraState.setStartTimeSeconds(options.dateTime); dijkstraState.setNumBoardings(state.nBoardings); dijkstraState.setWalkDistance(state.walkDistance); dijkstraState.setTimeSeconds(state.arrivalTime); dijkstraState.setExtension("raptorParent", state); dijkstraState.setOptions(walkOptions); dijkstraState.incrementWeight(state.weight); MaxWalkState newState = (MaxWalkState) dijkstraState.makeState(); startPoints.add(newState); } if (startPoints.size() == 0) { return false; } System.out.println("walk starts: " + startPoints.size() + " / " + visitedEver.size()); dijkstra.setPriorityQueueFactory(new PrefilledPriorityQueueFactory(startPoints.subList( 1, startPoints.size()))); bounder.addSptStates(startPoints.subList(1, startPoints.size())); bounder.prepareForSearch(); dijkstra.setSearchTerminationStrategy(bounder); if (options.rctx.target != null) { dijkstra.setSkipTraverseResultStrategy(bounder); dijkstra.setHeuristic(bounder); } // Do local search spt = dijkstra.getShortestPathTree(startPoints.get(0)); transitStopStates = bounder.getTransitStopsVisited(); } List<? extends State> targetStates = null; if (walkOptions.rctx.target != null) targetStates = spt.getStates(walkOptions.rctx.target); if (targetStates != null) { TARGET: for (State targetState : targetStates) { RaptorState parent = (RaptorState) targetState.getExtension("raptorParent"); RaptorState state; if (parent != null) { state = new RaptorState(parent); state.nBoardings = parent.nBoardings; state.rentingBike = targetState.isBikeRenting(); } else { state = new RaptorState(options); } state.weight = targetState.getWeight(); state.walkDistance = targetState.getWalkDistance(); state.arrivalTime = (int) targetState.getTimeSeconds(); state.walkPath = targetState; for (Iterator<RaptorState> it = getTargetStates().iterator(); it.hasNext();) { RaptorState oldState = it.next(); if (oldState.eDominates(state)) { continue TARGET; } else if (state.eDominates(oldState)) { it.remove(); } } addTargetState(state); log.debug("Found target at: " + state + " on " + state.getTrips()); } } for (State state : bounder.removedBoundingStates) { removeTargetState(state); } SPTSTATE: for (State state : transitStopStates) { final Vertex vertex = state.getVertex(); RaptorStop stop = data.raptorStopsForStopId.get(((OffboardVertex) vertex).getStopId()); if (stop == null) { // we have found a stop is totally unused, so skip it continue; } // Skip banned stops if (options.getBannedStops().matches(stop.stopVertex.getStop())) { continue; } if (options.rctx.target != null) { double minWalk = distanceToNearestTransitStop; double targetDistance = bounder.getTargetDistance(vertex); final double remainingWalk = options.maxWalkDistance - state.getWalkDistance(); if (maxTimeDayIndex > 0 && remainingWalk < 3218) { double minTime = (targetDistance - minWalk) / Raptor.MAX_TRANSIT_SPEED + minWalk / options.getStreetSpeedUpperBound(); if (targetDistance > remainingWalk) minTime += boardSlack; int maxTimeForVertex = 0; int region = vertex.getGroupIndex(); final int elapsedTime = (int) state.getElapsedTimeSeconds(); for (StopNearTarget stopNearTarget : stopsNearTarget.values()) { int destinationRegion = stopNearTarget.stop.stopVertex.getGroupIndex(); final int maxTimeFromThisRegion = data.maxTransitRegions.maxTransit[maxTimeDayIndex][destinationRegion][region]; int maxTime = elapsedTime + maxTimeFromThisRegion + stopNearTarget.time; if (maxTime > maxTimeForVertex) { maxTimeForVertex = maxTime; } } if (maxTimeForVertex < maxTime) { maxTime = maxTimeForVertex; } else { if (elapsedTime + minTime > maxTime * 1.5) { continue; } } } } List<RaptorState> states = statesByStop[stop.index]; if (states == null) { states = new ArrayList<RaptorState>(); statesByStop[stop.index] = states; } RaptorState parent = (RaptorState) state.getExtension("raptorParent"); RaptorState newState; if (parent != null) { newState = new RaptorState(parent); } else { //this only happens in round 0 newState = new RaptorState(options); } newState.weight = state.getWeight(); newState.nBoardings = nBoardings; newState.walkDistance = state.getWalkDistance(); newState.arrivalTime = (int) state.getTimeSeconds(); newState.walkPath = state; newState.stop = stop; newState.rentingBike = state.isBikeRenting(); for (RaptorState oldState : states) { if (oldState.eDominates(newState)) { continue SPTSTATE; } } visitedLastRound.add(stop); visitedEver.add(stop); states.add(newState); } return true; } class PrefilledPriorityQueueFactory implements OTPPriorityQueueFactory { private List<? extends State> startPoints; public PrefilledPriorityQueueFactory(List<? extends State> startPoints) { this.startPoints = startPoints; } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public <T> OTPPriorityQueue<T> create(int maxSize) { BinHeap heap = new BinHeap<T>(); for (State state : startPoints) { heap.insert(state, state.getWeight()); } return heap; } } public void reset(RoutingRequest options) { bounder.reset(options); Arrays.fill(statesByStop, null); } }