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 private boolean _valid = false;
13 private int _numPoints = 0;
14 private int _startIndex = 0, _endIndex = 0;
15 private int _numSegments = 0;
16 private AltitudeRange _totalAltitudeRange = null, _movingAltitudeRange = null;
17 private AltitudeRange _gentleAltitudeRange = null, _steepAltitudeRange = null;
18 private Timestamp _earliestTimestamp = null, _latestTimestamp = null;
19 private long _movingMilliseconds = 0L;
20 private boolean _timestampsIncomplete = false;
21 private double _totalDistanceRads = 0.0, _movingDistanceRads = 0.0;
22 // Note, maximum speed is not calculated here, use the SpeedData method instead
24 private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
29 * @param inTrack track to compile data for
30 * @param inStartIndex start index of range to examine
31 * @param inEndIndex end index (inclusive) of range to examine
33 public RangeStats(Track inTrack, int inStartIndex, int inEndIndex)
35 if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex
36 && inEndIndex < inTrack.getNumPoints())
38 _valid = calculateStats(inTrack, inStartIndex, inEndIndex);
43 * Calculate the statistics and populate the member variables with the results
44 * @param inTrack track
45 * @param inStartIndex start index of range
46 * @param inEndIndex end index (inclusive) of range
47 * @return true on success
49 private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex)
51 _startIndex = inStartIndex; _endIndex = inEndIndex;
52 _numPoints = inEndIndex - inStartIndex + 1;
53 _totalAltitudeRange = new AltitudeRange();
54 _movingAltitudeRange = new AltitudeRange();
55 _gentleAltitudeRange = new AltitudeRange();
56 _steepAltitudeRange = new AltitudeRange();
57 DataPoint prevPoint = null;
58 Altitude prevAltitude = null;
59 _totalDistanceRads = _movingDistanceRads = 0.0;
60 double radsSinceLastAltitude = 0.0;
61 _movingMilliseconds = 0L;
63 // Loop over the points in the range
64 for (int i=inStartIndex; i<= inEndIndex; i++)
66 DataPoint p = inTrack.getPoint(i);
67 if (p == null) return false;
68 // ignore all waypoints
69 if (p.isWaypoint()) continue;
71 if (p.getSegmentStart()) {_numSegments++;}
72 // Get the distance to the previous track point
73 if (prevPoint != null)
75 double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
76 _totalDistanceRads += rads;
77 if (!p.getSegmentStart()) {
78 _movingDistanceRads += rads;
80 // Keep track of rads since last point with an altitude
81 radsSinceLastAltitude += rads;
83 // Get the altitude difference to the previous track point
86 Altitude altitude = p.getAltitude();
87 _totalAltitudeRange.addValue(altitude);
88 if (p.getSegmentStart()) {
89 _movingAltitudeRange.ignoreValue(altitude);
93 _movingAltitudeRange.addValue(altitude);
94 if (prevAltitude != null)
96 // Work out gradient, see whether to ignore/add to gentle or steep
97 double heightDiff = altitude.getMetricValue() - prevAltitude.getMetricValue();
98 double metricDist = Distance.convertRadiansToDistance(radsSinceLastAltitude, UnitSetLibrary.UNITS_METRES);
99 final boolean isSteep = metricDist < 0.001 || (Math.abs(heightDiff / metricDist) > STEEP_ANGLE);
101 _steepAltitudeRange.ignoreValue(prevAltitude);
102 _steepAltitudeRange.addValue(altitude);
105 _gentleAltitudeRange.ignoreValue(prevAltitude);
106 _gentleAltitudeRange.addValue(altitude);
110 prevAltitude = altitude;
111 radsSinceLastAltitude = 0.0;
114 if (p.hasTimestamp())
116 if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
117 _earliestTimestamp = p.getTimestamp();
119 if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
120 _latestTimestamp = p.getTimestamp();
122 // Work out duration without segment gaps
123 if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
125 long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
126 if (millisLater < 0) {_timestampsIncomplete = true;}
127 _movingMilliseconds += millisLater;
130 else if (!p.getSegmentStart()) {
131 _timestampsIncomplete = true;
140 /** @return true if results are valid */
141 public boolean isValid() {
145 /** @return start index of range */
146 public int getStartIndex() {
150 /** @return end index of range */
151 public int getEndIndex() {
155 /** @return number of points in range */
156 public int getNumPoints() {
160 /** @return number of segments in range */
161 public int getNumSegments() {
165 /** @return altitude range of range including segment gaps */
166 public AltitudeRange getTotalAltitudeRange() {
167 return _totalAltitudeRange;
170 /** @return altitude range of range just within segments */
171 public AltitudeRange getMovingAltitudeRange() {
172 return _movingAltitudeRange;
175 /** @return altitude range of range just considering low gradient bits */
176 public AltitudeRange getGentleAltitudeRange() {
177 return _gentleAltitudeRange;
180 /** @return altitude range of range just considering high gradient bits */
181 public AltitudeRange getSteepAltitudeRange() {
182 return _steepAltitudeRange;
185 /** @return the earliest timestamp found */
186 public Timestamp getEarliestTimestamp() {
187 return _earliestTimestamp;
190 /** @return the latest timestamp found */
191 public Timestamp getLatestTimestamp() {
192 return _latestTimestamp;
195 /** @return total number of seconds in the range */
196 public long getTotalDurationInSeconds()
198 if (_earliestTimestamp != null && _latestTimestamp != null) {
199 return _latestTimestamp.getSecondsSince(_earliestTimestamp);
204 /** @return number of seconds within the segments of the range */
205 public long getMovingDurationInSeconds()
207 return _movingMilliseconds / 1000;
210 /** @return true if any timestamps are missing or out of sequence */
211 public boolean getTimestampsIncomplete() {
212 return _timestampsIncomplete;
215 /** @return total distance in the current distance units (km or mi) */
216 public double getTotalDistance() {
217 return Distance.convertRadiansToDistance(_totalDistanceRads);
220 /** @return moving distance in the current distance units (km or mi) */
221 public double getMovingDistance() {
222 return Distance.convertRadiansToDistance(_movingDistanceRads);
225 /** @return moving distance in km */
226 public double getMovingDistanceKilometres() {
227 return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
230 /** @return the total gradient in % (including segment gaps) */
231 public double getTotalGradient()
233 double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
234 if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
235 return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
240 /** @return the moving gradient in % (ignoring segment gaps) */
241 public double getMovingGradient()
243 double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
244 if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
245 return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
250 /** @return the total vertical speed (including segment gaps) in current vspeed units */
251 public double getTotalVerticalSpeed()
253 long time = getTotalDurationInSeconds();
254 if (time > 0 && _totalAltitudeRange.hasRange()) {
255 return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
260 /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
261 public double getMovingVerticalSpeed()
263 long time = getMovingDurationInSeconds();
264 if (time > 0 && _movingAltitudeRange.hasRange()) {
265 return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();