content: package tim.prune.data;
  
  /**
   * Class to do basic calculations of range statistics such as distances, durations,
   * and altitude ranges, and to hold the results of the calculations.
   */
  public class RangeStats
  {
  	private int _numPoints   = 0;
  	private int _numSegments = 0;
  	private boolean _foundTrackPoint = false;
  	protected final AltitudeRange _totalAltitudeRange;
  	protected final AltitudeRange _movingAltitudeRange;
  	private Timestamp _earliestTimestamp = null, _latestTimestamp = null, _movingTimestamp = null;
  	private long _movingMilliseconds = 0L;
  	private boolean _timesIncomplete = false;
  	private boolean _timesOutOfSequence = false;
  	protected double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
  	protected DataPoint _prevPoint = null;
  
  
  	/** Constructor */
  	public RangeStats(int inAltitudeTolerance)
  	{
  		_totalAltitudeRange = new AltitudeRange(inAltitudeTolerance);
  		_movingAltitudeRange = new AltitudeRange(inAltitudeTolerance);
  	}
  
  	/**
  	 * Constructor giving Track
  	 * @param inTrack track object to calculate with
  	 */
  	public RangeStats(Track inTrack, int inStartIndex, int inEndIndex, int inAltitudeTolerance)
  	{
  		this(inAltitudeTolerance);
  		populateFromTrack(inTrack, inStartIndex, inEndIndex);
  	}
  
  	/**
  	 * Add the specified points from the given track to the calculations
  	 * @param inTrack track object
  	 * @param inStartIndex start index (inclusive)
  	 * @param inEndIndex end index (inclusive)
  	 */
  	protected void populateFromTrack(Track inTrack, int inStartIndex, int inEndIndex)
  	{
  		for (int i=inStartIndex; i<=inEndIndex; i++) {
  			addPoint(inTrack.getPoint(i));
  		}
  	}
  
  	/**
  	 * @param inPoint point to add to the calculations
  	 */
  	public void addPoint(DataPoint inPoint)
  	{
  		if (inPoint == null) {
  			return;
  		}
  		_numPoints++;
  		// ignore all waypoints
  		if (inPoint.isWaypoint()) {
  			return;
  		}
  		if (inPoint.getSegmentStart() || !_foundTrackPoint) {
  			_numSegments++;
  		}
  		_foundTrackPoint = true;
  		// Get the distance to the previous track point
  		if (_prevPoint != null)
  		{
  			double rads = DataPoint.calculateRadiansBetween(_prevPoint, inPoint);
  			_totalDistanceRads += rads;
  			if (!inPoint.getSegmentStart()) {
  				_movingDistanceRads += rads;
  			}
  		}
  
  		// timestamps
  		if (inPoint.getSegmentStart())
  		{
  			// reset movingTimestamp for moving time at the start
  			// of each segment
  			_movingTimestamp = null;
  		}
  		if (inPoint.hasTimestamp())
  		{
  			Timestamp currTstamp = inPoint.getTimestamp();
  			if (_earliestTimestamp == null || currTstamp.isBefore(_earliestTimestamp)) {
  				_earliestTimestamp = currTstamp;
  			}
  			if (_latestTimestamp == null || currTstamp.isAfter(_latestTimestamp)) {
  				_latestTimestamp = currTstamp;
  			}
  
  			// Work out duration without segment gaps
  			if (_movingTimestamp != null)
  			{
  				long millisLater = currTstamp.getMillisecondsSince(_movingTimestamp);
  				if (millisLater < 0) {
  					_timesOutOfSequence = true;
  				}
  				else {
  					_movingMilliseconds += millisLater;
  				}
  			}
  			_movingTimestamp = currTstamp;
  		}
  		else {
  			_timesIncomplete = true;
  		}
  
  		// altitudes
  		if (inPoint.hasAltitude())
  		{
  			Altitude altitude = inPoint.getAltitude();
  			_totalAltitudeRange.addValue(altitude);
  			if (inPoint.getSegmentStart()) {
  				_movingAltitudeRange.ignoreValue(altitude);
  			}
  			else
  			{
  				_movingAltitudeRange.addValue(altitude);
  			}
  		}
  
  		// allow child classes to do additional calculations
  		doFurtherCalculations(inPoint);
  
  		_prevPoint = inPoint;
  	}
  
  	/**
  	 * Hook for subclasses to do what they want in addition
  	 * @param inPoint incoming point
  	 */
  	protected void doFurtherCalculations(DataPoint inPoint)
  	{
  	}
  
  
  	/** @return number of points in range */
  	public int getNumPoints() {
  		return _numPoints;
  	}
  
  	/** @return number of segments in range */
  	public int getNumSegments() {
  		return _numSegments;
  	}
  
  	/** @return altitude range of range including segment gaps */
  	public AltitudeRange getTotalAltitudeRange() {
  		return _totalAltitudeRange;
  	}
  
  	/** @return altitude range of range just within segments */
  	public AltitudeRange getMovingAltitudeRange() {
  		return _movingAltitudeRange;
  	}
  
  	/** @return the earliest timestamp found */
  	public Timestamp getEarliestTimestamp() {
  		return _earliestTimestamp;
  	}
  
  	/** @return the latest timestamp found */
  	public Timestamp getLatestTimestamp() {
  		return _latestTimestamp;
  	}
  
  	/** @return total number of seconds in the range */
  	public long getTotalDurationInSeconds()
  	{
  		if (_earliestTimestamp != null && _latestTimestamp != null) {
  			return _latestTimestamp.getSecondsSince(_earliestTimestamp);
  		}
  		return 0L;
  	}
  
  	/** @return number of seconds within the segments of the range */
  	public long getMovingDurationInSeconds() {
  		return _movingMilliseconds / 1000;
  	}
  
  	/** @return true if any timestamps are missing */
  	public boolean getTimestampsIncomplete() {
  		return _timesIncomplete;
  	}
  
  	/** @return true if any timestamps are out of sequence */
  	public boolean getTimestampsOutOfSequence() {
  		return _timesOutOfSequence;
  	}
  
  	/** @return total distance in the given distance units (km or mi) */
  	public double getTotalDistance(Unit inUnit) {
  		return Distance.convertRadiansToDistance(_totalDistanceRads, inUnit);
  	}
  
  	/** @return moving distance in the given distance units (km or mi) */
  	public double getMovingDistance(Unit inUnit) {
  		return Distance.convertRadiansToDistance(_movingDistanceRads, inUnit);
  	}
  
  	/** @return moving distance in km */
  	public double getMovingDistanceKilometres() {
  		return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
  	}
  
  	/**
  	 * @return the total vertical speed (including segment gaps) in metric units
  	 */
  	public double getTotalVerticalSpeed()
  	{
  		long time = getTotalDurationInSeconds();
  		if (time > 0 && _totalAltitudeRange.hasRange()) {
  			return _totalAltitudeRange.getMetricHeightDiff() / time;
  		}
  		return 0.0;
  	}
  
  	/**
  	 * @return the moving vertical speed (ignoring segment gaps) in metric units
  	 */
  	public double getMovingVerticalSpeed()
  	{
  		long time = getMovingDurationInSeconds();
  		if (time > 0 && _movingAltitudeRange.hasRange()) {
  			return _movingAltitudeRange.getMetricHeightDiff() / time;
  		}
  		return 0.0;
  	}
  }
  