]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/RangeStats.java
Version 16, February 2014
[GpsPrune.git] / tim / prune / data / RangeStats.java
1 package tim.prune.data;
2
3 import tim.prune.config.Config;
4
5 /**
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.
9  */
10 public class RangeStats
11 {
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
25
26         private static final double STEEP_ANGLE = 0.15; // gradient steeper than 15% counts as steep
27
28
29         /**
30          * Constructor
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
34          */
35         public RangeStats(Track inTrack, int inStartIndex, int inEndIndex)
36         {
37                 if (inTrack != null && inStartIndex >= 0 && inEndIndex > inStartIndex
38                         && inEndIndex < inTrack.getNumPoints())
39                 {
40                         _valid = calculateStats(inTrack, inStartIndex, inEndIndex);
41                 }
42         }
43
44         /**
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
50          */
51         private boolean calculateStats(Track inTrack, int inStartIndex, int inEndIndex)
52         {
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;
64
65                 // Loop over the points in the range
66                 for (int i=inStartIndex; i<= inEndIndex; i++)
67                 {
68                         DataPoint p = inTrack.getPoint(i);
69                         if (p == null) return false;
70                         // ignore all waypoints
71                         if (p.isWaypoint()) continue;
72
73                         if (p.getSegmentStart()) {_numSegments++;}
74                         // Get the distance to the previous track point
75                         if (prevPoint != null)
76                         {
77                                 double rads = DataPoint.calculateRadiansBetween(prevPoint, p);
78                                 _totalDistanceRads += rads;
79                                 if (!p.getSegmentStart()) {
80                                         _movingDistanceRads += rads;
81                                 }
82                                 // Keep track of rads since last point with an altitude
83                                 radsSinceLastAltitude += rads;
84                         }
85                         // Get the altitude difference to the previous track point
86                         if (p.hasAltitude())
87                         {
88                                 Altitude altitude = p.getAltitude();
89                                 _totalAltitudeRange.addValue(altitude);
90                                 if (p.getSegmentStart()) {
91                                         _movingAltitudeRange.ignoreValue(altitude);
92                                 }
93                                 else
94                                 {
95                                         _movingAltitudeRange.addValue(altitude);
96                                         if (prevAltitude != null)
97                                         {
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);
102                                                 if (isSteep) {
103                                                         _steepAltitudeRange.ignoreValue(prevAltitude);
104                                                         _steepAltitudeRange.addValue(altitude);
105                                                 }
106                                                 else {
107                                                         _gentleAltitudeRange.ignoreValue(prevAltitude);
108                                                         _gentleAltitudeRange.addValue(altitude);
109                                                 }
110                                         }
111                                 }
112                                 prevAltitude = altitude;
113                                 radsSinceLastAltitude = 0.0;
114                         }
115
116                         if (p.hasTimestamp())
117                         {
118                                 if (_earliestTimestamp == null || p.getTimestamp().isBefore(_earliestTimestamp)) {
119                                         _earliestTimestamp = p.getTimestamp();
120                                 }
121                                 if (_latestTimestamp == null || p.getTimestamp().isAfter(_latestTimestamp)) {
122                                         _latestTimestamp = p.getTimestamp();
123                                 }
124                                 // Work out duration without segment gaps
125                                 if (!p.getSegmentStart() && prevPoint != null && prevPoint.hasTimestamp())
126                                 {
127                                         long millisLater = p.getTimestamp().getMillisecondsSince(prevPoint.getTimestamp());
128                                         if (millisLater < 0) {_timesOutOfSequence = true;}
129                                         else {
130                                                 _movingMilliseconds += millisLater;
131                                         }
132                                 }
133                         }
134                         else {
135                                 _timesIncomplete = true;
136                         }
137
138                         prevPoint = p;
139                 }
140                 return true;
141         }
142
143
144         /** @return true if results are valid */
145         public boolean isValid() {
146                 return _valid;
147         }
148
149         /** @return start index of range */
150         public int getStartIndex() {
151                 return _startIndex;
152         }
153
154         /** @return end index of range */
155         public int getEndIndex() {
156                 return _endIndex;
157         }
158
159         /** @return number of points in range */
160         public int getNumPoints() {
161                 return _numPoints;
162         }
163
164         /** @return number of segments in range */
165         public int getNumSegments() {
166                 return _numSegments;
167         }
168
169         /** @return altitude range of range including segment gaps */
170         public AltitudeRange getTotalAltitudeRange() {
171                 return _totalAltitudeRange;
172         }
173
174         /** @return altitude range of range just within segments */
175         public AltitudeRange getMovingAltitudeRange() {
176                 return _movingAltitudeRange;
177         }
178
179         /** @return altitude range of range just considering low gradient bits */
180         public AltitudeRange getGentleAltitudeRange() {
181                 return _gentleAltitudeRange;
182         }
183
184         /** @return altitude range of range just considering high gradient bits */
185         public AltitudeRange getSteepAltitudeRange() {
186                 return _steepAltitudeRange;
187         }
188
189         /** @return the earliest timestamp found */
190         public Timestamp getEarliestTimestamp() {
191                 return _earliestTimestamp;
192         }
193
194         /** @return the latest timestamp found */
195         public Timestamp getLatestTimestamp() {
196                 return _latestTimestamp;
197         }
198
199         /** @return total number of seconds in the range */
200         public long getTotalDurationInSeconds()
201         {
202                 if (_earliestTimestamp != null && _latestTimestamp != null) {
203                         return _latestTimestamp.getSecondsSince(_earliestTimestamp);
204                 }
205                 return 0L;
206         }
207
208         /** @return number of seconds within the segments of the range */
209         public long getMovingDurationInSeconds()
210         {
211                 return _movingMilliseconds / 1000;
212         }
213
214         /** @return true if any timestamps are missing */
215         public boolean getTimestampsIncomplete() {
216                 return _timesIncomplete;
217         }
218
219         /** @return true if any timestamps are out of sequence */
220         public boolean getTimestampsOutOfSequence() {
221                 return _timesOutOfSequence;
222         }
223
224         /** @return total distance in the current distance units (km or mi) */
225         public double getTotalDistance() {
226                 return Distance.convertRadiansToDistance(_totalDistanceRads);
227         }
228
229         /** @return moving distance in the current distance units (km or mi) */
230         public double getMovingDistance() {
231                 return Distance.convertRadiansToDistance(_movingDistanceRads);
232         }
233
234         /** @return moving distance in km */
235         public double getMovingDistanceKilometres() {
236                 return Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_KILOMETRES);
237         }
238
239         /** @return the total gradient in % (including segment gaps) */
240         public double getTotalGradient()
241         {
242                 double dist = Distance.convertRadiansToDistance(_totalDistanceRads, UnitSetLibrary.UNITS_METRES);
243                 if (dist > 0.0 && _totalAltitudeRange.hasRange()) {
244                         return _totalAltitudeRange.getMetricHeightDiff() / dist * 100.0;
245                 }
246                 return 0.0;
247         }
248
249         /** @return the moving gradient in % (ignoring segment gaps) */
250         public double getMovingGradient()
251         {
252                 double dist = Distance.convertRadiansToDistance(_movingDistanceRads, UnitSetLibrary.UNITS_METRES);
253                 if (dist > 0.0 && _movingAltitudeRange.hasRange()) {
254                         return _movingAltitudeRange.getMetricHeightDiff() / dist * 100.0;
255                 }
256                 return 0.0;
257         }
258
259         /** @return the total vertical speed (including segment gaps) in current vspeed units */
260         public double getTotalVerticalSpeed()
261         {
262                 long time = getTotalDurationInSeconds();
263                 if (time > 0 && _totalAltitudeRange.hasRange()) {
264                         return _totalAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
265                 }
266                 return 0.0;
267         }
268
269         /** @return the moving vertical speed (ignoring segment gaps) in current vspeed units */
270         public double getMovingVerticalSpeed()
271         {
272                 long time = getMovingDurationInSeconds();
273                 if (time > 0 && _movingAltitudeRange.hasRange()) {
274                         return _movingAltitudeRange.getMetricHeightDiff() / time * Config.getUnitSet().getVerticalSpeedUnit().getMultFactorFromStd();
275                 }
276                 return 0.0;
277         }
278 }