/**
  * Get a list of all trips that pass through a stop during a single ServiceDate. Useful when
  * creating complete stop timetables for a single day.
  *
  * @param stop Stop object to perform the search for
  * @param serviceDate Return all departures for the specified date
  * @return
  */
 public List<StopTimesInPattern> getStopTimesForStop(Stop stop, ServiceDate serviceDate) {
   List<StopTimesInPattern> ret = new ArrayList<>();
   TimetableSnapshot snapshot = null;
   if (graph.timetableSnapshotSource != null) {
     snapshot = graph.timetableSnapshotSource.getTimetableSnapshot();
   }
   Collection<TripPattern> patterns = patternsForStop.get(stop);
   for (TripPattern pattern : patterns) {
     StopTimesInPattern stopTimes = new StopTimesInPattern(pattern);
     Timetable tt;
     if (snapshot != null) {
       tt = snapshot.resolve(pattern, serviceDate);
     } else {
       tt = pattern.scheduledTimetable;
     }
     ServiceDay sd =
         new ServiceDay(graph, serviceDate, calendarService, pattern.route.getAgency().getId());
     int sidx = 0;
     for (Stop currStop : pattern.stopPattern.stops) {
       if (currStop == stop) {
         for (TripTimes t : tt.tripTimes) {
           if (!sd.serviceRunning(t.serviceCode)) continue;
           stopTimes.times.add(new TripTimeShort(t, sidx, stop, sd));
         }
       }
       sidx++;
     }
     ret.add(stopTimes);
   }
   return ret;
 }
  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;
  }
  /**
   * Prune raptor data to include only routes and boardings which have trips today. Doesn't actually
   * improve speed
   */
  @SuppressWarnings("unchecked")
  private RaptorData pruneDataForServiceDays(Graph graph, ArrayList<ServiceDay> serviceDays) {

    if (serviceDays.equals(cachedServiceDays)) return cachedRaptorData;
    RaptorData data = graph.getService(RaptorDataService.class).getData();
    RaptorData pruned = new RaptorData();
    pruned.raptorStopsForStopId = data.raptorStopsForStopId;
    pruned.stops = data.stops;
    pruned.routes = new ArrayList<RaptorRoute>();
    pruned.routesForStop = new List[pruned.stops.length];

    for (RaptorRoute route : data.routes) {
      ArrayList<Integer> keep = new ArrayList<Integer>();

      for (int i = 0; i < route.boards[0].length; ++i) {
        Edge board = route.boards[0][i];
        int serviceId;
        if (board instanceof TransitBoardAlight) {
          serviceId = ((TransitBoardAlight) board).getPattern().getServiceId();
        } else {
          log.debug("Unexpected nonboard among boards");
          continue;
        }
        for (ServiceDay day : serviceDays) {
          if (day.serviceIdRunning(serviceId)) {
            keep.add(i);
            break;
          }
        }
      }
      if (keep.isEmpty()) continue;
      int nPatterns = keep.size();
      RaptorRoute prunedRoute = new RaptorRoute(route.getNStops(), nPatterns);
      for (int stop = 0; stop < route.getNStops() - 1; ++stop) {
        for (int pattern = 0; pattern < nPatterns; ++pattern) {
          prunedRoute.boards[stop][pattern] = route.boards[stop][keep.get(pattern)];
        }
      }
      pruned.routes.add(route);
      for (RaptorStop stop : route.stops) {
        List<RaptorRoute> routes = pruned.routesForStop[stop.index];
        if (routes == null) {
          routes = new ArrayList<RaptorRoute>();
          pruned.routesForStop[stop.index] = routes;
        }
        routes.add(route);
      }
    }
    for (RaptorStop stop : data.stops) {
      if (pruned.routesForStop[stop.index] == null) {
        pruned.routesForStop[stop.index] = Collections.emptyList();
      }
    }
    cachedServiceDays = serviceDays;
    cachedRaptorData = pruned;
    return pruned;
  }
 /* See weightLowerBound comment. */
 public double timeLowerBound(RoutingContext rctx) {
   if (rctx.opt.isArriveBy()) {
     if (!rctx.opt.getModes().get(modeMask)) {
       return Double.POSITIVE_INFINITY;
     }
     for (ServiceDay sd : rctx.serviceDays) if (sd.serviceIdRunning(serviceId)) return 0;
     return Double.POSITIVE_INFINITY;
   } else {
     return 0;
   }
 }
  /**
   * Fetch upcoming vehicle departures from a stop. It goes though all patterns passing the stop for
   * the previous, current and next service date. It uses a priority queue to keep track of the next
   * departures. The queue is shared between all dates, as services from the previous service date
   * can visit the stop later than the current service date's services. This happens eg. with
   * sleeper trains.
   *
   * <p>TODO: Add frequency based trips
   *
   * @param stop Stop object to perform the search for
   * @param startTime Start time for the search. Seconds from UNIX epoch
   * @param timeRange Searches forward for timeRange seconds from startTime
   * @param numberOfDepartures Number of departures to fetch per pattern
   * @return
   */
  public List<StopTimesInPattern> stopTimesForStop(
      Stop stop, long startTime, int timeRange, int numberOfDepartures) {

    if (startTime == 0) {
      startTime = System.currentTimeMillis() / 1000;
    }
    List<StopTimesInPattern> ret = new ArrayList<>();
    TimetableSnapshot snapshot = null;
    if (graph.timetableSnapshotSource != null) {
      snapshot = graph.timetableSnapshotSource.getTimetableSnapshot();
    }
    ServiceDate[] serviceDates = {
      new ServiceDate().previous(), new ServiceDate(), new ServiceDate().next()
    };

    for (TripPattern pattern : patternsForStop.get(stop)) {

      // Use the Lucene PriorityQueue, which has a fixed size
      PriorityQueue<TripTimeShort> pq =
          new PriorityQueue<TripTimeShort>(numberOfDepartures) {
            @Override
            protected boolean lessThan(TripTimeShort tripTimeShort, TripTimeShort t1) {
              // Calculate exact timestamp
              return (tripTimeShort.serviceDay + tripTimeShort.realtimeDeparture)
                  > (t1.serviceDay + t1.realtimeDeparture);
            }
          };

      // Loop through all possible days
      for (ServiceDate serviceDate : serviceDates) {
        ServiceDay sd =
            new ServiceDay(graph, serviceDate, calendarService, pattern.route.getAgency().getId());
        Timetable tt;
        if (snapshot != null) {
          tt = snapshot.resolve(pattern, serviceDate);
        } else {
          tt = pattern.scheduledTimetable;
        }

        if (!tt.temporallyViable(sd, startTime, timeRange, true)) continue;

        int secondsSinceMidnight = sd.secondsSinceMidnight(startTime);
        int sidx = 0;
        for (Stop currStop : pattern.stopPattern.stops) {
          if (currStop == stop) {
            for (TripTimes t : tt.tripTimes) {
              if (!sd.serviceRunning(t.serviceCode)) continue;
              if (t.getDepartureTime(sidx) != -1
                  && t.getDepartureTime(sidx) >= secondsSinceMidnight) {
                pq.insertWithOverflow(new TripTimeShort(t, sidx, stop, sd));
              }
            }

            // TODO: This needs to be adapted after #1647 is merged
            for (FrequencyEntry freq : tt.frequencyEntries) {
              if (!sd.serviceRunning(freq.tripTimes.serviceCode)) continue;
              int departureTime = freq.nextDepartureTime(sidx, secondsSinceMidnight);
              if (departureTime == -1) continue;
              int lastDeparture =
                  freq.endTime
                      + freq.tripTimes.getArrivalTime(sidx)
                      - freq.tripTimes.getDepartureTime(0);
              int i = 0;
              while (departureTime <= lastDeparture && i < numberOfDepartures) {
                pq.insertWithOverflow(
                    new TripTimeShort(freq.materialize(sidx, departureTime, true), sidx, stop, sd));
                departureTime += freq.headway;
                i++;
              }
            }
          }
          sidx++;
        }
      }

      if (pq.size() != 0) {
        StopTimesInPattern stopTimes = new StopTimesInPattern(pattern);
        while (pq.size() != 0) {
          stopTimes.times.add(0, pq.pop());
        }
        ret.add(stopTimes);
      }
    }
    return ret;
  }
  @Test
  public final void testOnBoardDepartureTime() {
    Coordinate[] coordinates = new Coordinate[5];
    coordinates[0] = new Coordinate(0.0, 0.0);
    coordinates[1] = new Coordinate(0.0, 1.0);
    coordinates[2] = new Coordinate(2.0, 1.0);
    coordinates[3] = new Coordinate(5.0, 1.0);
    coordinates[4] = new Coordinate(5.0, 5.0);

    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex dwell = mock(PatternArriveVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));

    GeometryFactory geometryFactory = GeometryUtils.getGeometryFactory();
    CoordinateSequenceFactory coordinateSequenceFactory =
        geometryFactory.getCoordinateSequenceFactory();
    CoordinateSequence coordinateSequence = coordinateSequenceFactory.create(coordinates);
    LineString geometry = new LineString(coordinateSequence, geometryFactory);
    ArrayList<Edge> hops = new ArrayList<Edge>(2);
    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(3);
    StopTime stopDepartTime = new StopTime();
    StopTime stopDwellTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopDwell = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(agencyAndId);
    stopDwell.setId(agencyAndId);
    stopArrive.setId(agencyAndId);
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopDwellTime.setArrivalTime(20);
    stopDwellTime.setStop(stopDwell);
    stopDwellTime.setDepartureTime(40);
    stopArriveTime.setArrivalTime(60);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopDwellTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);
    trip.setTripHeadsign("The right");

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);
    when(dwell.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0);
    PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1);

    hops.add(patternHop0);
    hops.add(patternHop1);

    when(graph.getEdges()).thenReturn(hops);
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(9);

    patternHop0.setGeometry(geometry);
    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    coordinates = new Coordinate[3];
    coordinates[0] = new Coordinate(3.5, 1.0);
    coordinates[1] = new Coordinate(5.0, 1.0);
    coordinates[2] = new Coordinate(5.0, 5.0);

    coordinateSequence = coordinateSequenceFactory.create(coordinates);
    geometry = new LineString(coordinateSequence, geometryFactory);

    Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext);
    Edge edge = vertex.getOutgoing().toArray(new Edge[1])[0];

    assertEquals(vertex, edge.getFromVertex());
    assertEquals(dwell, edge.getToVertex());
    assertEquals("The right", edge.getDirection());
    assertEquals(geometry, edge.getGeometry());

    assertEquals(coordinates[0].x, vertex.getX(), 0.0);
    assertEquals(coordinates[0].y, vertex.getY(), 0.0);
  }
  @Test
  public final void testOnBoardAtStation() {
    TransitStop station0 = mock(TransitStop.class);
    TransitStop station1 = mock(TransitStop.class);
    TransitStop station2 = mock(TransitStop.class);
    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex dwell = mock(PatternArriveVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));

    ArrayList<Edge> hops = new ArrayList<Edge>(2);
    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(2);
    StopTime stopDepartTime = new StopTime();
    StopTime stopDwellTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopDwell = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(new AgencyAndId("Station", "0"));
    stopDwell.setId(new AgencyAndId("Station", "1"));
    stopArrive.setId(new AgencyAndId("Station", "2"));
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopDwellTime.setArrivalTime(20);
    stopDwellTime.setStop(stopDwell);
    stopDwellTime.setDepartureTime(40);
    stopArriveTime.setArrivalTime(60);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopDwellTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);
    when(dwell.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop0 = new PatternHop(depart, dwell, stopDepart, stopDwell, 0);
    PatternHop patternHop1 = new PatternHop(dwell, arrive, stopDwell, stopArrive, 1);

    hops.add(patternHop0);
    hops.add(patternHop1);

    when(graph.getEdges()).thenReturn(hops);
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(dwell.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(graph.getVertex("Station_0")).thenReturn(station0);
    when(graph.getVertex("Station_1")).thenReturn(station1);
    when(graph.getVertex("Station_2")).thenReturn(station2);

    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(0);
    assertEquals(station0, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(20);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(30);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(40);
    assertEquals(station1, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));

    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(60);
    assertEquals(station2, onBoardDepartServiceImpl.setupDepartOnBoard(routingContext));
  }
  @Test
  public final void testOnBoardDepartureAtArrivalTime() {
    Coordinate[] coordinates = new Coordinate[2];
    coordinates[0] = new Coordinate(0.0, 0.0);
    coordinates[1] = new Coordinate(0.0, 1.0);

    TransitStop station0 = mock(TransitStop.class);
    TransitStop station1 = mock(TransitStop.class);
    PatternDepartVertex depart = mock(PatternDepartVertex.class);
    PatternArriveVertex arrive = mock(PatternArriveVertex.class);
    Graph graph = mock(Graph.class);
    RoutingRequest routingRequest = mock(RoutingRequest.class);
    ServiceDay serviceDay = mock(ServiceDay.class);

    when(graph.getTimeZone()).thenReturn(TimeZone.getTimeZone("GMT"));
    when(station0.getX()).thenReturn(coordinates[0].x);
    when(station0.getY()).thenReturn(coordinates[0].y);
    when(station1.getX()).thenReturn(coordinates[1].x);
    when(station1.getY()).thenReturn(coordinates[1].y);

    RoutingContext routingContext = new RoutingContext(routingRequest, graph, null, arrive);
    AgencyAndId agencyAndId = new AgencyAndId("Agency", "ID");
    Route route = new Route();
    ArrayList<StopTime> stopTimes = new ArrayList<StopTime>(2);
    StopTime stopDepartTime = new StopTime();
    StopTime stopArriveTime = new StopTime();
    Stop stopDepart = new Stop();
    Stop stopArrive = new Stop();
    Trip trip = new Trip();

    routingContext.serviceDays = new ArrayList<ServiceDay>(Collections.singletonList(serviceDay));
    route.setId(agencyAndId);
    stopDepart.setId(new AgencyAndId("Station", "0"));
    stopArrive.setId(new AgencyAndId("Station", "1"));
    stopDepartTime.setStop(stopDepart);
    stopDepartTime.setDepartureTime(0);
    stopArriveTime.setArrivalTime(10);
    stopArriveTime.setStop(stopArrive);
    stopTimes.add(stopDepartTime);
    stopTimes.add(stopArriveTime);
    trip.setId(agencyAndId);

    TripTimes tripTimes = new TripTimes(trip, stopTimes, new Deduplicator());
    StopPattern stopPattern = new StopPattern(stopTimes);
    TripPattern tripPattern = new TripPattern(route, stopPattern);

    when(depart.getTripPattern()).thenReturn(tripPattern);

    PatternHop patternHop = new PatternHop(depart, arrive, stopDepart, stopArrive, 0);

    when(graph.getEdges()).thenReturn(Collections.<Edge>singletonList(patternHop));
    when(depart.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(arrive.getCoordinate()).thenReturn(new Coordinate(0, 0));
    when(routingRequest.getFrom()).thenReturn(new GenericLocation());
    when(routingRequest.getStartingTransitTripId()).thenReturn(agencyAndId);
    when(serviceDay.secondsSinceMidnight(anyInt())).thenReturn(10);
    when(graph.getVertex("Station_0")).thenReturn(station0);
    when(graph.getVertex("Station_1")).thenReturn(station1);

    tripPattern.add(tripTimes);
    graph.index = new GraphIndex(graph);

    Vertex vertex = onBoardDepartServiceImpl.setupDepartOnBoard(routingContext);

    assertEquals(coordinates[1].x, vertex.getX(), 0.0);
    assertEquals(coordinates[1].y, vertex.getY(), 0.0);
  }
  public State traverse(State state0) {
    RoutingContext rctx = state0.getContext();
    RoutingRequest options = state0.getOptions();
    Trip trip = pattern.getTrip();

    if (options.isArriveBy()) {
      /* reverse traversal, not so much to do */
      // do not alight immediately when arrive-depart dwell has been eliminated
      // this affects multi-itinerary searches
      if (state0.getBackEdge() instanceof TransitBoardAlight
          && !((TransitBoardAlight) state0.getBackEdge()).isBoarding()) {
        return null;
      }
      StateEditor s1 = state0.edit(this);
      int type = pattern.getBoardType(stopIndex);
      if (TransitUtils.handleBoardAlightType(s1, type)) {
        return null;
      }
      s1.setTripId(null);
      s1.setLastAlightedTime(state0.getTime());
      s1.setBackMode(TraverseMode.BOARDING);
      s1.setPreviousStop(fromv);
      return s1.makeState();
    } else {
      /* forward traversal: look for a transit trip on this pattern */
      if (!options.getModes().get(modeMask)) {
        return null;
      }
      /* find next boarding time */
      /*
       * check lists of transit serviceIds running yesterday, today, and tomorrow (relative to
       * initial state) if this pattern's serviceId is running look for the next boarding time
       * choose the soonest boarding time among trips starting yesterday, today, or tomorrow
       */
      long currentTime = state0.getTime();
      int bestWait = -1;
      TraverseMode mode = state0.getNonTransitMode();
      if (options.bannedTrips.containsKey(trip.getId())) {
        // see comment in FrequencyAlight for details
        return null;
      }
      for (ServiceDay sd : rctx.serviceDays) {
        int secondsSinceMidnight = sd.secondsSinceMidnight(currentTime);
        // only check for service on days that are not in the future
        // this avoids unnecessarily examining tomorrow's services
        if (secondsSinceMidnight < 0) continue;
        if (sd.serviceIdRunning(serviceId)) {
          int startTime =
              pattern.getNextDepartureTime(
                  stopIndex,
                  secondsSinceMidnight,
                  options.wheelchairAccessible,
                  mode == TraverseMode.BICYCLE,
                  true);
          if (startTime >= 0) {
            // a trip was found, wait will be non-negative

            int wait = (int) (sd.time(startTime) - currentTime);
            if (wait < 0) _log.error("negative wait time on board");
            if (bestWait < 0 || wait < bestWait) {
              // track the soonest departure over all relevant schedules
              bestWait = wait;
            }
          }
        }
      }
      if (bestWait < 0) {
        return null;
      }

      /* check if trip is banned for this plan */
      if (options.tripIsBanned(trip)) return null;

      /* check if route is preferred for this plan */
      long preferences_penalty = options.preferencesPenaltyForTrip(trip);

      StateEditor s1 = state0.edit(this);
      int type = pattern.getBoardType(stopIndex);
      if (TransitUtils.handleBoardAlightType(s1, type)) {
        return null;
      }
      s1.incrementTimeInSeconds(bestWait);
      s1.incrementNumBoardings();
      s1.setTripId(trip.getId());
      s1.setZone(pattern.getZone(stopIndex));
      s1.setRoute(trip.getRoute().getId());
      s1.setBackMode(TraverseMode.BOARDING);

      long wait_cost = bestWait;
      if (state0.getNumBoardings() == 0) {
        wait_cost *= options.waitAtBeginningFactor;
      } else {
        wait_cost *= options.waitReluctance;
      }
      s1.incrementWeight(preferences_penalty);
      s1.incrementWeight(wait_cost + options.getBoardCost(mode));
      return s1.makeState();
    }
  }
  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;
        }
        if (options.getBannedStopsHard().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;
  }