/**
   * This method determines if an observation record is within a certain radius,
   * _terminalSearchRadius argument, of a stop at the start or end of a block. <br>
   * Note: all trips' stops within the radius are checked.
   *
   * @param record
   * @return whether the observation is within the search radius
   */
  public boolean isAtPotentialBlockTerminal(NycRawLocationRecord record) {

    final CoordinatePoint loc = new CoordinatePoint(record.getLatitude(), record.getLongitude());
    final CoordinateBounds bounds = SphericalGeometryLibrary.bounds(loc, _terminalSearchRadius);

    final List<BlockConfigurationEntry> blocks = new ArrayList<BlockConfigurationEntry>();
    final List<StopEntry> stops = _transitGraphDao.getStopsByLocation(bounds);
    for (final StopEntry stop : stops) {
      final List<BlockStopTimeIndex> stopTimeIndices =
          _blockIndexService.getStopTimeIndicesForStop(stop);
      for (final BlockStopTimeIndex stopTimeIndex : stopTimeIndices) {
        blocks.addAll(stopTimeIndex.getBlockConfigs());
      }
    }

    for (final BlockConfigurationEntry block : blocks) {

      final StopEntry firstStop = block.getStopTimes().get(0).getStopTime().getStop();

      final double firstStopDist = TurboButton.distance(loc, firstStop.getStopLocation());

      final int lastStopIdx = block.getStopTimes().size() - 1;

      final StopEntry lastStop = block.getStopTimes().get(lastStopIdx).getStopTime().getStop();
      final double lastStopDist = TurboButton.distance(loc, lastStop.getStopLocation());

      if (firstStopDist <= _terminalSearchRadius || lastStopDist <= _terminalSearchRadius) {
        return true;
      }
    }

    return false;
  }
  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;
  }
  public double getDistanceToBlockLocation(Observation obs, BlockState blockState) {

    final ScheduledBlockLocation blockLocation = blockState.getBlockLocation();

    final CoordinatePoint blockStart = blockLocation.getLocation();
    final CoordinatePoint currentLocation = obs.getLocation();

    return TurboButton.distance(currentLocation, blockStart);
  }
  /**
   * This method determines if an observation record is within a certain radius,
   * _terminalSearchRadius argument, of a stop at the start or end of a given block.
   *
   * @param record
   * @param blockInstance
   * @return whether the observation is within the search radius
   */
  public static boolean isAtPotentialBlockTerminal(
      NycRawLocationRecord record, BlockInstance blockInstance) {

    final CoordinatePoint loc = new CoordinatePoint(record.getLatitude(), record.getLongitude());

    final StopEntry firstStop =
        blockInstance.getBlock().getStopTimes().get(0).getStopTime().getStop();

    final double firstStopDist = TurboButton.distance(loc, firstStop.getStopLocation());

    final int lastStopIdx = blockInstance.getBlock().getStopTimes().size() - 1;

    final StopEntry lastStop =
        blockInstance.getBlock().getStopTimes().get(lastStopIdx).getStopTime().getStop();
    final double lastStopDist = TurboButton.distance(loc, lastStop.getStopLocation());

    if (firstStopDist <= _terminalSearchRadius || lastStopDist <= _terminalSearchRadius) {
      return true;
    }

    return false;
  }
  private static double getAvgVelocityToNextStop(Observation obs, BlockState blockState) {

    StopTimeEntry stop = blockState.getBlockLocation().getNextStop().getStopTime();
    final double distToDest =
        TurboButton.distance(obs.getLocation(), stop.getStop().getStopLocation());
    final int currSchedTime =
        (int) (obs.getTime() - blockState.getBlockInstance().getServiceDate()) / 1000;

    final double expectedVelocity;
    if (currSchedTime >= stop.getArrivalTime()) {
      /*
       * Assumption here is that the driver will speed up to get to the stop on time.
       * TODO: check this.
       */
      expectedVelocity = _avgVelocity * 3d / 2d;
    } else {
      expectedVelocity = distToDest / (stop.getArrivalTime() - currSchedTime);
    }

    return expectedVelocity;
  }