content: package tim.prune.data;
  
  /**
   * Class to do additional range calculations including gradients
   * Used by full details display as well as the EstimateTime functions.
   */
  public class RangeStatsWithGradients extends RangeStats
  {
  	private final AltitudeRange _gentleAltitudeRange;
  	private final AltitudeRange _steepAltitudeRange;
  	private Altitude _prevAltitude = null;
  	private double _radsSinceLastAltitude = 0.0;
  
  	private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
  
  
  	/**
  	 * Constructor
  	 * @param inAltitudeTolerance altitude tolerance in metres
  	 */
  	public RangeStatsWithGradients(int inAltitudeTolerance)
  	{
  		super(inAltitudeTolerance);
  		_gentleAltitudeRange = new AltitudeRange(inAltitudeTolerance);
  		_steepAltitudeRange = new AltitudeRange(inAltitudeTolerance);
  	}
  
  	/**
  	 * Constructor
  	 * @param inTrack track object
  	 * @param inStartIndex start index
  	 * @param inEndIndex end index
  	 * @param inAltitudeTolerance altitude tolerance in metres
  	 */
  	public RangeStatsWithGradients(Track inTrack, int inStartIndex, int inEndIndex, int inAltitudeTolerance)
  	{
  		this(inAltitudeTolerance);
  		populateFromTrack(inTrack, inStartIndex, inEndIndex);
  	}
  
  	/**
  	 * Add the given point to the calculations
  	 * @param inPoint incoming point
  	 */
  	protected void doFurtherCalculations(DataPoint inPoint)
  	{
  		if (_prevPoint != null)
  		{
  			// Keep track of rads since last point with an altitude
  			double rads = DataPoint.calculateRadiansBetween(_prevPoint, inPoint);
  			_radsSinceLastAltitude += rads;
  		}
  
  		if (inPoint.hasAltitude())
  		{
  			Altitude altitude = inPoint.getAltitude();
  
  			if (!inPoint.getSegmentStart() && _prevAltitude != null)
  			{
  				// Work out gradient, see whether to ignore/add to gentle or steep
  				double heightDiff = altitude.getMetricValue() - _prevAltitude.getMetricValue();
  				double metricDist = Distance.convertRadiansToDistance(_radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
  				final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
  				if (isSteep)
  				{
  					_steepAltitudeRange.ignoreValue(_prevAltitude);
  					_steepAltitudeRange.addValue(altitude);
  				}
  				else
  				{
  					_gentleAltitudeRange.ignoreValue(_prevAltitude);
  					_gentleAltitudeRange.addValue(altitude);
  				}
  			}
  			_prevAltitude = altitude;
  			_radsSinceLastAltitude = 0.0;
  		}
  
  	}
  
  	/** @return altitude range of range just considering low gradient bits */
  	public AltitudeRange getGentleAltitudeRange() {
  		return _gentleAltitudeRange;
  	}
  
  	/** @return altitude range of range just considering high gradient bits */
  	public AltitudeRange getSteepAltitudeRange() {
  		return _steepAltitudeRange;
  	}
  
  	/** @return the total gradient in % (including segment gaps) */
  	public double getTotalGradient()
  	{
  		double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
  		if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
  			return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
  		}
  		return 0.0;
  	}
  
  	/** @return the moving gradient in % (ignoring segment gaps) */
  	public double getMovingGradient()
  	{
  		double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
  		if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
  			return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
  		}
  		return 0.0;
  	}
  }
  