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