android/GlueIOIOPort: fix spurious errors after IOIO baud rate change
[xcsoar.git] / src / Computer / GlideComputerAirData.cpp
blob5a447924d14f540ad8ee605a6506b787319b23ca
1 /*
2 Copyright_License {
4 XCSoar Glide Computer - http://www.xcsoar.org/
5 Copyright (C) 2000-2013 The XCSoar Project
6 A detailed list of copyright holders can be found in the file "AUTHORS".
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25 #include "GlideComputerAirData.hpp"
26 #include "GlideComputer.hpp"
27 #include "Settings.hpp"
28 #include "Math/LowPassFilter.hpp"
29 #include "Terrain/RasterTerrain.hpp"
30 #include "ThermalBase.hpp"
31 #include "GlideSolvers/GlidePolar.hpp"
32 #include "NMEA/Aircraft.hpp"
33 #include "Math/SunEphemeris.hpp"
35 static constexpr fixed THERMAL_TIME_MIN(45);
37 GlideComputerAirData::GlideComputerAirData(const Waypoints &_way_points)
38 :waypoints(_way_points),
39 terrain(NULL)
41 // JMW TODO enhancement: seed initial wind store with start conditions
42 // SetWindEstimate(Calculated().WindSpeed, Calculated().WindBearing, 1);
45 void
46 GlideComputerAirData::ResetFlight(DerivedInfo &calculated,
47 const bool full)
49 auto_qnh.Reset();
51 average_vario.Reset();
53 lift_database_computer.Reset(calculated.lift_database,
54 calculated.trace_history.CirclingAverage);
56 thermallocator.Reset();
58 gr_computer.Reset();
60 if (full)
61 flying_computer.Reset();
63 circling_computer.Reset();
65 thermal_band_computer.Reset();
66 wind_computer.Reset();
69 void
70 GlideComputerAirData::ProcessBasic(const MoreData &basic,
71 DerivedInfo &calculated,
72 const ComputerSettings &settings)
74 TerrainHeight(basic, calculated);
75 ProcessSun(basic, calculated, settings);
77 NettoVario(basic, calculated.flight, calculated, settings);
80 void
81 GlideComputerAirData::ProcessVertical(const MoreData &basic,
82 const MoreData &last_basic,
83 DerivedInfo &calculated,
84 const ComputerSettings &settings)
86 /* the "circling" flag may be modified by
87 CirclingComputer::Turning(); remember the old state so this
88 method can check for modifications */
89 const bool last_circling = calculated.circling;
91 auto_qnh.Process(basic, calculated, settings, waypoints);
93 circling_computer.TurnRate(calculated, basic,
94 calculated.flight);
95 Turning(basic, calculated, settings);
97 wind_computer.Compute(settings.wind, settings.polar.glide_polar_task,
98 basic, calculated);
99 wind_computer.Select(settings.wind, basic, calculated);
100 wind_computer.ComputeHeadWind(basic, calculated);
102 thermallocator.Process(calculated.circling,
103 basic.time, basic.location,
104 basic.netto_vario,
105 calculated.GetWindOrZero(),
106 calculated.thermal_locator);
108 LastThermalStats(basic, calculated, last_circling);
110 gr_computer.Compute(basic, last_basic, calculated,
111 calculated,
112 settings);
114 GR(basic, calculated.flight, calculated);
115 CruiseGR(basic, calculated);
117 average_vario.Compute(basic, calculated.circling, last_circling,
118 calculated);
119 AverageClimbRate(basic, calculated);
121 if (calculated.circling)
122 CurrentThermal(basic, calculated, calculated.current_thermal);
124 lift_database_computer.Compute(calculated.lift_database,
125 calculated.trace_history.CirclingAverage,
126 basic, calculated);
127 circling_computer.MaxHeightGain(basic, calculated.flight, calculated);
128 NextLegEqThermal(basic, calculated, settings);
131 inline void
132 GlideComputerAirData::NettoVario(const NMEAInfo &basic,
133 const FlyingState &flight,
134 VarioInfo &vario,
135 const ComputerSettings &settings_computer)
137 fixed g_load = basic.acceleration.available ?
138 basic.acceleration.g_load : fixed(1);
140 vario.sink_rate =
141 flight.flying && basic.airspeed_available &&
142 settings_computer.polar.glide_polar_task.IsValid()
143 ? - settings_computer.polar.glide_polar_task.SinkRate(basic.indicated_airspeed,
144 g_load)
145 /* the glider sink rate is useless when not flying */
146 : fixed(0);
149 inline void
150 GlideComputerAirData::AverageClimbRate(const NMEAInfo &basic,
151 DerivedInfo &calculated)
153 if (basic.airspeed_available && positive(basic.indicated_airspeed) &&
154 positive(basic.true_airspeed) &&
155 basic.total_energy_vario_available &&
156 !calculated.circling &&
157 (!basic.acceleration.available ||
158 !basic.acceleration.real ||
159 fabs(basic.acceleration.g_load - fixed(1)) <= fixed(0.25))) {
160 // TODO: Check this is correct for TAS/IAS
161 fixed ias_to_tas = basic.indicated_airspeed / basic.true_airspeed;
162 fixed w_tas = basic.total_energy_vario * ias_to_tas;
164 calculated.climb_history.Add(uround(basic.indicated_airspeed), w_tas);
168 inline void
169 GlideComputerAirData::CurrentThermal(const MoreData &basic,
170 const CirclingInfo &circling,
171 OneClimbInfo &current_thermal)
173 if (positive(circling.climb_start_time)) {
174 current_thermal.start_time = circling.climb_start_time;
175 current_thermal.end_time = basic.time;
176 current_thermal.gain =
177 basic.TE_altitude - circling.climb_start_altitude_te;
178 current_thermal.CalculateAll();
179 } else
180 current_thermal.Clear();
183 inline void
184 GlideComputerAirData::GR(const MoreData &basic, const FlyingState &flying,
185 VarioInfo &vario_info)
187 // Lift / drag instantaneous from vario, updated every reading..
188 if (basic.total_energy_vario_available && basic.airspeed_available &&
189 flying.flying) {
190 vario_info.ld_vario =
191 UpdateGR(vario_info.ld_vario, basic.indicated_airspeed,
192 -basic.total_energy_vario, fixed(0.3));
193 } else {
194 vario_info.ld_vario = INVALID_GR;
198 inline void
199 GlideComputerAirData::CruiseGR(const MoreData &basic, DerivedInfo &calculated)
201 if (!calculated.circling && basic.NavAltitudeAvailable()) {
202 if (negative(calculated.cruise_start_time)) {
203 calculated.cruise_start_location = basic.location;
204 calculated.cruise_start_altitude = basic.nav_altitude;
205 calculated.cruise_start_time = basic.time;
206 } else {
207 fixed DistanceFlown =
208 basic.location.Distance(calculated.cruise_start_location);
210 calculated.cruise_gr =
211 UpdateGR(calculated.cruise_gr, DistanceFlown,
212 calculated.cruise_start_altitude - basic.nav_altitude,
213 fixed(0.5));
219 * Reads the current terrain height
221 inline void
222 GlideComputerAirData::TerrainHeight(const MoreData &basic,
223 TerrainInfo &calculated)
225 if (!basic.location_available || terrain == NULL) {
226 calculated.terrain_valid = false;
227 calculated.terrain_altitude = fixed(0);
228 calculated.altitude_agl_valid = false;
229 calculated.altitude_agl = fixed(0);
230 return;
233 short Alt = terrain->GetTerrainHeight(basic.location);
234 if (RasterBuffer::IsSpecial(Alt)) {
235 if (RasterBuffer::IsWater(Alt))
236 /* assume water is 0m MSL; that's the best guess */
237 Alt = 0;
238 else {
239 calculated.terrain_valid = false;
240 calculated.terrain_altitude = fixed(0);
241 calculated.altitude_agl_valid = false;
242 calculated.altitude_agl = fixed(0);
243 return;
247 calculated.terrain_valid = true;
248 calculated.terrain_altitude = fixed(Alt);
250 if (basic.NavAltitudeAvailable()) {
251 calculated.altitude_agl = basic.nav_altitude - calculated.terrain_altitude;
252 calculated.altitude_agl_valid = true;
253 } else
254 calculated.altitude_agl_valid = false;
257 bool
258 GlideComputerAirData::FlightTimes(const NMEAInfo &basic,
259 const NMEAInfo &last_basic,
260 DerivedInfo &calculated,
261 const ComputerSettings &settings)
263 if (basic.gps.replay != last_basic.gps.replay)
264 // reset flight before/after replay logger
265 ResetFlight(calculated, basic.gps.replay);
267 if (basic.time_available && basic.HasTimeRetreatedSince(last_basic)) {
268 // 20060519:sgi added (basic.Time != 0) due to always return here
269 // if no GPS time available
270 if (basic.location_available)
271 // Reset statistics.. (probably due to being in IGC replay mode)
272 ResetFlight(calculated, false);
274 return false;
277 FlightState(basic, calculated, calculated.flight,
278 settings.polar.glide_polar_task);
280 return true;
283 inline void
284 GlideComputerAirData::FlightState(const NMEAInfo &basic,
285 const DerivedInfo &calculated,
286 FlyingState &flying,
287 const GlidePolar &glide_polar)
289 fixed v_takeoff = glide_polar.IsValid()
290 ? glide_polar.GetVTakeoff()
291 /* if there's no valid polar, assume 10 m/s (36 km/h); that's an
292 arbitrary value, but better than nothing */
293 : fixed(10);
295 flying_computer.Compute(v_takeoff, basic,
296 calculated, flying);
299 inline void
300 GlideComputerAirData::Turning(const MoreData &basic,
301 DerivedInfo &calculated,
302 const ComputerSettings &settings)
304 circling_computer.Turning(calculated,
305 basic,
306 calculated.flight,
307 settings.circling);
309 // Calculate circling time percentage and call thermal band calculation
310 circling_computer.PercentCircling(basic, calculated);
312 thermal_band_computer.Compute(basic, calculated,
313 calculated.thermal_band,
314 settings);
317 inline void
318 GlideComputerAirData::ThermalSources(const MoreData &basic,
319 const DerivedInfo &calculated,
320 ThermalLocatorInfo &thermal_locator)
322 if (!thermal_locator.estimate_valid ||
323 !basic.NavAltitudeAvailable() ||
324 !calculated.last_thermal.IsDefined() ||
325 negative(calculated.last_thermal.lift_rate))
326 return;
328 if (calculated.wind_available &&
329 calculated.wind.norm / calculated.last_thermal.lift_rate > fixed(10.0)) {
330 // thermal strength is so weak compared to wind that source estimate
331 // is unlikely to be reliable, so don't calculate or remember it
332 return;
335 GeoPoint ground_location;
336 fixed ground_altitude = fixed(-1);
337 EstimateThermalBase(terrain, thermal_locator.estimate_location,
338 basic.nav_altitude,
339 calculated.last_thermal.lift_rate,
340 calculated.GetWindOrZero(),
341 ground_location,
342 ground_altitude);
344 if (positive(ground_altitude)) {
345 ThermalSource &source = thermal_locator.AllocateSource();
347 source.lift_rate = calculated.last_thermal.lift_rate;
348 source.location = ground_location;
349 source.ground_height = ground_altitude;
350 source.time = basic.time;
354 inline void
355 GlideComputerAirData::LastThermalStats(const MoreData &basic,
356 DerivedInfo &calculated,
357 bool last_circling)
359 if (calculated.circling || !last_circling ||
360 !positive(calculated.climb_start_time))
361 return;
363 fixed duration = calculated.cruise_start_time - calculated.climb_start_time;
364 if (duration < THERMAL_TIME_MIN)
365 return;
367 fixed gain =
368 calculated.cruise_start_altitude_te - calculated.climb_start_altitude_te;
370 if (!positive(gain))
371 return;
373 bool was_defined = calculated.last_thermal.IsDefined();
375 calculated.last_thermal.start_time = calculated.climb_start_time;
376 calculated.last_thermal.end_time = calculated.cruise_start_time;
377 calculated.last_thermal.gain = gain;
378 calculated.last_thermal.duration = duration;
379 calculated.last_thermal.CalculateLiftRate();
381 if (!was_defined)
382 calculated.last_thermal_average_smooth =
383 calculated.last_thermal.lift_rate;
384 else
385 calculated.last_thermal_average_smooth =
386 LowPassFilter(calculated.last_thermal_average_smooth,
387 calculated.last_thermal.lift_rate, fixed(0.3));
389 ThermalSources(basic, calculated, calculated.thermal_locator);
392 inline void
393 GlideComputerAirData::ProcessSun(const NMEAInfo &basic,
394 DerivedInfo &calculated,
395 const ComputerSettings &settings)
397 if (!basic.location_available || !basic.date_available)
398 return;
400 // Only calculate new azimuth if data is older than 15 minutes
401 if (!calculated.sun_data_available.IsOlderThan(basic.clock, fixed(15 * 60)))
402 return;
404 // Calculate new azimuth
405 calculated.sun_azimuth =
406 SunEphemeris::CalcAzimuth(basic.location, basic.date_time_utc,
407 settings.utc_offset);
408 calculated.sun_data_available.Update(basic.clock);
411 inline void
412 GlideComputerAirData::NextLegEqThermal(const NMEAInfo &basic,
413 DerivedInfo &calculated,
414 const ComputerSettings &settings)
416 const GeoVector vector_remaining =
417 calculated.task_stats.current_leg.vector_remaining;
418 const GeoVector next_leg_vector =
419 calculated.task_stats.current_leg.next_leg_vector;
421 if (!settings.polar.glide_polar_task.IsValid() ||
422 !next_leg_vector.IsValid() ||
423 !vector_remaining.IsValid() ||
424 !calculated.wind_available) {
425 // Assign a negative value to invalidate the result
426 calculated.next_leg_eq_thermal = fixed(-1);
427 return;
430 // Calculate wind component on current and next legs
431 const fixed wind_comp = calculated.wind.norm *
432 (calculated.wind.bearing - vector_remaining.bearing).fastcosine();
433 const fixed next_comp = calculated.wind.norm *
434 (calculated.wind.bearing - next_leg_vector.bearing).fastcosine();
436 calculated.next_leg_eq_thermal =
437 settings.polar.glide_polar_task.GetNextLegEqThermal(wind_comp, next_comp);