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