X-Git-Url: http://gitweb.fperrin.net/?p=GpsPrune.git;a=blobdiff_plain;f=src%2Ftim%2Fprune%2Fdata%2FRangeStats.java;fp=src%2Ftim%2Fprune%2Fdata%2FRangeStats.java;h=13ecaade10d4e6a8d0eccc87a383762ae399cc00;hp=0000000000000000000000000000000000000000;hb=ce6f2161b8596f7018d6a76bff79bc9e571f35fd;hpb=2d8cb72e84d5cc1089ce77baf1e34ea3ea2f8465 diff --git a/src/tim/prune/data/RangeStats.java b/src/tim/prune/data/RangeStats.java new file mode 100644 index 0000000..13ecaad --- /dev/null +++ b/src/tim/prune/data/RangeStats.java @@ -0,0 +1,281 @@ +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; + } +}