]> git.mxchange.org Git - simgear.git/blob - simgear/environment/metar.cxx
Introduce SGBinaryFile
[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         _grpcount++;
654         return true;
655 }
656
657
658 static const struct Token cloud_types[] = {
659         { "AC",    "altocumulus" },
660         { "ACC",   "altocumulus castellanus" },
661         { "ACSL",  "altocumulus standing lenticular" },
662         { "AS",    "altostratus" },
663         { "CB",    "cumulonimbus" },
664         { "CBMAM", "cumulonimbus mammatus" },
665         { "CC",    "cirrocumulus" },
666         { "CCSL",  "cirrocumulus standing lenticular" },
667         { "CI",    "cirrus" },
668         { "CS",    "cirrostratus" },
669         { "CU",    "cumulus" },
670         { "CUFRA", "cumulus fractus" },
671         { "NS",    "nimbostratus" },
672         { "SAC",   "stratoaltocumulus" },               // guessed
673         { "SC",    "stratocumulus" },
674         { "SCSL",  "stratocumulus standing lenticular" },
675         { "ST",    "stratus" },
676         { "STFRA", "stratus fractus" },
677         { "TCU",   "towering cumulus" },
678         { 0, 0 }
679 };
680
681 #include <iostream>
682 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
683 bool SGMetar::scanSkyCondition()
684 {
685         char *m = _m;
686         int i;
687         SGMetarCloud cl;
688
689         if (!strncmp(m, "//////", 6)) {
690                 m += 6;
691                 if (!scanBoundary(&m))
692                         return false;
693                 _m = m;
694                 return true;
695         }
696
697         if (!strncmp(m, "CLR", i = 3)                           // clear
698                         || !strncmp(m, "SKC", i = 3)            // sky clear
699                         || !strncmp(m, "NCD", i = 3)            // nil cloud detected
700                         || !strncmp(m, "NSC", i = 3)            // no significant clouds
701                         || !strncmp(m, "CAVOK", i = 5)) {       // ceiling and visibility OK (implies 9999)
702                 m += i;
703                 if (!scanBoundary(&m))
704                         return false;
705
706                 if (i == 3) {
707                         cl._coverage = SGMetarCloud::COVERAGE_CLEAR;
708                         _clouds.push_back(cl);
709                 } else {
710                         _cavok = true;
711                 }
712                 _m = m;
713                 return true;
714         }
715
716         if (!strncmp(m, "VV", i = 2))                           // vertical visibility
717                 ;
718         else if (!strncmp(m, "FEW", i = 3))
719         cl._coverage = SGMetarCloud::COVERAGE_FEW;
720         else if (!strncmp(m, "SCT", i = 3))
721         cl._coverage = SGMetarCloud::COVERAGE_SCATTERED;
722         else if (!strncmp(m, "BKN", i = 3))
723         cl._coverage = SGMetarCloud::COVERAGE_BROKEN;
724         else if (!strncmp(m, "OVC", i = 3))
725         cl._coverage = SGMetarCloud::COVERAGE_OVERCAST;
726         else
727                 return false;
728         m += i;
729
730         if (!strncmp(m, "///", 3))      // vis not measurable (e.g. because of heavy snowing)
731                 m += 3, i = -1;
732         else if (scanBoundary(&m)) {
733                 _m = m;
734                 return true;                            // ignore single OVC/BKN/...
735         } else if (!scanNumber(&m, &i, 3))
736                 i = -1;
737
738         if (cl._coverage == SGMetarCloud::COVERAGE_NIL) {
739                 if (!scanBoundary(&m))
740                         return false;
741                 if (i == -1)                    // 'VV///'
742                         _vert_visibility._modifier = SGMetarVisibility::NOGO;
743                 else
744                         _vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
745                 _m = m;
746                 return true;
747         }
748
749         if (i != -1)
750                 cl._altitude = i * 100 * SG_FEET_TO_METER;
751
752         const struct Token *a;
753         if ((a = scanToken(&m, cloud_types))) {
754                 cl._type = a->id;
755                 cl._type_long = a->text;
756         }
757
758         // @see WMO-49 Section 4.5.4.5
759         // Denotes temporary failure of sensor and covers cases like FEW045///
760         if (!strncmp(m, "///", 3))
761                 m += 3;
762         if (!scanBoundary(&m))
763                 return false;
764         _clouds.push_back(cl);
765
766         _m = m;
767         _grpcount++;
768         return true;
769 }
770
771
772 // M?[0-9]{2}/(M?[0-9]{2})?            (spec)
773 // (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)?    (Namibia)
774 bool SGMetar::scanTemperature()
775 {
776         char *m = _m;
777         int sign = 1, temp, dew;
778         if (!strncmp(m, "XX/XX", 5)) {          // not spec compliant!
779                 _m += 5;
780                 return scanBoundary(&_m);
781         }
782
783         if (*m == 'M')
784                 m++, sign = -1;
785         if (!scanNumber(&m, &temp, 2))
786                 return false;
787         temp *= sign;
788
789         if (*m++ != '/')
790                 return false;
791         if (!scanBoundary(&m)) {
792                 if (!strncmp(m, "XX", 2))       // not spec compliant!
793                         m += 2, sign = 0, dew = temp;
794                 else {
795                         sign = 1;
796                         if (*m == 'M')
797                                 m++, sign = -1;
798                         if (!scanNumber(&m, &dew, 2))
799                                 return false;
800                 }
801                 if (!scanBoundary(&m))
802                         return false;
803                 if (sign)
804                         _dewp = sign * dew;
805         }
806         _temp = temp;
807         _m = m;
808         _grpcount++;
809         return true;
810 }
811
812
813 double SGMetar::getRelHumidity() const
814 {
815         if (_temp == NaN || _dewp == NaN)
816                 return NaN;
817         double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
818         double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
819         return dewp * 100 / temp;
820 }
821
822
823 // [AQ]\d{4}             (spec)
824 // [AQ]\d{2}(\d{2}|//)   (Namibia)
825 bool SGMetar::scanPressure()
826 {
827         char *m = _m;
828         double factor;
829         int press, i;
830
831         if (*m == 'A')
832                 factor = SG_INHG_TO_PA / 100;
833         else if (*m == 'Q')
834                 factor = 100;
835         else
836                 return false;
837         m++;
838         if (!scanNumber(&m, &press, 2))
839                 return false;
840         press *= 100;
841         if (!strncmp(m, "//", 2))       // not spec compliant!
842                 m += 2;
843         else if (scanNumber(&m, &i, 2))
844                 press += i;
845         else
846                 return false;
847         if (!scanBoundary(&m))
848                 return false;
849         _pressure = press * factor;
850         _m = m;
851         _grpcount++;
852         return true;
853 }
854
855
856 static const char *runway_deposit[] = {
857         "clear and dry",
858         "damp",
859         "wet or puddles",
860         "frost",
861         "dry snow",
862         "wet snow",
863         "slush",
864         "ice",
865         "compacted snow",
866         "frozen ridges"
867 };
868
869
870 static const char *runway_deposit_extent[] = {
871         0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
872 };
873
874
875 static const char *runway_friction[] = {
876         0,
877         "poor braking action",
878         "poor/medium braking action",
879         "medium braking action",
880         "medium/good braking action",
881         "good braking action",
882         0, 0, 0,
883         "friction: unreliable measurement"
884 };
885
886
887 // \d\d(CLRD|[\d/]{4})(\d\d|//)
888 bool SGMetar::scanRunwayReport()
889 {
890         char *m = _m;
891         int i;
892         char id[4];
893         SGMetarRunway r;
894
895         if (!scanNumber(&m, &i, 2))
896                 return false;
897         if (i == 88)
898                 strcpy(id, "ALL");
899         else if (i == 99)
900                 strcpy(id, "REP");              // repetition of previous report
901         else if (i >= 50) {
902                 i -= 50;
903                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
904         } else
905                 id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
906
907         if (!strncmp(m, "CLRD", 4)) {
908                 m += 4;                                                 // runway cleared
909                 r._deposit_string = "cleared";
910         } else {
911                 if (scanNumber(&m, &i, 1)) {
912                         r._deposit = i;
913                         r._deposit_string = runway_deposit[i];
914                 } else if (*m == '/')
915                         m++;
916                 else
917                         return false;
918
919                 if (*m == '1' || *m == '2' || *m == '5' || *m == '9') { // extent of deposit
920                         r._extent = *m - '0';
921                         r._extent_string = runway_deposit_extent[*m - '0'];
922                 } else if (*m != '/')
923                         return false;
924
925                 m++;
926                 i = -1;
927                 if (!strncmp(m, "//", 2))
928                         m += 2;
929                 else if (!scanNumber(&m, &i, 2))
930                         return false;
931
932                 if (i == 0)
933                         r._depth = 0.0005;                              // < 1 mm deep (let's say 0.5 :-)
934                 else if (i > 0 && i <= 90)
935                         r._depth = i / 1000.0;                          // i mm deep
936                 else if (i >= 92 && i <= 98)
937                         r._depth = (i - 90) / 20.0;
938                 else if (i == 99)
939                         r._comment = "runway not in use";
940                 else if (i == -1)                                       // no depth given ("//")
941                         ;
942                 else
943                         return false;
944         }
945         i = -1;
946         if (m[0] == '/' && m[1] == '/')
947                 m += 2;
948         else if (!scanNumber(&m, &i, 2))
949                 return false;
950         if (i >= 1 && i < 90) {
951                 r._friction = i / 100.0;
952         } else if ((i >= 91 && i <= 95) || i == 99) {
953                 r._friction_string = runway_friction[i - 90];
954         }
955         if (!scanBoundary(&m))
956                 return false;
957
958         _runways[id]._deposit = r._deposit;
959         _runways[id]._deposit_string = r._deposit_string;
960         _runways[id]._extent = r._extent;
961         _runways[id]._extent_string = r._extent_string;
962         _runways[id]._depth = r._depth;
963         _runways[id]._friction = r._friction;
964         _runways[id]._friction_string = r._friction_string;
965         _runways[id]._comment = r._comment;
966         _m = m;
967         _grpcount++;
968         return true;
969 }
970
971
972 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
973 bool SGMetar::scanWindShear()
974 {
975         char *m = _m;
976         if (strncmp(m, "WS", 2))
977                 return false;
978         m += 2;
979         if (!scanBoundary(&m))
980                 return false;
981
982         if (!strncmp(m, "ALL", 3)) {
983                 m += 3;
984                 if (!scanBoundary(&m))
985                         return false;
986                 if (strncmp(m, "RWY", 3))
987                         return false;
988                 m += 3;
989                 if (*m == 'S')
990                         m++;
991                 if (!scanBoundary(&m))
992                         return false;
993                 _runways["ALL"]._wind_shear = true;
994                 _m = m;
995                 return true;
996         }
997
998         char id[4], *mm;
999         int i, cnt;
1000         for (cnt = 0;; cnt++) {                 // ??
1001                 if (strncmp(m, "RWY", 3))
1002                         break;
1003                 m += 3;
1004                 scanBoundary(&m);
1005                 mm = m;
1006                 if (!scanNumber(&m, &i, 2))
1007                         return false;
1008                 if (*m == 'L' || *m == 'C' || *m == 'R')
1009                         m++;
1010                 strncpy(id, mm, i = m - mm);
1011                 id[i] = '\0';
1012                 if (!scanBoundary(&m))
1013                         return false;
1014                 _runways[id]._wind_shear = true;
1015         }
1016         if (!cnt)
1017                 _runways["ALL"]._wind_shear = true;
1018         _m = m;
1019         return true;
1020 }
1021
1022
1023 bool SGMetar::scanTrendForecast()
1024 {
1025         char *m = _m;
1026         if (strncmp(m, "NOSIG", 5))
1027                 return false;
1028
1029         m += 5;
1030         if (!scanBoundary(&m))
1031                 return false;
1032         _m = m;
1033         return true;
1034 }
1035
1036
1037 // (BLU|WHT|GRN|YLO|AMB|RED)
1038 static const struct Token colors[] = {
1039         { "BLU", "Blue" },      // 2500 ft,  8.0 km
1040         { "WHT", "White" },     // 1500 ft,  5.0 km
1041         { "GRN", "Green" },     //  700 ft,  3.7 km
1042         { "YLO", "Yellow" },    //  300 ft,  1.6 km
1043         { "AMB", "Amber" },     //  200 ft,  0.8 km
1044         { "RED", "Red" },       // <200 ft, <0.8 km
1045         { 0, 0 }
1046 };
1047
1048
1049 bool SGMetar::scanColorState()
1050 {
1051         char *m = _m;
1052         const struct Token *a;
1053         if (!(a = scanToken(&m, colors)))
1054                 return false;
1055         if (!scanBoundary(&m))
1056                 return false;
1057         //printf(Y"Code %s\n"N, a->text);
1058         _m = m;
1059         return true;
1060 }
1061
1062
1063 bool SGMetar::scanRemark()
1064 {
1065         if (strncmp(_m, "RMK", 3))
1066                 return false;
1067         _m += 3;
1068         if (!scanBoundary(&_m))
1069                 return false;
1070
1071         while (*_m) {
1072                 if (!scanRunwayReport()) {
1073                         while (*_m && !isspace(*_m))
1074                                 _m++;
1075                         scanBoundary(&_m);
1076                 }
1077         }
1078         return true;
1079 }
1080
1081
1082 bool SGMetar::scanRemainder()
1083 {
1084         char *m = _m;
1085         if (!(strncmp(m, "NOSIG", 5))) {
1086                 m += 5;
1087                 if (scanBoundary(&m))
1088                         _m = m; //_comment.push_back("No significant tendency");
1089         }
1090
1091         if (!scanBoundary(&m))
1092                 return false;
1093         _m = m;
1094         return true;
1095 }
1096
1097
1098 bool SGMetar::scanBoundary(char **s)
1099 {
1100         if (**s && !isspace(**s))
1101                 return false;
1102         while (isspace(**s))
1103                 (*s)++;
1104         return true;
1105 }
1106
1107
1108 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1109 {
1110         int i;
1111         char *s = *src;
1112         *num = 0;
1113         for (i = 0; i < min; i++) {
1114                 if (!isdigit(*s))
1115                         return 0;
1116                 else
1117                         *num = *num * 10 + *s++ - '0';
1118         }
1119         for (; i < max && isdigit(*s); i++)
1120                 *num = *num * 10 + *s++ - '0';
1121         *src = s;
1122         return i;
1123 }
1124
1125
1126 // find longest match of str in list
1127 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1128 {
1129         const struct Token *longest = 0;
1130         int maxlen = 0, len;
1131         const char *s;
1132         for (int i = 0; (s = list[i].id); i++) {
1133                 len = strlen(s);
1134                 if (!strncmp(s, *str, len) && len > maxlen) {
1135                         maxlen = len;
1136                         longest = &list[i];
1137                 }
1138         }
1139         *str += maxlen;
1140         return longest;
1141 }
1142
1143
1144 void SGMetarCloud::set(double alt, Coverage cov)
1145 {
1146         _altitude = alt;
1147         if (cov != -1)
1148                 _coverage = cov;
1149 }
1150
1151 SGMetarCloud::Coverage SGMetarCloud::getCoverage( const std::string & coverage ) 
1152 {
1153         if( coverage == "clear" ) return COVERAGE_CLEAR;
1154         if( coverage == "few" ) return COVERAGE_FEW;
1155         if( coverage == "scattered" ) return COVERAGE_SCATTERED;
1156         if( coverage == "broken" ) return COVERAGE_BROKEN;
1157         if( coverage == "overcast" ) return COVERAGE_OVERCAST;
1158         return COVERAGE_NIL;
1159 }
1160
1161 const char * SGMetarCloud::COVERAGE_NIL_STRING = "nil";
1162 const char * SGMetarCloud::COVERAGE_CLEAR_STRING = "clear";
1163 const char * SGMetarCloud::COVERAGE_FEW_STRING = "few";
1164 const char * SGMetarCloud::COVERAGE_SCATTERED_STRING = "scattered";
1165 const char * SGMetarCloud::COVERAGE_BROKEN_STRING = "broken";
1166 const char * SGMetarCloud::COVERAGE_OVERCAST_STRING = "overcast";
1167
1168 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1169 {
1170         _distance = dist;
1171         if (dir != -1)
1172                 _direction = dir;
1173         if (mod != -1)
1174                 _modifier = mod;
1175         if (tend != 1)
1176                 _tendency = tend;
1177 }
1178
1179 #undef NaN