]> gitweb.fperrin.net Git - GpsPrune.git/blobdiff - src/tim/prune/data/RangeStats.java
Moved source into separate src directory due to popular request
[GpsPrune.git] / src / tim / prune / data / RangeStats.java
diff --git a/src/tim/prune/data/RangeStats.java b/src/tim/prune/data/RangeStats.java
new file mode 100644 (file)
index 0000000..13ecaad
--- /dev/null
@@ -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;
+       }
+}