]> git.mxchange.org Git - simgear.git/blob - simgear/environment/metar.cxx
hla: Make use of SGLocation.
[simgear.git] / simgear / environment / metar.cxx
1 // metar interface class
2 //
3 // Written by Melchior FRANZ, started December 2003.
4 //
5 // Copyright (C) 2003  Melchior FRANZ - mfranz@aon.at
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 // $Id$
22
23 /**
24  * @file metar.cxx
25  * Interface for encoded Meteorological Aerodrome Reports (METAR).
26  */
27 #ifdef HAVE_CONFIG_H
28 #  include <simgear_config.h>
29 #endif
30
31 #include <string>
32 #include <time.h>
33 #include <cstring>
34
35 #include <simgear/debug/logstream.hxx>
36 #include <simgear/structure/exception.hxx>
37
38 #include "metar.hxx"
39
40 #define NaN SGMetarNaN
41
42 using std::string;
43 using std::map;
44 using std::vector;
45
46 /**
47  * The constructor takes a Metar string
48  * The constructor throws sg_io_exceptions on failure. The "METAR"
49  * keyword has no effect (apart from incrementing the group counter
50  * @a grpcount) and can be left away. A keyword "SPECI" is
51  * likewise accepted.
52  *
53  * @param m     ICAO station id or metar string
54  *
55  * @par Examples:
56  * @code
57  * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013");
58  * double t = m->getTemperature_F();
59  * delete m;
60
61  * @endcode
62  */
63 SGMetar::SGMetar(const string& m) :
64         _grpcount(0),
65         _x_proxy(false),
66         _year(-1),
67         _month(-1),
68         _day(-1),
69         _hour(-1),
70         _minute(-1),
71         _report_type(-1),
72         _wind_dir(-1),
73         _wind_speed(NaN),
74         _gust_speed(NaN),
75         _wind_range_from(-1),
76         _wind_range_to(-1),
77         _temp(NaN),
78         _dewp(NaN),
79         _pressure(NaN),
80         _rain(false),
81         _hail(false),
82         _snow(false),
83         _cavok(false)
84 {
85         _data = new char[m.length() + 2];       // make room for " \0"
86         strcpy(_data, m.c_str());
87         _url = _data;
88                 
89         normalizeData();
90
91         _m = _data;
92         _icao[0] = '\0';
93
94         // NOAA preample
95         if (!scanPreambleDate())
96                 useCurrentDate();
97         scanPreambleTime();
98
99         // METAR header
100         scanType();
101         if (!scanId() || !scanDate()) {
102                 delete[] _data;
103                 throw sg_io_exception("metar data bogus ", sg_location(_url));
104         }
105         scanModifier();
106
107         // base set
108         scanWind();
109         scanVariability();
110         while (scanVisibility()) ;
111         while (scanRwyVisRange()) ;
112         while (scanWeather()) ;
113         while (scanSkyCondition()) ;
114         scanTemperature();
115         scanPressure();
116         while (scanSkyCondition()) ;
117         while (scanRunwayReport()) ;
118         scanWindShear();
119
120         // appendix
121         while (scanColorState()) ;
122         scanTrendForecast();
123         while (scanRunwayReport()) ;
124         scanRemainder();
125         scanRemark();
126
127         if (_grpcount < 4) {
128                 delete[] _data;
129                 throw sg_io_exception("metar data incomplete ", sg_location(_url));
130         }
131
132         _url = "";
133 }
134
135
136 /**
137   * Clears lists and maps to discourage access after destruction.
138   */
139 SGMetar::~SGMetar()
140 {
141         _clouds.clear();
142         _runways.clear();
143         _weather.clear();
144         delete[] _data;
145 }
146
147
148 void SGMetar::useCurrentDate()
149 {
150         struct tm now;
151         time_t now_sec = time(0);
152 #ifdef _WIN32
153         now = *gmtime(&now_sec);
154 #else
155         gmtime_r(&now_sec, &now);
156 #endif
157         _year = now.tm_year + 1900;
158         _month = now.tm_mon + 1;
159 }
160
161 /**
162   * Replace any number of subsequent spaces by just one space, and add
163   * a trailing space. This makes scanning for things like "ALL RWY" easier.
164   */
165 void SGMetar::normalizeData()
166 {
167         char *src, *dest;
168         for (src = dest = _data; (*dest++ = *src++); )
169                 while (*src == ' ' && src[1] == ' ')
170                         src++;
171         for (dest--; isspace(*--dest); ) ;
172         *++dest = ' ';
173         *++dest = '\0';
174 }
175
176
177 // \d\d\d\d/\d\d/\d\d
178 bool SGMetar::scanPreambleDate()
179 {
180         char *m = _m;
181         int year, month, day;
182         if (!scanNumber(&m, &year, 4))
183                 return false;
184         if (*m++ != '/')
185                 return false;
186         if (!scanNumber(&m, &month, 2))
187                 return false;
188         if (*m++ != '/')
189                 return false;
190         if (!scanNumber(&m, &day, 2))
191                 return false;
192         if (!scanBoundary(&m))
193                 return false;
194         _year = year;
195         _month = month;
196         _day = day;
197         _m = m;
198         return true;
199 }
200
201
202 // \d\d:\d\d
203 bool SGMetar::scanPreambleTime()
204 {
205         char *m = _m;
206         int hour, minute;
207         if (!scanNumber(&m, &hour, 2))
208                 return false;
209         if (*m++ != ':')
210                 return false;
211         if (!scanNumber(&m, &minute, 2))
212                 return false;
213         if (!scanBoundary(&m))
214                 return false;
215         _hour = hour;
216         _minute = minute;
217         _m = m;
218         return true;
219 }
220
221
222 // (METAR|SPECI)
223 bool SGMetar::scanType()
224 {
225         if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
226                 return false;
227         _m += 6;
228         _grpcount++;
229         return true;
230 }
231
232
233 // [A-Z]{4}
234 bool SGMetar::scanId()
235 {
236         char *m = _m;
237         for (int i = 0; i < 4; m++, i++)
238                 if (!(isalpha(*m) || isdigit(*m)))
239                         return false;
240         if (!scanBoundary(&m))
241                 return false;
242         strncpy(_icao, _m, 4);
243         _icao[4] = '\0';
244         _m = m;
245         _grpcount++;
246         return true;
247 }
248
249
250 // \d{6}Z
251 bool SGMetar::scanDate()
252 {
253         char *m = _m;
254         int day, hour, minute;
255         if (!scanNumber(&m, &day, 2))
256                 return false;
257         if (!scanNumber(&m, &hour, 2))
258                 return false;
259         if (!scanNumber(&m, &minute, 2))
260                 return false;
261         if (*m++ != 'Z')
262                 return false;
263         if (!scanBoundary(&m))
264                 return false;
265         _day = day;
266         _hour = hour;
267         _minute = minute;
268         _m = m;
269         _grpcount++;
270         return true;
271 }
272
273
274 // (NIL|AUTO|COR|RTD)
275 bool SGMetar::scanModifier()
276 {
277         char *m = _m;
278         int type;
279         if (!strncmp(m, "NIL", 3)) {
280                 _m += strlen(_m);
281                 return true;
282         }
283         if (!strncmp(m, "AUTO", 4))                     // automatically generated
284                 m += 4, type = AUTO;
285         else if (!strncmp(m, "COR", 3))                 // manually corrected
286                 m += 3, type = COR;
287         else if (!strncmp(m, "RTD", 3))                 // routine delayed
288                 m += 3, type = RTD;
289         else
290                 return false;
291         if (!scanBoundary(&m))
292                 return false;
293         _report_type = type;
294         _m = m;
295         _grpcount++;
296         return true;
297 }
298
299
300 // (\d{3}|VRB)\d{1,3}(G\d{2,3})?(KT|KMH|MPS)
301 bool SGMetar::scanWind()
302 {
303         char *m = _m;
304         int dir;
305         if (!strncmp(m, "VRB", 3))
306                 m += 3, dir = -1;
307         else if (!scanNumber(&m, &dir, 3))
308                 return false;
309
310         int i;
311         if (!scanNumber(&m, &i, 2, 3))
312                 return false;
313         double speed = i;
314
315         double gust = NaN;
316         if (*m == 'G') {
317                 m++;
318                 if (!scanNumber(&m, &i, 2, 3))
319                         return false;
320                 gust = i;
321         }
322         double factor;
323         if (!strncmp(m, "KT", 2))
324                 m += 2, factor = SG_KT_TO_MPS;
325         else if (!strncmp(m, "KMH", 3))
326                 m += 3, factor = SG_KMH_TO_MPS;
327         else if (!strncmp(m, "KPH", 3))         // ??
328                 m += 3, factor = SG_KMH_TO_MPS;
329         else if (!strncmp(m, "MPS", 3))
330                 m += 3, factor = 1.0;
331         else
332                 return false;
333         if (!scanBoundary(&m))
334                 return false;
335         _m = m;
336         _wind_dir = dir;
337         _wind_speed = speed * factor;
338         if (gust != NaN)
339                 _gust_speed = gust * factor;
340         _grpcount++;
341         return true;
342 }
343
344
345 // \d{3}V\d{3}
346 bool SGMetar::scanVariability()
347 {
348         char *m = _m;
349         int from, to;
350         if (!scanNumber(&m, &from, 3))
351                 return false;
352         if (*m++ != 'V')
353                 return false;
354         if (!scanNumber(&m, &to, 3))
355                 return false;
356         if (!scanBoundary(&m))
357                 return false;
358         _m = m;
359         _wind_range_from = from;
360         _wind_range_to = to;
361         _grpcount++;
362         return true;
363 }
364
365
366 bool SGMetar::scanVisibility()
367 // TODO: if only directed vis are given, do still set min/max
368 {
369         if (!strncmp(_m, "//// ", 5)) {         // spec compliant?
370                 _m += 5;
371                 _grpcount++;
372                 return true;
373         }
374
375         char *m = _m;
376         double distance;
377         int i, dir = -1;
378         int modifier = SGMetarVisibility::EQUALS;
379 // \d{4}(N|NE|E|SE|S|SW|W|NW)?
380         if (scanNumber(&m, &i, 4)) {
381                 if( strncmp( m, "NDV",3 ) == 0 ) {
382                         m+=3; // tolerate NDV (no directional validation)
383                 } else if (*m == 'E') {
384                         m++, dir = 90;
385                 } else if (*m == 'W') {
386                         m++, dir = 270;
387                 } else if (*m == 'N') {
388                         m++;
389                         if (*m == 'E')
390                                 m++, dir = 45;
391                         else if (*m == 'W')
392                                 m++, dir = 315;
393                         else
394                                 dir = 0;
395                 } else if (*m == 'S') {
396                         m++;
397                         if (*m == 'E')
398                                 m++, dir = 135;
399                         else if (*m == 'W')
400                                 m++, dir = 225;
401                         else
402                                 dir = 180;
403                 }
404                 if (i == 0)
405                         i = 50, modifier = SGMetarVisibility::LESS_THAN;
406                 else if (i == 9999)
407                         i++, modifier = SGMetarVisibility::GREATER_THAN;
408                 distance = i;
409         } else {
410 // M?(\d{1,2}|\d{1,2}/\d{1,2}|\d{1,2} \d{1,2}/\d{1,2})(SM|KM)
411                 if (*m == 'M')
412                         m++, modifier = SGMetarVisibility::LESS_THAN;
413
414                 if (!scanNumber(&m, &i, 1, 2))
415                         return false;
416                 distance = i;
417
418                 if (*m == '/') {
419                         m++;
420                         if (!scanNumber(&m, &i, 1, 2))
421                                 return false;
422                         distance /= i;
423                 } else if (*m == ' ') {
424                         m++;
425                         int denom;
426                         if (!scanNumber(&m, &i, 1, 2))
427                                 return false;
428                         if (*m++ != '/')
429                                 return false;
430                         if (!scanNumber(&m, &denom, 1, 2))
431                                 return false;
432                         distance += (double)i / denom;
433                 }
434
435                 if (!strncmp(m, "SM", 2))
436                         distance *= SG_SM_TO_METER, m += 2;
437                 else if (!strncmp(m, "KM", 2))
438                         distance *= 1000, m += 2;
439                 else
440                         return false;
441         }
442         if (!scanBoundary(&m))
443                 return false;
444
445         SGMetarVisibility *v;
446         if (dir != -1)
447                 v = &_dir_visibility[dir / 45];
448         else if (_min_visibility._distance == NaN)
449                 v = &_min_visibility;
450         else
451                 v = &_max_visibility;
452
453         v->_distance = distance;
454         v->_modifier = modifier;
455         v->_direction = dir;
456         _m = m;
457         _grpcount++;
458         return true;
459 }
460
461
462 // R\d\d[LCR]?/([PM]?\d{4}V)?[PM]?\d{4}(FT)?[DNU]?
463 bool SGMetar::scanRwyVisRange()
464 {
465         char *m = _m;
466         int i;
467         SGMetarRunway r;
468         if (*m++ != 'R')
469                 return false;
470         if (!scanNumber(&m, &i, 2))
471                 return false;
472         if (*m == 'L' || *m == 'C' || *m == 'R')
473                 m++;
474
475         char id[4];
476         strncpy(id, _m + 1, i = m - _m - 1);
477         id[i] = '\0';
478
479         if (*m++ != '/')
480                 return false;
481
482         int from, to;
483         if (*m == 'P')
484                 m++, r._min_visibility._modifier = SGMetarVisibility::GREATER_THAN;
485         else if (*m == 'M')
486                 m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
487         if (!scanNumber(&m, &from, 4))
488                 return false;
489         if (*m == 'V') {
490                 m++;
491                 if (*m == 'P')
492                         m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
493                 else if (*m == 'M')
494                         m++, r._max_visibility._modifier = SGMetarVisibility::LESS_THAN;
495                 if (!scanNumber(&m, &to, 4))
496                         return false;
497         } else
498                 to = from;
499
500         if (!strncmp(m, "FT", 2)) {
501                 from = int(from * SG_FEET_TO_METER);
502                 to = int(to * SG_FEET_TO_METER);
503                 m += 2;
504         }
505         r._min_visibility._distance = from;
506         r._max_visibility._distance = to;
507
508         if (*m == '/')                                  // this is not in the spec!
509                 m++;
510         if (*m == 'D')
511                 m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING;
512         else if (*m == 'N')
513                 m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
514         else if (*m == 'U')
515                 m++, r._min_visibility._tendency = SGMetarVisibility::INCREASING;
516
517         if (!scanBoundary(&m))
518                 return false;
519         _m = m;
520
521         _runways[id]._min_visibility = r._min_visibility;
522         _runways[id]._max_visibility = r._max_visibility;
523         _grpcount++;
524         return true;
525 }
526
527
528 static const struct Token special[] = {
529         { "NSW",  "no significant weather" },
530 /*      { "VCSH", "showers in the vicinity" },
531         { "VCTS", "thunderstorm in the vicinity" }, */
532         { 0, 0 }
533 };
534
535
536 static const struct Token description[] = {
537         { "SH", "showers of" },
538         { "TS", "thunderstorm with" },
539         { "BC", "patches of" },
540         { "BL", "blowing" },
541         { "DR", "low drifting" },
542         { "FZ", "freezing" },
543         { "MI", "shallow" },
544         { "PR", "partial" },
545         { 0, 0 }
546 };
547
548
549 static const struct Token phenomenon[] = {
550         { "DZ",   "drizzle" },
551         { "GR",   "hail" },
552         { "GS",   "small hail and/or snow pellets" },
553         { "IC",   "ice crystals" },
554         { "PE",   "ice pellets" },
555         { "RA",   "rain" },
556         { "SG",   "snow grains" },
557         { "SN",   "snow" },
558         { "UP",   "unknown precipitation" },
559         { "BR",   "mist" },
560         { "DU",   "widespread dust" },
561         { "FG",   "fog" },
562         { "FGBR", "fog bank" },
563         { "FU",   "smoke" },
564         { "HZ",   "haze" },
565         { "PY",   "spray" },
566         { "SA",   "sand" },
567         { "VA",   "volcanic ash" },
568         { "DS",   "duststorm" },
569         { "FC",   "funnel cloud/tornado waterspout" },
570         { "PO",   "well-developed dust/sand whirls" },
571         { "SQ",   "squalls" },
572         { "SS",   "sandstorm" },
573         { "UP",   "unknown" },  // ... due to failed automatic acquisition
574         { 0, 0 }
575 };
576
577
578 // (+|-|VC)?(NSW|MI|PR|BC|DR|BL|SH|TS|FZ)?((DZ|RA|SN|SG|IC|PE|GR|GS|UP){0,3})(BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS){0,3}
579 bool SGMetar::scanWeather()
580 {
581         char *m = _m;
582         string weather;
583         const struct Token *a;
584
585         if ((a = scanToken(&m, special))) {
586                 if (!scanBoundary(&m))
587                         return false;
588                 _weather.push_back(a->text);
589                 _m = m;
590                 return true;
591         }
592
593         string pre, post;
594     struct Weather w;
595         if (*m == '-')
596                 m++, pre = "light ", w.intensity = LIGHT;
597         else if (*m == '+')
598                 m++, pre = "heavy ", w.intensity = HEAVY;
599         else if (!strncmp(m, "VC", 2))
600         m += 2, post = "in the vicinity ", w.vincinity=true;
601         else
602                 pre = "moderate ", w.intensity = MODERATE;
603
604         int i;
605         for (i = 0; i < 3; i++) {
606                 if (!(a = scanToken(&m, description)))
607                         break;
608                 w.descriptions.push_back(a->id);
609                 weather += string(a->text) + " ";
610         }
611
612         for (i = 0; i < 3; i++) {
613                 if (!(a = scanToken(&m, phenomenon)))
614                         break;
615         w.phenomena.push_back(a->id);
616                 weather += string(a->text) + " ";
617                 if (!strcmp(a->id, "RA"))
618                         _rain = w.intensity;
619                 else if (!strcmp(a->id, "HA"))
620                         _hail = w.intensity;
621                 else if (!strcmp(a->id, "SN"))
622                         _snow = w.intensity;
623         }
624         if (!weather.length())
625                 return false;
626         if (!scanBoundary(&m))
627                 return false;
628         _m = m;
629         weather = pre + weather + post;
630         weather.erase(weather.length() - 1);
631         _weather.push_back(weather);
632     if( w.phenomena.size() > 0 )
633         _weather2.push_back( w );
634         _grpcount++;
635         return true;
636 }
637
638
639 static const struct Token cloud_types[] = {
640         { "AC",    "altocumulus" },
641         { "ACC",   "altocumulus castellanus" },
642         { "ACSL",  "altocumulus standing lenticular" },
643         { "AS",    "altostratus" },
644         { "CB",    "cumulonimbus" },
645         { "CBMAM", "cumulonimbus mammatus" },
646         { "CC",    "cirrocumulus" },
647         { "CCSL",  "cirrocumulus standing lenticular" },
648         { "CI",    "cirrus" },
649         { "CS",    "cirrostratus" },
650         { "CU",    "cumulus" },
651         { "CUFRA", "cumulus fractus" },
652         { "NS",    "nimbostratus" },
653         { "SAC",   "stratoaltocumulus" },               // guessed
654         { "SC",    "stratocumulus" },
655         { "SCSL",  "stratocumulus standing lenticular" },
656         { "ST",    "stratus" },
657         { "STFRA", "stratus fractus" },
658         { "TCU",   "towering cumulus" },
659         { 0, 0 }
660 };
661
662 #include <iostream>
663 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
664 bool SGMetar::scanSkyCondition()
665 {
666         char *m = _m;
667         int i;
668         SGMetarCloud cl;
669
670         if (!strncmp(m, "//////", 6)) {
671                 m += 6;
672                 if (!scanBoundary(&m))
673                         return false;
674                 _m = m;
675                 return true;
676         }
677
678         if (!strncmp(m, "CLR", i = 3)                           // clear
679                         || !strncmp(m, "SKC", i = 3)            // sky clear
680                         || !strncmp(m, "NCD", i = 3)            // nil cloud detected
681                         || !strncmp(m, "NSC", i = 3)            // no significant clouds
682                         || !strncmp(m, "CAVOK", i = 5)) {       // ceiling and visibility OK (implies 9999)
683                 m += i;
684                 if (!scanBoundary(&m))
685                         return false;
686
687                 if (i == 3) {
688             cl._coverage = SGMetarCloud::COVERAGE_CLEAR;
689                         _clouds.push_back(cl);
690                 } else {
691                         _cavok = true;
692                 }
693                 _m = m;
694                 return true;
695         }
696
697         if (!strncmp(m, "VV", i = 2))                           // vertical visibility
698                 ;
699         else if (!strncmp(m, "FEW", i = 3))
700         cl._coverage = SGMetarCloud::COVERAGE_FEW;
701         else if (!strncmp(m, "SCT", i = 3))
702         cl._coverage = SGMetarCloud::COVERAGE_SCATTERED;
703         else if (!strncmp(m, "BKN", i = 3))
704         cl._coverage = SGMetarCloud::COVERAGE_BROKEN;
705         else if (!strncmp(m, "OVC", i = 3))
706         cl._coverage = SGMetarCloud::COVERAGE_OVERCAST;
707         else
708                 return false;
709         m += i;
710
711         if (!strncmp(m, "///", 3))      // vis not measurable (e.g. because of heavy snowing)
712                 m += 3, i = -1;
713         else if (scanBoundary(&m)) {
714                 _m = m;
715                 return true;                            // ignore single OVC/BKN/...
716         } else if (!scanNumber(&m, &i, 3))
717                 i = -1;
718
719     if (cl._coverage == SGMetarCloud::COVERAGE_NIL) {
720                 if (!scanBoundary(&m))
721                         return false;
722                 if (i == -1)                    // 'VV///'
723                         _vert_visibility._modifier = SGMetarVisibility::NOGO;
724                 else
725                         _vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
726                 _m = m;
727                 return true;
728         }
729
730         if (i != -1)
731                 cl._altitude = i * 100 * SG_FEET_TO_METER;
732
733         const struct Token *a;
734         if ((a = scanToken(&m, cloud_types))) {
735                 cl._type = a->id;
736                 cl._type_long = a->text;
737         }
738         if (!scanBoundary(&m))
739                 return false;
740         _clouds.push_back(cl);
741         _m = m;
742         _grpcount++;
743         return true;
744 }
745
746
747 // M?[0-9]{2}/(M?[0-9]{2})?            (spec)
748 // (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)?    (Namibia)
749 bool SGMetar::scanTemperature()
750 {
751         char *m = _m;
752         int sign = 1, temp, dew;
753         if (!strncmp(m, "XX/XX", 5)) {          // not spec compliant!
754                 _m += 5;
755                 return scanBoundary(&_m);
756         }
757
758         if (*m == 'M')
759                 m++, sign = -1;
760         if (!scanNumber(&m, &temp, 2))
761                 return false;
762         temp *= sign;
763
764         if (*m++ != '/')
765                 return false;
766         if (!scanBoundary(&m)) {
767                 if (!strncmp(m, "XX", 2))       // not spec compliant!
768                         m += 2, sign = 0, dew = temp;
769                 else {
770                         sign = 1;
771                         if (*m == 'M')
772                                 m++, sign = -1;
773                         if (!scanNumber(&m, &dew, 2))
774                                 return false;
775                 }
776                 if (!scanBoundary(&m))
777                         return false;
778                 if (sign)
779                         _dewp = sign * dew;
780         }
781         _temp = temp;
782         _m = m;
783         _grpcount++;
784         return true;
785 }
786
787
788 double SGMetar::getRelHumidity() const
789 {
790         if (_temp == NaN || _dewp == NaN)
791                 return NaN;
792         double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
793         double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
794         return dewp * 100 / temp;
795 }
796
797
798 // [AQ]\d{4}             (spec)
799 // [AQ]\d{2}(\d{2}|//)   (Namibia)
800 bool SGMetar::scanPressure()
801 {
802         char *m = _m;
803         double factor;
804         int press, i;
805
806         if (*m == 'A')
807                 factor = SG_INHG_TO_PA / 100;
808         else if (*m == 'Q')
809                 factor = 100;
810         else
811                 return false;
812         m++;
813         if (!scanNumber(&m, &press, 2))
814                 return false;
815         press *= 100;
816         if (!strncmp(m, "//", 2))       // not spec compliant!
817                 m += 2;
818         else if (scanNumber(&m, &i, 2))
819                 press += i;
820         else
821                 return false;
822         if (!scanBoundary(&m))
823                 return false;
824         _pressure = press * factor;
825         _m = m;
826         _grpcount++;
827         return true;
828 }
829
830
831 static const char *runway_deposit[] = {
832         "clear and dry",
833         "damp",
834         "wet or puddles",
835         "frost",
836         "dry snow",
837         "wet snow",
838         "slush",
839         "ice",
840         "compacted snow",
841         "frozen ridges"
842 };
843
844
845 static const char *runway_deposit_extent[] = {
846         0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
847 };
848
849
850 static const char *runway_friction[] = {
851         0,
852         "poor braking action",
853         "poor/medium braking action",
854         "medium braking action",
855         "medium/good braking action",
856         "good braking action",
857         0, 0, 0,
858         "friction: unreliable measurement"
859 };
860
861
862 // \d\d(CLRD|[\d/]{4})(\d\d|//)
863 bool SGMetar::scanRunwayReport()
864 {
865         char *m = _m;
866         int i;
867         char id[4];
868         SGMetarRunway r;
869
870         if (!scanNumber(&m, &i, 2))
871                 return false;
872         if (i == 88)
873                 strcpy(id, "ALL");
874         else if (i == 99)
875                 strcpy(id, "REP");              // repetition of previous report
876         else if (i >= 50) {
877                 i -= 50;
878                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
879         } else
880                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
881
882         if (!strncmp(m, "CLRD", 4)) {
883                 m += 4;                                                 // runway cleared
884                 r._deposit_string = "cleared";
885         } else {
886                 if (scanNumber(&m, &i, 1)) {
887                         r._deposit = i;
888                         r._deposit_string = runway_deposit[i];
889                 } else if (*m == '/')
890                         m++;
891                 else
892                         return false;
893
894                 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit
895                         r._extent = *m - '0';
896                         r._extent_string = runway_deposit_extent[*m - '0'];
897                 } else if (*m != '/')
898                         return false;
899
900                 m++;
901                 i = -1;
902                 if (!strncmp(m, "//", 2))
903                         m += 2;
904                 else if (!scanNumber(&m, &i, 2))
905                         return false;
906
907                 if (i == 0)
908                         r._depth = 0.0005;                              // < 1 mm deep (let's say 0.5 :-)
909                 else if (i > 0 && i <= 90)
910                         r._depth = i / 1000.0;                          // i mm deep
911                 else if (i >= 92 && i <= 98)
912                         r._depth = (i - 90) / 20.0;
913                 else if (i == 99)
914                         r._comment = "runway not in use";
915                 else if (i == -1)                                       // no depth given ("//")
916                         ;
917                 else
918                         return false;
919         }
920         i = -1;
921         if (m[0] == '/' && m[1] == '/')
922                 m += 2;
923         else if (!scanNumber(&m, &i, 2))
924                 return false;
925         if (i >= 1 && i < 90) {
926                 r._friction = i / 100.0;
927         } else if ((i >= 91 && i <= 95) || i == 99) {
928                 r._friction_string = runway_friction[i - 90];
929         }
930         if (!scanBoundary(&m))
931                 return false;
932
933         _runways[id]._deposit = r._deposit;
934         _runways[id]._deposit_string = r._deposit_string;
935         _runways[id]._extent = r._extent;
936         _runways[id]._extent_string = r._extent_string;
937         _runways[id]._depth = r._depth;
938         _runways[id]._friction = r._friction;
939         _runways[id]._friction_string = r._friction_string;
940         _runways[id]._comment = r._comment;
941         _m = m;
942         _grpcount++;
943         return true;
944 }
945
946
947 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
948 bool SGMetar::scanWindShear()
949 {
950         char *m = _m;
951         if (strncmp(m, "WS", 2))
952                 return false;
953         m += 2;
954         if (!scanBoundary(&m))
955                 return false;
956
957         if (!strncmp(m, "ALL", 3)) {
958                 m += 3;
959                 if (!scanBoundary(&m))
960                         return false;
961                 if (strncmp(m, "RWY", 3))
962                         return false;
963                 m += 3;
964                 if (*m == 'S')
965                         m++;
966                 if (!scanBoundary(&m))
967                         return false;
968                 _runways["ALL"]._wind_shear = true;
969                 _m = m;
970                 return true;
971         }
972
973         char id[4], *mm;
974         int i, cnt;
975         for (cnt = 0;; cnt++) {                 // ??
976                 if (strncmp(m, "RWY", 3))
977                         break;
978                 m += 3;
979                 scanBoundary(&m);
980                 mm = m;
981                 if (!scanNumber(&m, &i, 2))
982                         return false;
983                 if (*m == 'L' || *m == 'C' || *m == 'R')
984                         m++;
985                 strncpy(id, mm, i = m - mm);
986                 id[i] = '\0';
987                 if (!scanBoundary(&m))
988                         return false;
989                 _runways[id]._wind_shear = true;
990         }
991         if (!cnt)
992                 _runways["ALL"]._wind_shear = true;
993         _m = m;
994         return true;
995 }
996
997
998 bool SGMetar::scanTrendForecast()
999 {
1000         char *m = _m;
1001         if (strncmp(m, "NOSIG", 5))
1002                 return false;
1003
1004         m += 5;
1005         if (!scanBoundary(&m))
1006                 return false;
1007         _m = m;
1008         return true;
1009 }
1010
1011
1012 // (BLU|WHT|GRN|YLO|AMB|RED)
1013 static const struct Token colors[] = {
1014         { "BLU", "Blue" },      // 2500 ft,  8.0 km
1015         { "WHT", "White" },     // 1500 ft,  5.0 km
1016         { "GRN", "Green" },     //  700 ft,  3.7 km
1017         { "YLO", "Yellow" },    //  300 ft,  1.6 km
1018         { "AMB", "Amber" },     //  200 ft,  0.8 km
1019         { "RED", "Red" },       // <200 ft, <0.8 km
1020         { 0, 0 }
1021 };
1022
1023
1024 bool SGMetar::scanColorState()
1025 {
1026         char *m = _m;
1027         const struct Token *a;
1028         if (!(a = scanToken(&m, colors)))
1029                 return false;
1030         if (!scanBoundary(&m))
1031                 return false;
1032         //printf(Y"Code %s\n"N, a->text);
1033         _m = m;
1034         return true;
1035 }
1036
1037
1038 bool SGMetar::scanRemark()
1039 {
1040         if (strncmp(_m, "RMK", 3))
1041                 return false;
1042         _m += 3;
1043         if (!scanBoundary(&_m))
1044                 return false;
1045
1046         while (*_m) {
1047                 if (!scanRunwayReport()) {
1048                         while (*_m && !isspace(*_m))
1049                                 _m++;
1050                         scanBoundary(&_m);
1051                 }
1052         }
1053         return true;
1054 }
1055
1056
1057 bool SGMetar::scanRemainder()
1058 {
1059         char *m = _m;
1060         if (!(strncmp(m, "NOSIG", 5))) {
1061                 m += 5;
1062                 if (scanBoundary(&m))
1063                         _m = m; //_comment.push_back("No significant tendency");
1064         }
1065
1066         if (!scanBoundary(&m))
1067                 return false;
1068         _m = m;
1069         return true;
1070 }
1071
1072
1073 bool SGMetar::scanBoundary(char **s)
1074 {
1075         if (**s && !isspace(**s))
1076                 return false;
1077         while (isspace(**s))
1078                 (*s)++;
1079         return true;
1080 }
1081
1082
1083 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1084 {
1085         int i;
1086         char *s = *src;
1087         *num = 0;
1088         for (i = 0; i < min; i++) {
1089                 if (!isdigit(*s))
1090                         return 0;
1091                 else
1092                         *num = *num * 10 + *s++ - '0';
1093         }
1094         for (; i < max && isdigit(*s); i++)
1095                 *num = *num * 10 + *s++ - '0';
1096         *src = s;
1097         return i;
1098 }
1099
1100
1101 // find longest match of str in list
1102 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1103 {
1104         const struct Token *longest = 0;
1105         int maxlen = 0, len;
1106         const char *s;
1107         for (int i = 0; (s = list[i].id); i++) {
1108                 len = strlen(s);
1109                 if (!strncmp(s, *str, len) && len > maxlen) {
1110                         maxlen = len;
1111                         longest = &list[i];
1112                 }
1113         }
1114         *str += maxlen;
1115         return longest;
1116 }
1117
1118
1119 void SGMetarCloud::set(double alt, Coverage cov)
1120 {
1121         _altitude = alt;
1122         if (cov != -1)
1123                 _coverage = cov;
1124 }
1125
1126 SGMetarCloud::Coverage SGMetarCloud::getCoverage( const std::string & coverage ) 
1127 {
1128         if( coverage == "clear" ) return COVERAGE_CLEAR;
1129         if( coverage == "few" ) return COVERAGE_FEW;
1130         if( coverage == "scattered" ) return COVERAGE_SCATTERED;
1131         if( coverage == "broken" ) return COVERAGE_BROKEN;
1132         if( coverage == "overcast" ) return COVERAGE_OVERCAST;
1133         return COVERAGE_NIL;
1134 }
1135
1136 const char * SGMetarCloud::COVERAGE_NIL_STRING = "nil";
1137 const char * SGMetarCloud::COVERAGE_CLEAR_STRING = "clear";
1138 const char * SGMetarCloud::COVERAGE_FEW_STRING = "few";
1139 const char * SGMetarCloud::COVERAGE_SCATTERED_STRING = "scattered";
1140 const char * SGMetarCloud::COVERAGE_BROKEN_STRING = "broken";
1141 const char * SGMetarCloud::COVERAGE_OVERCAST_STRING = "overcast";
1142
1143 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1144 {
1145         _distance = dist;
1146         if (dir != -1)
1147                 _direction = dir;
1148         if (mod != -1)
1149                 _modifier = mod;
1150         if (tend != 1)
1151                 _tendency = tend;
1152 }
1153
1154 #undef NaN