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