3 XCSoar Glide Computer - http://www.xcsoar.org/
4 Copyright (C) 2000-2013 The XCSoar Project
5 A detailed list of copyright holders can be found in the file "AUTHORS".
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include "FlightPhaseDetector.hpp"
24 #include "NMEA/CirclingInfo.hpp"
25 #include "NMEA/MoreData.hpp"
26 #include "NMEA/Derived.hpp"
28 constexpr fixed MIN_PHASE_TIME
= fixed(30);
29 constexpr fixed FP_TOLERANCE
= fixed(0.001);
32 * Update circling directon for given phase with given new direction
34 * If direction change is detected, phase direction will become MIXED.
35 * Note, that direction will be set for circling phases only.
38 UpdateCirclingDirection(Phase
&phase
, const Phase::CirclingDirection dir
)
40 if (phase
.phase_type
!= Phase::Type::CIRCLING
) {
41 phase
.circling_direction
= Phase::CirclingDirection::NO_DIRECTION
;
45 if (dir
== Phase::CirclingDirection::NO_DIRECTION
) {
48 if (phase
.circling_direction
!= dir
) {
49 if (phase
.circling_direction
== Phase::CirclingDirection::NO_DIRECTION
) {
50 phase
.circling_direction
= dir
;
53 phase
.circling_direction
= Phase::CirclingDirection::MIXED
;
59 CombinePhases(Phase
&base
, const Phase
&addition
)
61 UpdateCirclingDirection(base
, addition
.circling_direction
);
62 base
.end_datetime
= addition
.end_datetime
;
63 base
.end_time
= addition
.end_time
;
64 base
.end_alt
= addition
.end_alt
;
65 base
.end_loc
= addition
.end_loc
;
66 base
.distance
+= addition
.distance
;
67 base
.duration
+= addition
.duration
;
68 base
.alt_diff
+= addition
.alt_diff
;
73 GetPhaseType(const DerivedInfo
&calculated
)
75 switch (calculated
.turn_mode
) {
76 case CirclingMode::CRUISE
:
77 return Phase::Type::CRUISE
;
78 case CirclingMode::CLIMB
:
79 return Phase::Type::CIRCLING
;
81 return Phase::Type::NO_PHASE
;
86 * Return true if current flight is appears to be powered, false otherwise
89 IsPoweredFlight(const DerivedInfo
&calculated
)
91 return calculated
.flight
.IsTowing();
94 static Phase::CirclingDirection
95 CalcCirclingDirection(const DerivedInfo
&calculated
)
97 if (!calculated
.circling
) {
98 return Phase::CirclingDirection::NO_DIRECTION
;
100 return calculated
.TurningLeft() ?
101 Phase::CirclingDirection::LEFT
: Phase::CirclingDirection::RIGHT
;
105 Phase::GetSpeed() const {
106 if (duration
< FP_TOLERANCE
) {
109 return distance
/ duration
;
113 Phase::GetVario() const {
114 if (duration
< FP_TOLERANCE
) {
117 return alt_diff
/ duration
;
121 Phase::GetGlideRate() const {
122 if (fabs(alt_diff
) < FP_TOLERANCE
) {
125 return distance
/ -alt_diff
;
128 FlightPhaseDetector::FlightPhaseDetector()
130 previous_phase
.Clear();
131 current_phase
.Clear();
136 FlightPhaseDetector::Update(const MoreData
&basic
, const DerivedInfo
&calculated
)
138 bool mode_changed
= last_turn_mode
!= calculated
.turn_mode
;
139 bool lost_power
= current_phase
.phase_type
== Phase::Type::POWERED
&&
140 !IsPoweredFlight(calculated
);
142 bool phase_changed
= mode_changed
|| lost_power
;
145 if (current_phase
.phase_type
!= Phase::Type::NO_PHASE
) {
149 /* uncertain turn mode, try to determine it now and continue filling this
151 current_phase
.phase_type
= GetPhaseType(calculated
);
155 if (!current_phase
.start_datetime
.Plausible()) {
156 // Initialize new phase.
157 current_phase
.phase_type
= GetPhaseType(calculated
);
158 current_phase
.end_loc
= basic
.location
;
159 /* First phase' start_attributes are initialized with factual data,
160 * subsequent phases starts where previous one ended. */
161 if (phase_count
== 0) {
162 current_phase
.start_datetime
= basic
.date_time_utc
;
163 current_phase
.start_time
= basic
.time
;
164 current_phase
.start_loc
= basic
.location
;
165 current_phase
.start_alt
= basic
.nav_altitude
;
168 current_phase
.start_datetime
= previous_phase
.end_datetime
;
169 current_phase
.start_time
= previous_phase
.end_time
;
170 current_phase
.start_loc
= previous_phase
.end_loc
;
171 current_phase
.start_alt
= previous_phase
.end_alt
;
174 // Update the current phase with additional data
175 current_phase
.duration
= basic
.time
- current_phase
.start_time
;
176 current_phase
.alt_diff
= basic
.nav_altitude
- current_phase
.start_alt
;
177 current_phase
.distance
+= current_phase
.end_loc
.Distance(basic
.location
);
178 current_phase
.end_datetime
= basic
.date_time_utc
;
179 current_phase
.end_time
= basic
.time
;
180 current_phase
.end_loc
= basic
.location
;
181 current_phase
.end_alt
= basic
.nav_altitude
;
182 UpdateCirclingDirection(current_phase
, CalcCirclingDirection(calculated
));
183 // When powered flight is detected, current phase becomes "powered"
184 if (IsPoweredFlight(calculated
)) {
185 current_phase
.phase_type
= Phase::Type::POWERED
;
188 // detect powered flight
190 // save last turn mode seen
191 last_turn_mode
= calculated
.turn_mode
;
195 FlightPhaseDetector::Finish()
197 // Push last phases (we still have two latest phases in state)
198 if (current_phase
.phase_type
== Phase::Type::NO_PHASE
) {
199 current_phase
.phase_type
= previous_phase
.phase_type
;
202 phases
.push_back(previous_phase
);
204 // Calculate total statistics
205 for (Phase ph
: phases
) {
206 switch (ph
.phase_type
) {
207 case Phase::Type::CIRCLING
:
208 CombinePhases(totals
.total_circstats
, ph
);
209 switch (ph
.circling_direction
) {
210 case Phase::CirclingDirection::LEFT
:
211 CombinePhases(totals
.left_circstats
, ph
);
213 case Phase::CirclingDirection::RIGHT
:
214 CombinePhases(totals
.right_circstats
, ph
);
216 case Phase::CirclingDirection::MIXED
:
217 CombinePhases(totals
.mixed_circstats
, ph
);
219 case Phase::CirclingDirection::NO_DIRECTION
:
223 case Phase::Type::CRUISE
:
224 CombinePhases(totals
.total_cruisestats
, ph
);
226 case Phase::Type::POWERED
:
227 case Phase::Type::NO_PHASE
:
232 // Calculate fractions
233 fixed total_circling
= totals
.total_circstats
.duration
;
234 fixed total_cruise
= totals
.total_cruisestats
.duration
;
235 fixed total_duration
= total_circling
+ total_cruise
;
236 if (total_duration
> fixed(0)) {
237 totals
.total_circstats
.fraction
= total_circling
/ total_duration
;
238 totals
.total_cruisestats
.fraction
= total_cruise
/ total_duration
;
240 if (total_circling
> fixed(0)) {
241 totals
.left_circstats
.fraction
=
242 totals
.left_circstats
.duration
/ total_circling
;
243 totals
.right_circstats
.fraction
=
244 totals
.right_circstats
.duration
/ total_circling
;
245 totals
.mixed_circstats
.fraction
=
246 totals
.mixed_circstats
.duration
/ total_circling
;
252 FlightPhaseDetector::PushPhase()
254 assert(current_phase
.phase_type
!= Phase::Type::NO_PHASE
);
256 if (phase_count
== 0) {
257 previous_phase
= current_phase
;
261 bool same_type
= previous_phase
.phase_type
== current_phase
.phase_type
;
262 bool too_short
= current_phase
.duration
< MIN_PHASE_TIME
;
263 if (same_type
|| too_short
) {
264 CombinePhases(previous_phase
, current_phase
);
267 phases
.push_back(previous_phase
);
269 previous_phase
= current_phase
;
272 current_phase
.Clear();