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 method 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; _endIndex = inEndIndex;
54 _numPoints = inEndIndex - inStartIndex + 1;
55 _totalAltitudeRange = new AltitudeRange();
56 _movingAltitudeRange = new AltitudeRange();
57 _gentleAltitudeRange = new AltitudeRange();
58 _steepAltitudeRange = new AltitudeRange();
59 DataPoint prevPoint = null;
60 Altitude prevAltitude = null;
61 _totalDistanceRads = _movingDistanceRads = 0.0;
62 double radsSinceLastAltitude = 0.0;
63 _movingMilliseconds = 0L;
65 // Loop over the points in the range
66 for (int i=inStartIndex; i<= inEndIndex; i++)
68 DataPoint p = inTrack.getPoint(i);
69 if (p == null) return false;
70 // ignore all waypoints
71 if (p.isWaypoint()) continue;
73 if (p.getSegmentStart()) {_numSegments++;}
74 // Get the distance to the previous track point
75 if (prevPoint != null)
77 double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
78 _totalDistanceRads += rads;
79 if (!p.getSegmentStart()) {
80 _movingDistanceRads += rads;
82 // Keep track of rads since last point with an altitude
83 radsSinceLastAltitude += rads;
85 // Get the altitude difference to the previous track point
88 Altitude altitude = p.getAltitude();
89 _totalAltitudeRange.addValue(altitude);
90 if (p.getSegmentStart()) {
91 _movingAltitudeRange.ignoreValue(altitude);
95 _movingAltitudeRange.addValue(altitude);
96 if (prevAltitude != null)
98 // Work out gradient, see whether to ignore/add to gentle or steep
99 double heightDiff = altitude.getMetricValue() - prevAltitude.getMetricValue();
100 double metricDist = Distance.convertRadiansToDistance(radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
101 final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
103 _steepAltitudeRange.ignoreValue(prevAltitude);
104 _steepAltitudeRange.addValue(altitude);
107 _gentleAltitudeRange.ignoreValue(prevAltitude);
108 _gentleAltitudeRange.addValue(altitude);
112 prevAltitude = altitude;
113 radsSinceLastAltitude = 0.0;
116 if (p.hasTimestamp())
118 if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
119 _earliestTimestamp = p.getTimestamp();
121 if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
122 _latestTimestamp = p.getTimestamp();
124 // Work out duration without segment gaps
125 if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
127 long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
128 if (millisLater < 0) {_timesOutOfSequence = true;}
130 _movingMilliseconds += millisLater;
135 _timesIncomplete = true;
144 /** @return true if results are valid */
145 public boolean isValid() {
149 /** @return start index of range */
150 public int getStartIndex() {
154 /** @return end index of range */
155 public int getEndIndex() {
159 /** @return number of points in range */
160 public int getNumPoints() {
164 /** @return number of segments in range */
165 public int getNumSegments() {
169 /** @return altitude range of range including segment gaps */
170 public AltitudeRange getTotalAltitudeRange() {
171 return _totalAltitudeRange;
174 /** @return altitude range of range just within segments */
175 public AltitudeRange getMovingAltitudeRange() {
176 return _movingAltitudeRange;
179 /** @return altitude range of range just considering low gradient bits */
180 public AltitudeRange getGentleAltitudeRange() {
181 return _gentleAltitudeRange;
184 /** @return altitude range of range just considering high gradient bits */
185 public AltitudeRange getSteepAltitudeRange() {
186 return _steepAltitudeRange;
189 /** @return the earliest timestamp found */
190 public Timestamp getEarliestTimestamp() {
191 return _earliestTimestamp;
194 /** @return the latest timestamp found */
195 public Timestamp getLatestTimestamp() {
196 return _latestTimestamp;
199 /** @return total number of seconds in the range */
200 public long getTotalDurationInSeconds()
202 if (_earliestTimestamp != null && _latestTimestamp != null) {
203 return _latestTimestamp.getSecondsSince(_earliestTimestamp);
208 /** @return number of seconds within the segments of the range */
209 public long getMovingDurationInSeconds()
211 return _movingMilliseconds / 1000;
214 /** @return true if any timestamps are missing */
215 public boolean getTimestampsIncomplete() {
216 return _timesIncomplete;
219 /** @return true if any timestamps are out of sequence */
220 public boolean getTimestampsOutOfSequence() {
221 return _timesOutOfSequence;
224 /** @return total distance in the current distance units (km or mi) */
225 public double getTotalDistance() {
226 return Distance.convertRadiansToDistance(_totalDistanceRads);
229 /** @return moving distance in the current distance units (km or mi) */
230 public double getMovingDistance() {
231 return Distance.convertRadiansToDistance(_movingDistanceRads);
234 /** @return moving distance in km */
235 public double getMovingDistanceKilometres() {
236 return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
239 /** @return the total gradient in % (including segment gaps) */
240 public double getTotalGradient()
242 double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
243 if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
244 return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
249 /** @return the moving gradient in % (ignoring segment gaps) */
250 public double getMovingGradient()
252 double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
253 if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
254 return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
259 /** @return the total vertical speed (including segment gaps) in current vspeed units */
260 public double getTotalVerticalSpeed()
262 long time = getTotalDurationInSeconds();
263 if (time > 0 && _totalAltitudeRange.hasRange()) {
264 return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
269 /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
270 public double getMovingVerticalSpeed()
272 long time = getMovingDurationInSeconds();
273 if (time > 0 && _movingAltitudeRange.hasRange()) {
274 return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();