public static int computeTravelTimeBetweenLocations(
      ScheduledBlockLocation scheduledBlockLocationA,
      ScheduledBlockLocation scheduledBlockLocationB) {

    if (scheduledBlockLocationA.getScheduledTime() == scheduledBlockLocationB.getScheduledTime())
      return 0;

    boolean inOrder =
        scheduledBlockLocationA.getScheduledTime() <= scheduledBlockLocationB.getScheduledTime();

    ScheduledBlockLocation from = inOrder ? scheduledBlockLocationA : scheduledBlockLocationB;
    ScheduledBlockLocation to = inOrder ? scheduledBlockLocationB : scheduledBlockLocationA;

    int delta = to.getScheduledTime() - from.getScheduledTime();

    BlockStopTimeEntry fromStop = from.getNextStop();
    BlockStopTimeEntry toStop = to.getNextStop();

    int slack = toStop.getAccumulatedSlackTime() - fromStop.getAccumulatedSlackTime();

    int slackFrom = computeSlackToNextStop(from);
    slack += slackFrom;
    int slackTo = computeSlackToNextStop(to);
    slack -= slackTo;

    return Math.max(0, delta - slack);
  }
  public static boolean isAtPotentialLayoverSpot(
      BlockState blockState, Observation obs, boolean optimistic) {

    /**
     * If there is no block assigned to this vehicle state, then we allow a layover spot the
     * terminals of all blocks.
     */
    if (blockState == null) {
      return false;
    }

    /**
     * Otherwise, if there is a block assigned, then we need to be more specific and check only
     * terminals for trips on this block.
     */
    final ScheduledBlockLocation blockLocation = blockState.getBlockLocation();

    if (blockLocation == null) return false;

    final BlockStopTimeEntry layoverSpot = getPotentialLayoverSpot(blockLocation, optimistic);
    if (layoverSpot == null) return false;

    final double dist =
        TurboButton.distance(
            obs.getLocation(), layoverSpot.getStopTime().getStop().getStopLocation());
    return dist <= _layoverStopDistance;
  }
  private static boolean isLayoverInRange(
      BlockStopTimeEntry prevStop, BlockStopTimeEntry nextStop, ScheduledBlockLocation location) {

    if (prevStop.getDistanceAlongBlock() <= location.getDistanceAlongBlock()
        && location.getDistanceAlongBlock() <= nextStop.getDistanceAlongBlock()) return true;

    final double d1 = Math.abs(location.getDistanceAlongBlock() - prevStop.getDistanceAlongBlock());
    final double d2 = Math.abs(location.getDistanceAlongBlock() - nextStop.getDistanceAlongBlock());
    return Math.min(d1, d2) < _layoverStopDistance;
  }
  /** ** Private Methods ** */
  private static boolean tripChangesBetweenPrevAndNextStop(
      List<BlockStopTimeEntry> stopTimes,
      BlockStopTimeEntry nextStop,
      ScheduledBlockLocation location) {

    final BlockStopTimeEntry previousStop = stopTimes.get(nextStop.getBlockSequence() - 1);
    final BlockTripEntry nextTrip = nextStop.getTrip();
    final BlockTripEntry previousTrip = previousStop.getTrip();

    return !previousTrip.equals(nextTrip) && isLayoverInRange(previousStop, nextStop, location);
  }
  public static int computeSlackToNextStop(ScheduledBlockLocation scheduledBlockLocation) {

    BlockStopTimeEntry nextStop = scheduledBlockLocation.getNextStop();
    StopTimeEntry stopTime = nextStop.getStopTime();
    int t = scheduledBlockLocation.getScheduledTime();

    /**
     * If we are actually at the next stop already, we return a negative value: the amount of slack
     * time already consumed.
     */
    if (stopTime.getArrivalTime() <= t && t <= stopTime.getDepartureTime())
      return stopTime.getArrivalTime() - t;

    int sequence = nextStop.getBlockSequence();

    /**
     * Are we before the first stop in the block? Are we already at the stop or on our way there?
     * Not sure, for now, let's assume there is no slack to be had
     */
    if (sequence == 0) return 0;

    BlockConfigurationEntry blockConfig = nextStop.getTrip().getBlockConfiguration();
    BlockStopTimeEntry previousStop = blockConfig.getStopTimes().get(sequence - 1);

    int slack = nextStop.getAccumulatedSlackTime() - previousStop.getAccumulatedSlackTime();
    slack -= previousStop.getStopTime().getSlackTime();

    int timeToNextStop = stopTime.getArrivalTime() - t;

    if (timeToNextStop > slack) return slack;
    else return timeToNextStop;
  }
 public static final double getAvgVelocityBetweenStops(BlockState blockState) {
   final BlockStopTimeEntry nextStop = blockState.getBlockLocation().getNextStop();
   if (nextStop != null && nextStop.getBlockSequence() - 1 > 0) {
     final BlockStopTimeEntry prevStop =
         blockState
             .getBlockInstance()
             .getBlock()
             .getStopTimes()
             .get(nextStop.getBlockSequence() - 1);
     final double avgVelocity =
         (nextStop.getDistanceAlongBlock() - prevStop.getDistanceAlongBlock())
             / (nextStop.getStopTime().getArrivalTime()
                 - prevStop.getStopTime().getDepartureTime());
     return avgVelocity;
   } else {
     return _avgTripVelocity;
   }
 }
  /**
   * Suggest a stop that may be a potential layover spot given the current location. Note that this
   * mostly guesses, and may give non-sensical answers. Always compare location of stop to current
   * observation to evaluate likelihood.
   *
   * @param location current location in block
   * @param optimistic be wildly optimistic about what stops could be a layover.
   * @return a stop on that block to consider for layovers
   */
  private static BlockStopTimeEntry getPotentialLayoverSpot(
      ScheduledBlockLocation location, boolean optimistic) {

    final int time = location.getScheduledTime();

    /** We could be at a layover at a stop itself */
    final BlockStopTimeEntry closestStop = location.getClosestStop();
    final StopTimeEntry closestStopTime = closestStop.getStopTime();

    if (closestStopTime.getAccumulatedSlackTime() > 60
        && closestStopTime.getArrivalTime() <= time
        && time <= closestStopTime.getDepartureTime()) return closestStop;

    final BlockStopTimeEntry nextStop = location.getNextStop();

    /**
     * If we're at the first or last stop of a trip in our run, then we're at a potential layover
     * spot.
     */
    final BlockStopTimeEntry tripFirstStop =
        Iterables.getLast(location.getActiveTrip().getStopTimes());
    final BlockStopTimeEntry tripLastStop =
        Iterables.getFirst(location.getActiveTrip().getStopTimes(), null);
    if (tripFirstStop.equals(closestStop) || tripLastStop.equals(closestStop)) return closestStop;

    /**
     * If the next stop is null, it means we're at the end of the block. Do we consider this a
     * layover spot? My sources say no. But now they say yes?
     */
    if (nextStop == null) return closestStop;

    /**
     * Is the next stop the first stop on the block? Then we're potentially at a layover before the
     * route starts
     */
    if (nextStop.getBlockSequence() == 0) return nextStop;

    final BlockTripEntry nextTrip = nextStop.getTrip();
    final BlockConfigurationEntry blockConfig = nextTrip.getBlockConfiguration();
    final List<BlockStopTimeEntry> stopTimes = blockConfig.getStopTimes();

    if (tripChangesBetweenPrevAndNextStop(stopTimes, nextStop, location)) return nextStop;

    /**
     * Due to some issues in the underlying GTFS with stop locations, buses sometimes make their
     * layover after they have just passed the layover point or right before they get there
     */
    if (nextStop.getBlockSequence() > 1) {
      final BlockStopTimeEntry previousStop =
          blockConfig.getStopTimes().get(nextStop.getBlockSequence() - 1);
      if (tripChangesBetweenPrevAndNextStop(stopTimes, previousStop, location)) return previousStop;
    }

    if (nextStop.getBlockSequence() + 1 < stopTimes.size()) {
      final BlockStopTimeEntry nextNextStop = stopTimes.get(nextStop.getBlockSequence() + 1);
      if (tripChangesBetweenPrevAndNextStop(stopTimes, nextNextStop, location)) return nextNextStop;
    }

    if (nextStop.getBlockSequence() + 2 < stopTimes.size()) {
      final BlockStopTimeEntry nextNextStop = stopTimes.get(nextStop.getBlockSequence() + 2);
      if (tripChangesBetweenPrevAndNextStop(stopTimes, nextNextStop, location)) return nextNextStop;
    }

    if (optimistic) {
      // if closestStop doesn't make sense, look to previous trip
      // we may have just switched to a new trip and the distance along block
      // may not make sense -- the proximity to the stop needs to be considered
      // for this to work
      if (location.getActiveTrip().getPreviousTrip() != null) {
        final BlockStopTimeEntry lastTripLastStop =
            Iterables.getLast(location.getActiveTrip().getPreviousTrip().getStopTimes());
        return lastTripLastStop;
      }
    }

    return null;
  }