package tim.prune.data; import tim.prune.config.Config; /** * Class to do calculations of range statistics such as distances, durations, * speeds, gradients etc, and to hold the results of the calculations. * Used by FullRangeDetails as well as the EstimateTime functions. */ public class RangeStats { // MAYBE: Split into basic stats (quick to calculate, for detailsdisplay) and full stats (for other two) private boolean _valid = false; private int _numPoints = 0; private int _startIndex = 0, _endIndex = 0; private int _numSegments = 0; private AltitudeRange _totalAltitudeRange = null, _movingAltitudeRange = null; private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null; private Timestamp _earliestTimestamp = null, _latestTimestamp = null; private long _movingMilliseconds = 0L; private boolean _timesIncomplete = false; private boolean _timesOutOfSequence = false; private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0; // Note, maximum speed is not calculated here, use the SpeedData class instead private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep /** * Constructor * @param inTrack track to compile data for * @param inStartIndex start index of range to examine * @param inEndIndex end index (inclusive) of range to examine */ public RangeStats(Track inTrack, int inStartIndex, int inEndIndex) { if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex && inEndIndex < inTrack.getNumPoints()) { _valid = calculateStats(inTrack, inStartIndex, inEndIndex); } } /** * Calculate the statistics and populate the member variables with the results * @param inTrack track * @param inStartIndex start index of range * @param inEndIndex end index (inclusive) of range * @return true on success */ private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex) { _startIndex = inStartIndex; _endIndex = inEndIndex; _numPoints = inEndIndex - inStartIndex + 1; _totalAltitudeRange = new AltitudeRange(); _movingAltitudeRange = new AltitudeRange(); _gentleAltitudeRange = new AltitudeRange(); _steepAltitudeRange = new AltitudeRange(); DataPoint prevPoint = null; Altitude prevAltitude = null; _totalDistanceRads = _movingDistanceRads = 0.0; double radsSinceLastAltitude = 0.0; _movingMilliseconds = 0L; // Loop over the points in the range for (int i=inStartIndex; i<= inEndIndex; i++) { DataPoint p = inTrack.getPoint(i); if (p == null) return false; // ignore all waypoints if (p.isWaypoint()) continue; if (p.getSegmentStart()) { _numSegments++; } // Get the distance to the previous track point if (prevPoint != null) { double rads = DataPoint.calculateRadiansBetween(prevPoint, p); _totalDistanceRads += rads; if (!p.getSegmentStart()) { _movingDistanceRads += rads; } // Keep track of rads since last point with an altitude radsSinceLastAltitude += rads; } // Get the altitude difference to the previous track point if (p.hasAltitude()) { Altitude altitude = p.getAltitude(); _totalAltitudeRange.addValue(altitude); if (p.getSegmentStart()) { _movingAltitudeRange.ignoreValue(altitude); } else { _movingAltitudeRange.addValue(altitude); if (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; } if (p.hasTimestamp()) { if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) { _earliestTimestamp = p.getTimestamp(); } if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) { _latestTimestamp = p.getTimestamp(); } // Work out duration without segment gaps if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp()) { long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp()); if (millisLater < 0) {_timesOutOfSequence = true;} else { _movingMilliseconds += millisLater; } } } else { _timesIncomplete = true; } prevPoint = p; } return true; } /** @return true if results are valid */ public boolean isValid() { return _valid; } /** @return start index of range */ public int getStartIndex() { return _startIndex; } /** @return end index of range */ public int getEndIndex() { return _endIndex; } /** @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 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 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 current distance units (km or mi) */ public double getTotalDistance() { return Distance.convertRadiansToDistance(_totalDistanceRads); } /** @return moving distance in the current distance units (km or mi) */ public double getMovingDistance() { return Distance.convertRadiansToDistance(_movingDistanceRads); } /** @return moving distance in km */ public double getMovingDistanceKilometres() { return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES); } /** @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; } /** @return the total vertical speed (including segment gaps) in current vspeed units */ public double getTotalVerticalSpeed() { long time = getTotalDurationInSeconds(); if (time > 0 && _totalAltitudeRange.hasRange()) { return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd(); } return 0.0; } /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */ public double getMovingVerticalSpeed() { long time = getMovingDurationInSeconds(); if (time > 0 && _movingAltitudeRange.hasRange()) { return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd(); } return 0.0; } }