1 package tim.prune.data;
3 import tim.prune.config.Config;
6 * Class to do calculations of range statistics such as distances, durations,
7 * speeds, gradients etc, and to hold the results of the calculations.
8 * Used by FullRangeDetails as well as the EstimateTime functions.
10 public class RangeStats
12 // MAYBE: Split into basic stats (quick to calculate, for detailsdisplay) and full stats (for other two)
13 private boolean _valid = false;
14 private int _numPoints = 0;
15 private int _startIndex = 0, _endIndex = 0;
16 private int _numSegments = 0;
17 private AltitudeRange _totalAltitudeRange = null, _movingAltitudeRange = null;
18 private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null;
19 private Timestamp _earliestTimestamp = null, _latestTimestamp = null;
20 private long _movingMilliseconds = 0L;
21 private boolean _timesIncomplete = false;
22 private boolean _timesOutOfSequence = false;
23 private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
24 // Note, maximum speed is not calculated here, use the SpeedData class instead
26 private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
31 * @param inTrack track to compile data for
32 * @param inStartIndex start index of range to examine
33 * @param inEndIndex end index (inclusive) of range to examine
35 public RangeStats(Track inTrack, int inStartIndex, int inEndIndex)
37 if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex
38 && inEndIndex < inTrack.getNumPoints())
40 _valid = calculateStats(inTrack, inStartIndex, inEndIndex);
45 * Calculate the statistics and populate the member variables with the results
46 * @param inTrack track
47 * @param inStartIndex start index of range
48 * @param inEndIndex end index (inclusive) of range
49 * @return true on success
51 private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex)
53 _startIndex = inStartIndex;
54 _endIndex = inEndIndex;
55 _numPoints = inEndIndex - inStartIndex + 1;
56 _totalAltitudeRange = new AltitudeRange();
57 _movingAltitudeRange = new AltitudeRange();
58 _gentleAltitudeRange = new AltitudeRange();
59 _steepAltitudeRange = new AltitudeRange();
60 DataPoint prevPoint = null;
61 Altitude prevAltitude = null;
62 _totalDistanceRads = _movingDistanceRads = 0.0;
63 double radsSinceLastAltitude = 0.0;
64 _movingMilliseconds = 0L;
66 // Loop over the points in the range
67 for (int i=inStartIndex; i<= inEndIndex; i++)
69 DataPoint p = inTrack.getPoint(i);
70 if (p == null) return false;
71 // ignore all waypoints
72 if (p.isWaypoint()) continue;
74 if (p.getSegmentStart()) {
77 // Get the distance to the previous track point
78 if (prevPoint != null)
80 double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
81 _totalDistanceRads += rads;
82 if (!p.getSegmentStart()) {
83 _movingDistanceRads += rads;
85 // Keep track of rads since last point with an altitude
86 radsSinceLastAltitude += rads;
88 // Get the altitude difference to the previous track point
91 Altitude altitude = p.getAltitude();
92 _totalAltitudeRange.addValue(altitude);
93 if (p.getSegmentStart()) {
94 _movingAltitudeRange.ignoreValue(altitude);
98 _movingAltitudeRange.addValue(altitude);
99 if (prevAltitude != null)
101 // Work out gradient, see whether to ignore/add to gentle or steep
102 double heightDiff = altitude.getMetricValue() - prevAltitude.getMetricValue();
103 double metricDist = Distance.convertRadiansToDistance(radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
104 final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
106 _steepAltitudeRange.ignoreValue(prevAltitude);
107 _steepAltitudeRange.addValue(altitude);
110 _gentleAltitudeRange.ignoreValue(prevAltitude);
111 _gentleAltitudeRange.addValue(altitude);
115 prevAltitude = altitude;
116 radsSinceLastAltitude = 0.0;
119 if (p.hasTimestamp())
121 if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
122 _earliestTimestamp = p.getTimestamp();
124 if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
125 _latestTimestamp = p.getTimestamp();
127 // Work out duration without segment gaps
128 if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
130 long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
131 if (millisLater < 0) {_timesOutOfSequence = true;}
133 _movingMilliseconds += millisLater;
138 _timesIncomplete = true;
147 /** @return true if results are valid */
148 public boolean isValid() {
152 /** @return start index of range */
153 public int getStartIndex() {
157 /** @return end index of range */
158 public int getEndIndex() {
162 /** @return number of points in range */
163 public int getNumPoints() {
167 /** @return number of segments in range */
168 public int getNumSegments() {
172 /** @return altitude range of range including segment gaps */
173 public AltitudeRange getTotalAltitudeRange() {
174 return _totalAltitudeRange;
177 /** @return altitude range of range just within segments */
178 public AltitudeRange getMovingAltitudeRange() {
179 return _movingAltitudeRange;
182 /** @return altitude range of range just considering low gradient bits */
183 public AltitudeRange getGentleAltitudeRange() {
184 return _gentleAltitudeRange;
187 /** @return altitude range of range just considering high gradient bits */
188 public AltitudeRange getSteepAltitudeRange() {
189 return _steepAltitudeRange;
192 /** @return the earliest timestamp found */
193 public Timestamp getEarliestTimestamp() {
194 return _earliestTimestamp;
197 /** @return the latest timestamp found */
198 public Timestamp getLatestTimestamp() {
199 return _latestTimestamp;
202 /** @return total number of seconds in the range */
203 public long getTotalDurationInSeconds()
205 if (_earliestTimestamp != null && _latestTimestamp != null) {
206 return _latestTimestamp.getSecondsSince(_earliestTimestamp);
211 /** @return number of seconds within the segments of the range */
212 public long getMovingDurationInSeconds()
214 return _movingMilliseconds / 1000;
217 /** @return true if any timestamps are missing */
218 public boolean getTimestampsIncomplete() {
219 return _timesIncomplete;
222 /** @return true if any timestamps are out of sequence */
223 public boolean getTimestampsOutOfSequence() {
224 return _timesOutOfSequence;
227 /** @return total distance in the current distance units (km or mi) */
228 public double getTotalDistance() {
229 return Distance.convertRadiansToDistance(_totalDistanceRads);
232 /** @return moving distance in the current distance units (km or mi) */
233 public double getMovingDistance() {
234 return Distance.convertRadiansToDistance(_movingDistanceRads);
237 /** @return moving distance in km */
238 public double getMovingDistanceKilometres() {
239 return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
242 /** @return the total gradient in % (including segment gaps) */
243 public double getTotalGradient()
245 double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
246 if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
247 return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
252 /** @return the moving gradient in % (ignoring segment gaps) */
253 public double getMovingGradient()
255 double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
256 if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
257 return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
262 /** @return the total vertical speed (including segment gaps) in current vspeed units */
263 public double getTotalVerticalSpeed()
265 long time = getTotalDurationInSeconds();
266 if (time > 0 && _totalAltitudeRange.hasRange()) {
267 return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
272 /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
273 public double getMovingVerticalSpeed()
275 long time = getMovingDurationInSeconds();
276 if (time > 0 && _movingAltitudeRange.hasRange()) {
277 return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();