po: update French translation
[xcsoar.git] / src / Wind / CirclingWind.cpp
blobc1cdd9a54e2baada075abd8668d750fa0a9c5350
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.
24 /* This library was originally imported from Cumulus
25 http://kflog.org/cumulus/ */
27 #include "Wind/CirclingWind.hpp"
28 #include "Math/Constants.h"
29 #include "LogFile.hpp"
30 #include "NMEA/MoreData.hpp"
31 #include "NMEA/Derived.hpp"
33 #include <stdlib.h>
34 #include <algorithm>
36 using std::min;
39 About Windanalysation
41 Currently, the wind is being analyzed by finding the minimum and the maximum
42 groundspeeds measured while flying a circle. The direction of the wind is taken
43 to be the direction in wich the speed reaches it's maximum value, the speed
44 is half the difference between the maximum and the minimum speeds measured.
45 A quality parameter, based on the number of circles allready flown (the first
46 circles are taken to be less accurate) and the angle between the headings at
47 minimum and maximum speeds, is calculated in order to be able to weigh the
48 resulting measurement.
50 There are other options for determining the windspeed. You could for instance
51 add all the vectors in a circle, and take the resuling vector as the windspeed.
52 This is a more complex method, but because it is based on more heading/speed
53 measurements by the GPS, it is probably more accurate. If equiped with
54 instruments that pass along airspeed, the calculations can be compensated for
55 changes in airspeed, resulting in better measurements. We are now assuming
56 the pilot flies in perfect circles with constant airspeed, wich is of course
57 not a safe assumption.
58 The quality indication we are calculation can also be approched differently,
59 by calculating how constant the speed in the circle would be if corrected for
60 the windspeed we just derived. The more constant, the better. This is again
61 more CPU intensive, but may produce better results.
63 Some of the errors made here will be averaged-out by the WindStore, wich keeps
64 a number of windmeasurements and calculates a weighted average based on quality.
67 void
68 CirclingWind::Reset()
70 last_track_available.Clear();
71 last_ground_speed_available.Clear();
72 circle_count = 0;
73 active = false;
74 circle_deg = 0;
75 last_track = Angle::Zero();
76 first = true;
79 CirclingWind::Result
80 CirclingWind::NewSample(const MoreData &info)
82 if (!active)
83 // only work if we are in active mode
84 return Result(0);
86 if (last_track_available.FixTimeWarp(info.track_available) ||
87 last_ground_speed_available.FixTimeWarp(info.ground_speed_available))
88 /* time warp: start from scratch */
89 Reset();
91 if (!info.track_available.Modified(last_track_available) ||
92 !info.ground_speed_available.Modified(last_ground_speed_available))
93 /* no updated GPS fix */
94 return Result(0);
96 last_track_available = info.track_available;
97 last_ground_speed_available = info.ground_speed_available;
99 Vector curVector;
101 // Circle detection
102 int diff = (int)(info.track - last_track).AsDelta().AbsoluteDegrees();
103 circle_deg += diff;
104 last_track = info.track;
106 const bool fullCircle = circle_deg >= 360;
107 if (fullCircle) {
108 //full circle made!
110 circle_deg = 0;
111 circle_count++; //increase the number of circles flown (used
112 //to determine the quality)
115 curVector = Vector(SpeedVector(info.track, info.ground_speed));
117 if (!samples.full()) {
118 Sample &sample = samples.append();
119 sample.v = curVector;
120 sample.time = info.clock;
121 sample.mag = info.ground_speed;
122 } else {
123 // TODO code: give error, too many wind samples
124 // or use circular buffer
127 if (first || (info.ground_speed < min_vector.Magnitude()))
128 min_vector = curVector;
130 if (first || (info.ground_speed > max_vector.Magnitude()))
131 max_vector = curVector;
133 Result result(0);
134 if (fullCircle) { //we have completed a full circle!
135 if (!samples.full())
136 // calculate the wind for this circle, only if it is valid
137 result = CalcWind();
139 // should set each vector to average
141 min_vector = max_vector = Vector((max_vector.x - min_vector.x) / 2,
142 (max_vector.y - min_vector.y) / 2);
144 first = true;
145 samples.clear();
148 first = false;
150 return result;
153 void
154 CirclingWind::NewFlightMode(const DerivedInfo &derived)
156 // we are inactive by default
157 active = false;
159 // reset the circlecounter for each flightmode
160 // change. The important thing to measure is the
161 // number of turns in this thermal only.
162 circle_count = 0;
164 circle_deg = 0;
166 // we are not circling? Exit function!
167 if (!derived.circling)
168 return;
170 // initialize analyser-parameters
171 active = true;
172 first = true;
173 samples.clear();
176 CirclingWind::Result
177 CirclingWind::CalcWind()
179 if (samples.empty())
180 return Result(0);
182 // reject if average time step greater than 2.0 seconds
183 if ((samples.last().time - samples[0].time) / (samples.size() - 1) > fixed(2))
184 return Result(0);
186 // find average
187 fixed av = fixed(0);
188 for (unsigned i = 0; i < samples.size(); i++)
189 av += samples[i].mag;
191 av /= samples.size();
193 // find zero time for times above average
194 fixed rthisp;
195 int ithis = 0;
196 fixed rthismax = fixed(0);
197 fixed rthismin = fixed(0);
198 int jmax = -1;
199 int jmin = -1;
201 for (unsigned j = 0; j < samples.size(); j++) {
202 rthisp = fixed(0);
204 for (unsigned i = 0; i < samples.size(); i++) {
205 if (i == j)
206 continue;
208 ithis = (i + j) % samples.size();
209 unsigned idiff = i;
211 if (idiff > samples.size() / 2)
212 idiff = samples.size() - idiff;
214 rthisp += (samples[ithis].mag) * idiff;
217 if ((rthisp < rthismax) || (jmax == -1)) {
218 rthismax = rthisp;
219 jmax = j;
222 if ((rthisp > rthismin) || (jmin == -1)) {
223 rthismin = rthisp;
224 jmin = j;
228 // jmax is the point where most wind samples are below
229 max_vector = samples[jmax].v;
231 // jmin is the point where most wind samples are above
232 min_vector = samples[jmin].v;
234 // attempt to fit cycloid
236 fixed mag = Half(samples[jmax].mag - samples[jmin].mag);
237 fixed rthis = fixed(0);
239 const Angle step = Angle::FullCircle() / samples.size();
240 Angle angle = step * jmax;
241 for (unsigned i = 0; i < samples.size(); i++, angle += step) {
242 const auto sc = angle.SinCos();
243 fixed wx = sc.second, wy = sc.first;
244 wx = wx * av + mag;
245 wy *= av;
246 fixed cmag = SmallHypot(wx, wy) - samples[i].mag;
247 rthis += sqr(cmag);
250 rthis /= samples.size();
251 rthis = sqrt(rthis);
253 int quality;
255 if (mag > fixed(1))
256 quality = 5 - iround(rthis / mag * 3);
257 else
258 quality = 5 - iround(rthis);
260 if (circle_count < 3)
261 quality--;
262 if (circle_count < 2)
263 quality--;
264 if (circle_count < 1)
265 return Result(0);
267 quality = min(quality, 5); //5 is maximum quality, make sure we honour that.
269 if (quality < 1)
270 //measurment quality too low
271 return Result(0);
273 Vector a(-mag * max_vector.x / samples[jmax].mag,
274 -mag * max_vector.y / samples[jmax].mag);
276 if (a.SquareMagnitude() >= fixed(30 * 30))
277 // limit to reasonable values (60 knots), reject otherwise
278 return Result(0);
280 return Result(quality, a);