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 <iomanip>
42 #include <string>
43 #include <time.h>
44 #include <cstring>
45 #include <ostream>
46 #include <sstream>
47 
48 #include <simgear/debug/logstream.hxx>
49 #include <simgear/math/sg_random.h>
50 #include <simgear/structure/exception.hxx>
51 
52 #include "metar.hxx"
53 
54 #define NaN SGMetarNaN
55 
56 using std::string;
57 using std::map;
58 using std::vector;
59 
60 /**
61  * The constructor takes a Metar string
62  * The constructor throws sg_io_exceptions on failure. The "METAR"
63  * keyword has no effect (apart from incrementing the group counter
64  * @a grpcount) and can be left away. A keyword "SPECI" is
65  * likewise accepted.
66  *
67  * @param m     ICAO station id or metar string
68  *
69  * @par Examples:
70  * @code
71  * SGMetar *m = new SGMetar("METAR KSFO 061656Z 19004KT 9SM SCT100 OVC200 08/03 A3013");
72  * double t = m->getTemperature_F();
73  * delete m;
74 
75  * @endcode
76  */
SGMetar(const string & m)77 SGMetar::SGMetar(const string& m) :
78 	_grpcount(0),
79 	_x_proxy(false),
80 	_year(-1),
81 	_month(-1),
82 	_day(-1),
83 	_hour(-1),
84 	_minute(-1),
85 	_report_type(-1),
86 	_wind_dir(-1),
87 	_wind_speed(NaN),
88 	_gust_speed(NaN),
89 	_wind_range_from(-1),
90 	_wind_range_to(-1),
91 	_temp(NaN),
92 	_dewp(NaN),
93 	_pressure(NaN),
94 	_rain(false),
95 	_hail(false),
96 	_snow(false),
97 	_cavok(false)
98 {
99 	_data = new char[m.length() + 2];	// make room for " \0"
100 	strcpy(_data, m.c_str());
101 	_url = _data;
102 
103 	normalizeData();
104 
105 	_m = _data;
106 	_icao[0] = '\0';
107 
108 	// NOAA preample
109 	if (!scanPreambleDate())
110 		useCurrentDate();
111 	scanPreambleTime();
112 
113 	// METAR header
114 	scanType();
115 	if (!scanId() || !scanDate()) {
116 		delete[] _data;
117 		throw sg_io_exception("metar data bogus ", sg_location(_url));
118 	}
119 	scanModifier();
120 
121 	// base set
122 	scanWind();
123 	scanVariability();
124 	while (scanVisibility()) ;
125 	while (scanRwyVisRange()) ;
126 	while (scanWeather()) ;
127 	while (scanSkyCondition()) ;
128 	scanTemperature();
129 	scanPressure();
130 	while (scanSkyCondition()) ;
131 	while (scanRunwayReport()) ;
132 	scanWindShear();
133 
134 	// appendix
135 	while (scanColorState()) ;
136 	scanTrendForecast();
137 	while (scanRunwayReport()) ;
138 	scanRemainder();
139 	scanRemark();
140 
141 	if (_grpcount < 4) {
142 		delete[] _data;
143 		throw sg_io_exception("metar data incomplete ", sg_location(_url));
144 	}
145 
146 	_url = "";
147 }
148 
149 
150 /**
151   * Clears lists and maps to discourage access after destruction.
152   */
~SGMetar()153 SGMetar::~SGMetar()
154 {
155 	_clouds.clear();
156 	_runways.clear();
157 	_weather.clear();
158 	delete[] _data;
159 }
160 
161 
azimuthName(double d)162 static const char *azimuthName(double d)
163 {
164 	const char *dir[] = {
165 		"N", "NNE", "NE", "ENE",
166 		"E", "ESE", "SE", "SSE",
167 		"S", "SSW", "SW", "WSW",
168 		"W", "WNW", "NW", "NNW"
169 	};
170 	d += 11.25;
171 	while (d < 0)
172 		d += 360;
173 	while (d >= 360)
174 		d -= 360;
175 	return dir[int(d / 22.5)];
176 }
177 
178 
179 // round double to 10^g
rnd(double r,int g=0)180 static double rnd(double r, int g = 0)
181 {
182 	double f = pow(10.0, g);
183 	return f * floor(r / f + 0.5);
184 }
185 
186 
187 /* A manipulator that can use spaces to emulate tab characters. */
188 struct Tab
189 {
190     /* If <stops> is 0, we simply insert tab characters. Otherwise we insert
191     spaces to align with the next column at multiple of <stops>. */
TabTab192     explicit Tab(int stops)
193     :
194     _stops(stops)
195     {}
196     int _stops;
197 };
198 
operator <<(std::ostream & out,const Tab & t)199 std::ostream& operator << (std::ostream& out, const Tab& t)
200 {
201     if (t._stops == 0) {
202         return out << '\t';
203     }
204 
205     std::ostringstream& out2 = *(std::ostringstream*) &out;
206     std::string s = out2.str();
207 
208     if (t._stops < 0) {
209         if (!s.size() || s[s.size()-1] != ' ') {
210             out << ' ';
211         }
212         return out;
213     }
214 
215     auto nl = s.rfind('\n');
216     if (nl < 0) nl = 0;
217     int column = 0;
218     for (auto i = nl+1; i != s.size(); ++i) {
219         if (s[i] == '\t')
220             column = (column + t._stops) / t._stops * t._stops;
221         else
222             column += 1;
223     }
224     int column2 = (column + t._stops) / t._stops * t._stops;
225     for (int i=column; i<column2; ++i) {
226         out << ' ';
227     }
228     return out;
229 }
230 
231 /* Manipulator for SGMetarVisibility using a Tab. */
232 struct SGMetarVisibilityManip
233 {
SGMetarVisibilityManipSGMetarVisibilityManip234     explicit SGMetarVisibilityManip(const SGMetarVisibility& v, const Tab& tab)
235     :
236     _v(v),
237     _tab(tab)
238     {}
239     const SGMetarVisibility&    _v;
240     const Tab&                  _tab;
241 };
242 
operator <<(std::ostream & out,const SGMetarVisibilityManip & v)243 std::ostream& operator << (std::ostream& out, const SGMetarVisibilityManip& v)
244 {
245 	int m = v._v.getModifier();
246 	const char *mod;
247 	if (m == SGMetarVisibility::GREATER_THAN)
248 		mod = ">=";
249 	else if (m == SGMetarVisibility::LESS_THAN)
250 		mod = "<";
251 	else
252 		mod = "";
253 	out << mod;
254 
255 	double dist = rnd(v._v.getVisibility_m(), 1);
256 	if (dist < 1000.0)
257 		out << rnd(dist, 1) << " m";
258 	else
259 		out << rnd(dist / 1000.0, -1) << " km";
260 
261 	const char *dir = "";
262 	int i;
263 	if ((i = v._v.getDirection()) != -1) {
264 		dir = azimuthName(i);
265 		out << " " << dir;
266 	}
267         out << v._tab << v._tab << v._tab << v._tab << v._tab;
268         out << mod << rnd(v._v.getVisibility_sm(), -1) << " US-miles " << dir;
269         return out;
270 }
271 
272 
getDescription(int tabstops) const273 std::string SGMetar::getDescription(int tabstops) const
274 {
275         std::ostringstream  out;
276 	const char *s;
277 	char buf[256];
278 	double d;
279 	int i, lineno;
280         Tab tab(tabstops);
281 
282 	if ((i = getReportType()) == SGMetar::AUTO)
283 		out << "(METAR automatically generated)\n";
284 	else if (i == SGMetar::COR)
285 		out << "(METAR manually corrected)\n";
286 	else if (i == SGMetar::RTD)
287 		out << "(METAR routine delayed)\n";
288 
289 	out << "Airport-Id:" << tab << tab << getId() << "\n";
290 
291 	// date/time
292 	int year = getYear();
293 	int month = getMonth();
294 	out << "Report time:" << tab << tab << year << '/' << month << '/' << getDay();
295 	out << ' ' << getHour() << ':';
296 	out << std::setw(2) << std::setfill('0') << getMinute() << " UTC\n";
297 
298 
299 	// visibility
300 	SGMetarVisibility minvis = getMinVisibility();
301 	SGMetarVisibility maxvis = getMaxVisibility();
302 	double min = minvis.getVisibility_m();
303 	double max = maxvis.getVisibility_m();
304 	if (min != NaN) {
305 		if (max != NaN) {
306 			out << "min. Visibility:" << tab << SGMetarVisibilityManip(minvis, tab) << "\n";
307 			out << "max. Visibility:" << tab << SGMetarVisibilityManip(maxvis, tab) << "\n";
308 		} else {
309 			out << "Visibility:" << tab << tab << SGMetarVisibilityManip(minvis, tab) << "\n";
310                 }
311 	}
312 
313 
314 	// directed visibility
315 	const SGMetarVisibility *dirvis = getDirVisibility();
316 	for (i = 0; i < 8; i++, dirvis++)
317 		if (dirvis->getVisibility_m() != NaN)
318 			out << tab << tab << tab << SGMetarVisibilityManip(*dirvis, tab) << "\n";
319 
320 
321 	// vertical visibility
322 	SGMetarVisibility vertvis = getVertVisibility();
323 	if ((d = vertvis.getVisibility_ft()) != NaN)
324 		out << "Vert. visibility:" << tab << SGMetarVisibilityManip(vertvis, tab) << "\n";
325 	else if (vertvis.getModifier() == SGMetarVisibility::NOGO)
326 		out << "Vert. visibility:" << tab << "impossible to determine" << "\n";
327 
328 
329 	// wind
330 	d = getWindSpeed_kmh();
331 	out << "Wind:" << tab << tab << tab;
332 	if (d < .1)
333 		out << "none" << "\n";
334 	else {
335 		if ((i = getWindDir()) == -1)
336 			out << "from variable directions";
337 		else
338 			out << "from the " << azimuthName(i) << " (" << i << " deg)";
339 		out << " at " << rnd(d, -1) << " km/h";
340 
341 		out << tab << tab << rnd(getWindSpeed_kt(), -1) << " kt";
342 		out << " = " << rnd(getWindSpeed_mph(), -1) << " mph";
343 		out << " = " << rnd(getWindSpeed_mps(), -1) << " m/s";
344 		out << "\n";
345 
346 		d = getGustSpeed_kmh();
347                 if (d != NaN && d != 0) {
348 			out << tab << tab << tab << "with gusts at " << rnd(d, -1) << " km/h";
349 			out << tab << tab << tab << rnd(getGustSpeed_kt(), -1) << " kt";
350 			out << " = " << rnd(getGustSpeed_mph(), -1) << " mph";
351 			out << " = " << rnd(getGustSpeed_mps(), -1) << " m/s";
352 			out << "\n";
353 		}
354 
355 		int from = getWindRangeFrom();
356 		int to = getWindRangeTo();
357 		if (from != to) {
358 			out << tab << tab << tab << "variable from " << azimuthName(from);
359 			out << " to " << azimuthName(to);
360 			out << " (" << from << "deg --" << to << " deg)" << "\n";
361 		}
362 	}
363 
364 
365 	// temperature/humidity/air pressure
366 	if ((d = getTemperature_C()) != NaN) {
367 		out << "Temperature:" << tab << tab << d << " C" << tab << tab << tab << tab << tab;
368 		out << rnd(getTemperature_F(), -1) << " F" << "\n";
369 
370 		if ((d = getDewpoint_C()) != NaN) {
371 			out << "Dewpoint:" << tab << tab << d << " C" << tab << tab << tab << tab << tab;
372 			out << rnd(getDewpoint_F(), -1) << " F"  << "\n";
373 			out << "Rel. Humidity: " << tab << tab << rnd(getRelHumidity()) << " %" << "\n";
374 		}
375 	}
376 	if ((d = getPressure_hPa()) != NaN) {
377 		out << "Pressure:" << tab << tab << rnd(d) << " hPa" << tab << tab << tab << tab;
378 		out << rnd(getPressure_inHg(), -2) << " in. Hg" << "\n";
379 	}
380 
381 
382 	// weather phenomena
383 	vector<string> wv = getWeather();
384 	vector<string>::iterator weather;
385 	for (i = 0, weather = wv.begin(); weather != wv.end(); weather++, i++) {
386 		out << (i ? ", " : "Weather:") << tab << tab << weather->c_str();
387 	}
388 	if (i)
389 		out << "\n";
390 
391 
392 	// cloud layers
393 	const char *coverage_string[5] = {
394 		"clear skies", "few clouds", "scattered clouds", "broken clouds", "sky overcast"
395 	};
396 	vector<SGMetarCloud> cv = getClouds();
397 	vector<SGMetarCloud>::iterator cloud;
398 	for (lineno = 0, cloud = cv.begin(); cloud != cv.end(); cloud++, lineno++) {
399                 if (lineno) out << tab << tab << tab;
400                 else    out << "Sky condition:" << tab << tab;
401 
402 		if ((i = cloud->getCoverage()) != -1)
403 			out << coverage_string[i];
404 		if ((d = cloud->getAltitude_ft()) != NaN)
405 			out << " at " << rnd(d, 1) << " ft";
406 		if ((s = cloud->getTypeLongString()))
407 			out << " (" << s << ')';
408 		if (d != NaN)
409 			out << tab << tab << tab << rnd(cloud->getAltitude_m(), 1) << " m";
410 		out << "\n";
411 	}
412 
413 
414 	// runways
415 	map<string, SGMetarRunway> rm = getRunways();
416 	map<string, SGMetarRunway>::iterator runway;
417 	for (runway = rm.begin(); runway != rm.end(); runway++) {
418 		lineno = 0;
419 		if (!strcmp(runway->first.c_str(), "ALL"))
420 			out << "All runways:" << tab << tab;
421 		else
422 			out << "Runway " << runway->first << ":" << tab << tab;
423 		SGMetarRunway rwy = runway->second;
424 
425 		// assemble surface string
426 		vector<string> surface;
427 		if ((s = rwy.getDepositString()) && strlen(s))
428 			surface.push_back(s);
429 		if ((s = rwy.getExtentString()) && strlen(s))
430 			surface.push_back(s);
431 		if ((d = rwy.getDepth()) != NaN) {
432 			sprintf(buf, "%.1lf mm", d * 1000.0);
433 			surface.push_back(buf);
434 		}
435 		if ((s = rwy.getFrictionString()) && strlen(s))
436 			surface.push_back(s);
437 		if ((d = rwy.getFriction()) != NaN) {
438 			sprintf(buf, "friction: %.2lf", d);
439 			surface.push_back(buf);
440 		}
441 
442 		if (! surface.empty()) {
443 			vector<string>::iterator rwysurf = surface.begin();
444 			for (i = 0; rwysurf != surface.end(); rwysurf++, i++) {
445 				if (i)
446 					out << ", ";
447 				out << *rwysurf;
448 			}
449 			lineno++;
450 		}
451 
452 		// assemble visibility string
453 		SGMetarVisibility minvis = rwy.getMinVisibility();
454 		SGMetarVisibility maxvis = rwy.getMaxVisibility();
455 		if ((d = minvis.getVisibility_m()) != NaN) {
456 			if (lineno++)
457 				out << "\n" << tab << tab << tab;
458 			out << SGMetarVisibilityManip(minvis, tab);
459 		}
460 		if (maxvis.getVisibility_m() != d) {
461 			out << "\n" << tab << tab << tab << SGMetarVisibilityManip(maxvis, tab) << "\n";
462 			lineno++;
463 		}
464 
465 		if (rwy.getWindShear()) {
466 			if (lineno++)
467 				out << "\n" << tab << tab << tab;
468 			out << "critical wind shear" << "\n";
469 		}
470 		out << "\n";
471 	}
472 	out << "\n";
473         return out.str();
474 }
475 
useCurrentDate()476 void SGMetar::useCurrentDate()
477 {
478 	struct tm now;
479 	time_t now_sec = time(0);
480 #ifdef _WIN32
481 	now = *gmtime(&now_sec);
482 #else
483 	gmtime_r(&now_sec, &now);
484 #endif
485 	_year = now.tm_year + 1900;
486 	_month = now.tm_mon + 1;
487 }
488 
489 /**
490   * Replace any number of subsequent spaces by just one space, and add
491   * a trailing space. This makes scanning for things like "ALL RWY" easier.
492   */
normalizeData()493 void SGMetar::normalizeData()
494 {
495 	char *src, *dest;
496 	for (src = dest = _data; (*dest++ = *src++); )
497 		while (*src == ' ' && src[1] == ' ')
498 			src++;
499 	for (dest--; isspace(*--dest); ) ;
500 	*++dest = ' ';
501 	*++dest = '\0';
502 }
503 
504 
505 // \d\d\d\d/\d\d/\d\d
scanPreambleDate()506 bool SGMetar::scanPreambleDate()
507 {
508 	char *m = _m;
509 	int year, month, day;
510 	if (!scanNumber(&m, &year, 4))
511 		return false;
512 	if (*m++ != '/')
513 		return false;
514 	if (!scanNumber(&m, &month, 2))
515 		return false;
516 	if (*m++ != '/')
517 		return false;
518 	if (!scanNumber(&m, &day, 2))
519 		return false;
520 	if (!scanBoundary(&m))
521 		return false;
522 	_year = year;
523 	_month = month;
524 	_day = day;
525 	_m = m;
526 	return true;
527 }
528 
529 
530 // \d\d:\d\d
scanPreambleTime()531 bool SGMetar::scanPreambleTime()
532 {
533 	char *m = _m;
534 	int hour, minute;
535 	if (!scanNumber(&m, &hour, 2))
536 		return false;
537 	if (*m++ != ':')
538 		return false;
539 	if (!scanNumber(&m, &minute, 2))
540 		return false;
541 	if (!scanBoundary(&m))
542 		return false;
543 	_hour = hour;
544 	_minute = minute;
545 	_m = m;
546 	return true;
547 }
548 
549 
550 // (METAR|SPECI)
scanType()551 bool SGMetar::scanType()
552 {
553 	if (strncmp(_m, "METAR ", 6) && strncmp(_m, "SPECI ", 6))
554 		return false;
555 	_m += 6;
556 	_grpcount++;
557 	return true;
558 }
559 
560 
561 // [A-Z]{4}
scanId()562 bool SGMetar::scanId()
563 {
564 	char *m = _m;
565 	for (int i = 0; i < 4; m++, i++)
566 		if (!(isalpha(*m) || isdigit(*m)))
567 			return false;
568 	if (!scanBoundary(&m))
569 		return false;
570 	strncpy(_icao, _m, 4);
571 	_icao[4] = '\0';
572 	_m = m;
573 	_grpcount++;
574 	return true;
575 }
576 
577 
578 // \d{6}Z
scanDate()579 bool SGMetar::scanDate()
580 {
581 	char *m = _m;
582 	int day, hour, minute;
583 	if (!scanNumber(&m, &day, 2))
584 		return false;
585 	if (!scanNumber(&m, &hour, 2))
586 		return false;
587 	if (!scanNumber(&m, &minute, 2))
588 		return false;
589 	if (*m++ != 'Z')
590 		return false;
591 	if (!scanBoundary(&m))
592 		return false;
593 	_day = day;
594 	_hour = hour;
595 	_minute = minute;
596 	_m = m;
597 	_grpcount++;
598 	return true;
599 }
600 
601 
602 // (NIL|AUTO|COR|RTD)
scanModifier()603 bool SGMetar::scanModifier()
604 {
605 	char *m = _m;
606 	int type;
607 	if (!strncmp(m, "NIL", 3)) {
608 		_m += strlen(_m);
609 		return true;
610 	}
611 	if (!strncmp(m, "AUTO", 4))			// automatically generated
612 		m += 4, type = AUTO;
613 	else if (!strncmp(m, "COR", 3))			// manually corrected
614 		m += 3, type = COR;
615 	else if (!strncmp(m, "RTD", 3))			// routine delayed
616 		m += 3, type = RTD;
617 	else
618 		return false;
619 	if (!scanBoundary(&m))
620 		return false;
621 	_report_type = type;
622 	_m = m;
623 	_grpcount++;
624 	return true;
625 }
626 
627 
628 // (\d{3}|VRB)\d{1,3}(G\d{2,3})?(KT|KMH|MPS)
scanWind()629 bool SGMetar::scanWind()
630 {
631 	char *m = _m;
632 	int dir;
633 	if (!strncmp(m, "VRB", 3))
634 		m += 3, dir = -1;
635 	else if (!strncmp(m, "///", 3))	// direction not measurable
636 		m += 3, dir = -1;
637 	else if (!scanNumber(&m, &dir, 3))
638 		return false;
639 
640 	int i;
641 	if (!strncmp(m, "//", 2))	// speed not measurable
642 		m += 2, i = -1;
643 	else if (!scanNumber(&m, &i, 2, 3))
644 		return false;
645 	double speed = i;
646 
647 	double gust = NaN;
648 	if (*m == 'G') {
649 		m++;
650 		if (!strncmp(m, "//", 2))	// speed not measurable
651 			m += 2, i = -1;
652 		else if (!scanNumber(&m, &i, 2, 3))
653 			return false;
654 
655 		if (i != -1)
656 			gust = i;
657 	}
658 
659 	double factor;
660 	if (!strncmp(m, "KT", 2))
661 		m += 2, factor = SG_KT_TO_MPS;
662 	else if (!strncmp(m, "KMH", 3))		// invalid Km/h
663 		m += 3, factor = SG_KMH_TO_MPS;
664 	else if (!strncmp(m, "KPH", 3))		// invalid Km/h
665 		m += 3, factor = SG_KMH_TO_MPS;
666 	else if (!strncmp(m, "MPS", 3))
667 		m += 3, factor = 1.0;
668 	else if (!strncmp(m, " ", 1))		// default to Knots
669 		factor = SG_KT_TO_MPS;
670 	else
671 		return false;
672 	if (!scanBoundary(&m))
673 		return false;
674 	_m = m;
675 	_wind_dir = dir;
676 	_wind_speed = speed * factor;
677 	if (gust != NaN)
678 		_gust_speed = gust * factor;
679 	_grpcount++;
680 	return true;
681 }
682 
683 
684 // \d{3}V\d{3}
scanVariability()685 bool SGMetar::scanVariability()
686 {
687 	char *m = _m;
688 	int from, to;
689 
690 	if (!strncmp(m, "///", 3))	// direction not measurable
691 		m += 3, from = -1;
692 	else if (!scanNumber(&m, &from, 3))
693 		return false;
694 
695 	if (*m++ != 'V')
696 		return false;
697 
698 	if (!strncmp(m, "///", 3))	// direction not measurable
699 		m += 3, to = -1;
700 	else if (!scanNumber(&m, &to, 3))
701 		return false;
702 
703 	if (!scanBoundary(&m))
704 		return false;
705 
706 	_m = m;
707 	_wind_range_from = from;
708 	_wind_range_to = to;
709 	_grpcount++;
710 
711 	return true;
712 }
713 
714 
scanVisibility()715 bool SGMetar::scanVisibility()
716 // TODO: if only directed vis are given, do still set min/max
717 {
718 	if (!strncmp(_m, "//// ", 5)) {         // spec compliant?
719 		_m += 5;
720 		_grpcount++;
721 		return true;
722 	}
723 
724 	char *m = _m;
725 	double distance;
726 	int i, dir = -1;
727 	int modifier = SGMetarVisibility::EQUALS;
728 // \d{4}(N|NE|E|SE|S|SW|W|NW)?
729 	if (scanNumber(&m, &i, 4)) {
730 		if( strncmp( m, "NDV",3 ) == 0 ) {
731 			m+=3; // tolerate NDV (no directional validation)
732 		} else if (*m == 'E') {
733 			m++, dir = 90;
734 		} else if (*m == 'W') {
735 			m++, dir = 270;
736 		} else if (*m == 'N') {
737 			m++;
738 			if (*m == 'E')
739 				m++, dir = 45;
740 			else if (*m == 'W')
741 				m++, dir = 315;
742 			else
743 				dir = 0;
744 		} else if (*m == 'S') {
745 			m++;
746 			if (*m == 'E')
747 				m++, dir = 135;
748 			else if (*m == 'W')
749 				m++, dir = 225;
750 			else
751 				dir = 180;
752                 }
753 		if (i == 0)
754 			i = 50, modifier = SGMetarVisibility::LESS_THAN;
755 		else if (i == 9999)
756 			i++, modifier = SGMetarVisibility::GREATER_THAN;
757 		distance = i;
758 	} else {
759 // M?(\d{1,2}|\d{1,2}/\d{1,2}|\d{1,2} \d{1,2}/\d{1,2})(SM|KM)
760 		if (*m == 'M')
761 			m++, modifier = SGMetarVisibility::LESS_THAN;
762 
763 		if (!scanNumber(&m, &i, 1, 2))
764 			return false;
765 		distance = i;
766 
767 		if (*m == '/') {
768 			m++;
769 			if (!scanNumber(&m, &i, 1, 2))
770 				return false;
771 			distance /= i;
772 		} else if (*m == ' ') {
773 			m++;
774 			int denom;
775 			if (!scanNumber(&m, &i, 1, 2))
776 				return false;
777 			if (*m++ != '/')
778 				return false;
779 			if (!scanNumber(&m, &denom, 1, 2))
780 				return false;
781 			distance += (double)i / denom;
782 		}
783 
784 		if (!strncmp(m, "SM", 2))
785 			distance *= SG_SM_TO_METER, m += 2;
786 		else if (!strncmp(m, "KM", 2))
787 			distance *= 1000, m += 2;
788 		else
789 			return false;
790 	}
791 	if (!scanBoundary(&m))
792 		return false;
793 
794 	SGMetarVisibility *v;
795 	if (dir != -1)
796 		v = &_dir_visibility[dir / 45];
797 	else if (_min_visibility._distance == NaN)
798 		v = &_min_visibility;
799 	else
800 		v = &_max_visibility;
801 
802 	v->_distance = distance;
803 	v->_modifier = modifier;
804 	v->_direction = dir;
805 	_m = m;
806 	_grpcount++;
807 	return true;
808 }
809 
810 
811 // R\d\d[LCR]?/([PM]?\d{4}V)?[PM]?\d{4}(FT)?[DNU]?
scanRwyVisRange()812 bool SGMetar::scanRwyVisRange()
813 {
814 	char *m = _m;
815 	int i;
816 	SGMetarRunway r;
817 
818 	if (*m++ != 'R')
819 		return false;
820 	if (!scanNumber(&m, &i, 2))
821 		return false;
822 	if (*m == 'L' || *m == 'C' || *m == 'R')
823 		m++;
824 
825 	char id[4];
826 	strncpy(id, _m + 1, i = m - _m - 1);
827 	id[i] = '\0';
828 
829 	if (*m++ != '/')
830 		return false;
831 
832 	int from, to;
833 	if (*m == 'P')
834 		m++, r._min_visibility._modifier = SGMetarVisibility::GREATER_THAN;
835 	else if (*m == 'M')
836 		m++, r._min_visibility._modifier = SGMetarVisibility::LESS_THAN;
837 	if (!scanNumber(&m, &from, 4))
838 		return false;
839 	if (*m == 'V') {
840 		m++;
841 		if (*m == 'P')
842 			m++, r._max_visibility._modifier = SGMetarVisibility::GREATER_THAN;
843 		else if (*m == 'M')
844 			m++, r._max_visibility._modifier = SGMetarVisibility::LESS_THAN;
845 		if (!scanNumber(&m, &to, 4))
846 			return false;
847 	} else
848 		to = from;
849 
850 	if (!strncmp(m, "FT", 2)) {
851 		from = int(from * SG_FEET_TO_METER);
852 		to = int(to * SG_FEET_TO_METER);
853 		m += 2;
854 	}
855 	r._min_visibility._distance = from;
856 	r._max_visibility._distance = to;
857 
858 	if (*m == '/')					// this is not in the spec!
859 		m++;
860 	if (*m == 'D')
861 		m++, r._min_visibility._tendency = SGMetarVisibility::DECREASING;
862 	else if (*m == 'N')
863 		m++, r._min_visibility._tendency = SGMetarVisibility::STABLE;
864 	else if (*m == 'U')
865 		m++, r._min_visibility._tendency = SGMetarVisibility::INCREASING;
866 
867 	if (!scanBoundary(&m))
868 		return false;
869 	_m = m;
870 
871 	_runways[id]._min_visibility = r._min_visibility;
872 	_runways[id]._max_visibility = r._max_visibility;
873 	_grpcount++;
874 	return true;
875 }
876 
877 
878 static const struct Token special[] = {
879 	{ "NSW",  "no significant weather" },
880 /*	{ "VCSH", "showers in the vicinity" },
881 	{ "VCTS", "thunderstorm in the vicinity" }, */
882 	{ 0, 0 }
883 };
884 
885 
886 static const struct Token description[] = {
887 	{ "SH", "showers of" },
888 	{ "TS", "thunderstorm with" },
889 	{ "BC", "patches of" },
890 	{ "BL", "blowing" },
891 	{ "DR", "low drifting" },
892 	{ "FZ", "freezing" },
893 	{ "MI", "shallow" },
894 	{ "PR", "partial" },
895 	{ 0, 0 }
896 };
897 
898 
899 static const struct Token phenomenon[] = {
900 	{ "DZ",   "drizzle" },
901 	{ "GR",   "hail" },
902 	{ "GS",   "small hail and/or snow pellets" },
903 	{ "IC",   "ice crystals" },
904 	{ "PE",   "ice pellets" },
905 	{ "RA",   "rain" },
906 	{ "SG",   "snow grains" },
907 	{ "SN",   "snow" },
908 	{ "UP",   "unknown precipitation" },
909 	{ "BR",   "mist" },
910 	{ "DU",   "widespread dust" },
911 	{ "FG",   "fog" },
912 	{ "FGBR", "fog bank" },
913 	{ "FU",   "smoke" },
914 	{ "HZ",   "haze" },
915 	{ "PY",   "spray" },
916 	{ "SA",   "sand" },
917 	{ "VA",   "volcanic ash" },
918 	{ "DS",   "duststorm" },
919 	{ "FC",   "funnel cloud/tornado waterspout" },
920 	{ "PO",   "well-developed dust/sand whirls" },
921 	{ "SQ",   "squalls" },
922 	{ "SS",   "sandstorm" },
923 	{ "UP",   "unknown" },	// ... due to failed automatic acquisition
924 	{ 0, 0 }
925 };
926 
927 
928 // (+|-|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}
scanWeather()929 bool SGMetar::scanWeather()
930 {
931 	char *m = _m;
932 	string weather;
933 	const struct Token *a;
934 
935 	// @see WMO-49 Section 4.4.2.9
936 	// Denotes a temporary failure of the sensor
937 	if (!strncmp(m, "// ", 3)) {
938 		_m += 3;
939 		_grpcount++;
940 		return false;
941 	}
942 
943 	if ((a = scanToken(&m, special))) {
944 		if (!scanBoundary(&m))
945 			return false;
946 		_weather.push_back(a->text);
947 		_m = m;
948 		return true;
949 	}
950 
951 	string pre, post;
952 	struct Weather w;
953 	if (*m == '-')
954 		m++, pre = "light ", w.intensity = LIGHT;
955 	else if (*m == '+')
956 		m++, pre = "heavy ", w.intensity = HEAVY;
957 	else if (!strncmp(m, "VC", 2))
958         m += 2, post = "in the vicinity ", w.vincinity=true;
959 	else
960 		pre = "moderate ", w.intensity = MODERATE;
961 
962 	int i;
963 	for (i = 0; i < 3; i++) {
964 		if (!(a = scanToken(&m, description)))
965 			break;
966 		w.descriptions.push_back(a->id);
967 		weather += string(a->text) + " ";
968 	}
969 
970 	for (i = 0; i < 3; i++) {
971 		if (!(a = scanToken(&m, phenomenon)))
972 			break;
973         w.phenomena.push_back(a->id);
974 		weather += string(a->text) + " ";
975 		if (!strcmp(a->id, "RA"))
976 			_rain = w.intensity;
977 		else if (!strcmp(a->id, "DZ"))
978 			_rain = LIGHT;
979 		else if (!strcmp(a->id, "HA"))
980 			_hail = w.intensity;
981 		else if (!strcmp(a->id, "SN"))
982 			_snow = w.intensity;
983 	}
984 	if (!weather.length())
985 		return false;
986 	if (!scanBoundary(&m))
987 		return false;
988 	_m = m;
989 	weather = pre + weather + post;
990 	weather.erase(weather.length() - 1);
991 	_weather.push_back(weather);
992     if( ! w.phenomena.empty() ) {
993         _weather2.push_back( w );
994     }
995     _grpcount++;
996     return true;
997 }
998 
999 
1000 static const struct Token cloud_types[] = {
1001 	{ "AC",    "altocumulus" },
1002 	{ "ACC",   "altocumulus castellanus" },
1003 	{ "ACSL",  "altocumulus standing lenticular" },
1004 	{ "AS",    "altostratus" },
1005 	{ "CB",    "cumulonimbus" },
1006 	{ "CBMAM", "cumulonimbus mammatus" },
1007 	{ "CC",    "cirrocumulus" },
1008 	{ "CCSL",  "cirrocumulus standing lenticular" },
1009 	{ "CI",    "cirrus" },
1010 	{ "CS",    "cirrostratus" },
1011 	{ "CU",    "cumulus" },
1012 	{ "CUFRA", "cumulus fractus" },
1013 	{ "NS",    "nimbostratus" },
1014 	{ "SAC",   "stratoaltocumulus" },		// guessed
1015 	{ "SC",    "stratocumulus" },
1016 	{ "SCSL",  "stratocumulus standing lenticular" },
1017 	{ "ST",    "stratus" },
1018 	{ "STFRA", "stratus fractus" },
1019 	{ "TCU",   "towering cumulus" },
1020 	{ 0, 0 }
1021 };
1022 
1023 #include <iostream>
1024 // (FEW|SCT|BKN|OVC|SKC|CLR|CAVOK|VV)([0-9]{3}|///)?[:cloud_type:]?
scanSkyCondition()1025 bool SGMetar::scanSkyCondition()
1026 {
1027 	char *m = _m;
1028 	int i;
1029 	SGMetarCloud cl;
1030 
1031 	if (!strncmp(m, "//////", 6)) {
1032 		m += 6;
1033 		if (!scanBoundary(&m))
1034 			return false;
1035 		_m = m;
1036 		return true;
1037 	}
1038 
1039 	if (!strncmp(m, "CLR", i = 3)				// clear
1040 			|| !strncmp(m, "SKC", i = 3)		// sky clear
1041 			|| !strncmp(m, "NCD", i = 3)		// nil cloud detected
1042 			|| !strncmp(m, "NSC", i = 3)		// no significant clouds
1043 			|| !strncmp(m, "CAVOK", i = 5))	{	// ceiling and visibility OK (implies 9999)
1044 		m += i;
1045 		if (!scanBoundary(&m))
1046 			return false;
1047 
1048 		if (i == 3) {
1049 			cl._coverage = SGMetarCloud::COVERAGE_CLEAR;
1050 			_clouds.push_back(cl);
1051 		} else {
1052 			_cavok = true;
1053 		}
1054 		_m = m;
1055 		return true;
1056 	}
1057 
1058 	if (!strncmp(m, "VV", i = 2))				// vertical visibility
1059 		;
1060 	else if (!strncmp(m, "FEW", i = 3))
1061         cl._coverage = SGMetarCloud::COVERAGE_FEW;
1062 	else if (!strncmp(m, "SCT", i = 3))
1063         cl._coverage = SGMetarCloud::COVERAGE_SCATTERED;
1064 	else if (!strncmp(m, "BKN", i = 3))
1065         cl._coverage = SGMetarCloud::COVERAGE_BROKEN;
1066 	else if (!strncmp(m, "OVC", i = 3))
1067         cl._coverage = SGMetarCloud::COVERAGE_OVERCAST;
1068 	else
1069 		return false;
1070 	m += i;
1071 
1072 	if (!strncmp(m, "///", 3))	{ // vis not measurable (e.g. because of heavy snowing)
1073 		m += 3, i = -1;
1074 		sg_srandom_time();
1075 		// randomize the base height to avoid the black sky issue
1076 		i = 50 + static_cast<int>(sg_random() * 250.0);		// range [5,000, 30,000]
1077 	} else if (scanBoundary(&m)) {
1078 		_m = m;
1079 		return true;				// ignore single OVC/BKN/...
1080 	} else if (!scanNumber(&m, &i, 3))
1081 		i = -1;
1082 
1083 	if (cl._coverage == SGMetarCloud::COVERAGE_NIL) {
1084 		if (!scanBoundary(&m))
1085 			return false;
1086 		if (i == -1)			// 'VV///'
1087 			_vert_visibility._modifier = SGMetarVisibility::NOGO;
1088 		else
1089 			_vert_visibility._distance = i * 100 * SG_FEET_TO_METER;
1090 		_m = m;
1091 		return true;
1092 	}
1093 
1094 	if (i != -1)
1095 		cl._altitude = i * 100 * SG_FEET_TO_METER;
1096 
1097 	const struct Token *a;
1098 	if ((a = scanToken(&m, cloud_types))) {
1099 		cl._type = a->id;
1100 		cl._type_long = a->text;
1101 	}
1102 
1103 	// @see WMO-49 Section 4.5.4.5
1104 	// Denotes temporary failure of sensor and covers cases like FEW045///
1105 	if (!strncmp(m, "///", 3))
1106 		m += 3;
1107 	if (!scanBoundary(&m))
1108 		return false;
1109 	_clouds.push_back(cl);
1110 
1111 	_m = m;
1112 	_grpcount++;
1113 	return true;
1114 }
1115 
1116 
1117 // M?[0-9]{2}/(M?[0-9]{2})?            (spec)
1118 // (M?[0-9]{2}|XX)/(M?[0-9]{2}|XX)?    (Namibia)
scanTemperature()1119 bool SGMetar::scanTemperature()
1120 {
1121 	char *m = _m;
1122 	int sign = 1, temp, dew;
1123 	if (!strncmp(m, "XX/XX", 5)) {		// not spec compliant!
1124 		_m += 5;
1125 		return scanBoundary(&_m);
1126 	}
1127 
1128 	if (*m == 'M')
1129 		m++, sign = -1;
1130 	if (!scanNumber(&m, &temp, 2))
1131 		return false;
1132 	temp *= sign;
1133 
1134 	if (*m++ != '/')
1135 		return false;
1136 	if (!scanBoundary(&m)) {
1137 		if (!strncmp(m, "XX", 2))	// not spec compliant!
1138 			m += 2, sign = 0, dew = temp;
1139 		else {
1140 			sign = 1;
1141 			if (*m == 'M')
1142 				m++, sign = -1;
1143 			if (!scanNumber(&m, &dew, 2))
1144 				return false;
1145 		}
1146 		if (!scanBoundary(&m))
1147 			return false;
1148 		if (sign)
1149 			_dewp = sign * dew;
1150 	}
1151 	_temp = temp;
1152 	_m = m;
1153 	_grpcount++;
1154 	return true;
1155 }
1156 
1157 
getRelHumidity() const1158 double SGMetar::getRelHumidity() const
1159 {
1160 	if (_temp == NaN || _dewp == NaN)
1161 		return NaN;
1162 	double dewp = pow(10.0, 7.5 * _dewp / (237.7 + _dewp));
1163 	double temp = pow(10.0, 7.5 * _temp / (237.7 + _temp));
1164 	return dewp * 100 / temp;
1165 }
1166 
1167 
1168 // [AQ]\d{4}             (spec)
1169 // [AQ]\d{2}(\d{2}|//)   (Namibia)
scanPressure()1170 bool SGMetar::scanPressure()
1171 {
1172 	char *m = _m;
1173 	double factor;
1174 	int press, i;
1175 
1176 	if (*m == 'A')
1177 		factor = SG_INHG_TO_PA / 100;
1178 	else if (*m == 'Q')
1179 		factor = 100;
1180 	else
1181 		return false;
1182 	m++;
1183 	if (!scanNumber(&m, &press, 2))
1184 		return false;
1185 	press *= 100;
1186 	if (!strncmp(m, "//", 2))	// not spec compliant!
1187 		m += 2;
1188 	else if (scanNumber(&m, &i, 2))
1189 		press += i;
1190 	else
1191 		return false;
1192 	if (!scanBoundary(&m))
1193 		return false;
1194 	_pressure = press * factor;
1195 	_m = m;
1196 	_grpcount++;
1197 	return true;
1198 }
1199 
1200 
1201 static const char *runway_deposit[] = {
1202 	"clear and dry",
1203 	"damp",
1204 	"wet or puddles",
1205 	"frost",
1206 	"dry snow",
1207 	"wet snow",
1208 	"slush",
1209 	"ice",
1210 	"compacted snow",
1211 	"frozen ridges"
1212 };
1213 
1214 
1215 static const char *runway_deposit_extent[] = {
1216 	0, "1-10%", "11-25%", 0, 0, "26-50%", 0, 0, 0, "51-100%"
1217 };
1218 
1219 
1220 static const char *runway_friction[] = {
1221 	0,
1222 	"poor braking action",
1223 	"poor/medium braking action",
1224 	"medium braking action",
1225 	"medium/good braking action",
1226 	"good braking action",
1227 	0, 0, 0,
1228 	"friction: unreliable measurement"
1229 };
1230 
1231 
1232 // \d\d(CLRD|[\d/]{4})(\d\d|//)
scanRunwayReport()1233 bool SGMetar::scanRunwayReport()
1234 {
1235 	char *m = _m;
1236 	int i;
1237 	char id[4];
1238 	SGMetarRunway r;
1239 
1240 	if (!scanNumber(&m, &i, 2))
1241 		return false;
1242 	if (i == 88)
1243 		strcpy(id, "ALL");
1244 	else if (i == 99)
1245 		strcpy(id, "REP");		// repetition of previous report
1246 	else if (i >= 50) {
1247 		i -= 50;
1248 		id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = 'R', id[3] = '\0';
1249 	} else
1250 		id[0] = i / 10 + '0', id[1] = i % 10 + '0', id[2] = '\0';
1251 
1252 	if (!strncmp(m, "CLRD", 4)) {
1253 		m += 4;							// runway cleared
1254 		r._deposit_string = "cleared";
1255 	} else {
1256 		if (scanNumber(&m, &i, 1)) {
1257 			r._deposit = i;
1258 			r._deposit_string = runway_deposit[i];
1259 		} else if (*m == '/')
1260 			m++;
1261 		else
1262 			return false;
1263 
1264 		if (*m == '1' || *m == '2' || *m == '5' || *m == '9') {	// extent of deposit
1265 			r._extent = *m - '0';
1266 			r._extent_string = runway_deposit_extent[*m - '0'];
1267 		} else if (*m != '/')
1268 			return false;
1269 
1270 		m++;
1271 		i = -1;
1272 		if (!strncmp(m, "//", 2))
1273 			m += 2;
1274 		else if (!scanNumber(&m, &i, 2))
1275 			return false;
1276 
1277 		if (i == 0)
1278 			r._depth = 0.0005;				// < 1 mm deep (let's say 0.5 :-)
1279 		else if (i > 0 && i <= 90)
1280 			r._depth = i / 1000.0;				// i mm deep
1281 		else if (i >= 92 && i <= 98)
1282 			r._depth = (i - 90) / 20.0;
1283 		else if (i == 99)
1284 			r._comment = "runway not in use";
1285 		else if (i == -1)					// no depth given ("//")
1286 			;
1287 		else
1288 			return false;
1289 	}
1290 	i = -1;
1291 	if (m[0] == '/' && m[1] == '/')
1292 		m += 2;
1293 	else if (!scanNumber(&m, &i, 2))
1294 		return false;
1295 	if (i >= 1 && i < 90) {
1296 		r._friction = i / 100.0;
1297 	} else if ((i >= 91 && i <= 95) || i == 99) {
1298 		r._friction_string = runway_friction[i - 90];
1299 	}
1300 	if (!scanBoundary(&m))
1301 		return false;
1302 
1303 	_runways[id]._deposit = r._deposit;
1304 	_runways[id]._deposit_string = r._deposit_string;
1305 	_runways[id]._extent = r._extent;
1306 	_runways[id]._extent_string = r._extent_string;
1307 	_runways[id]._depth = r._depth;
1308 	_runways[id]._friction = r._friction;
1309 	_runways[id]._friction_string = r._friction_string;
1310 	_runways[id]._comment = r._comment;
1311 	_m = m;
1312 	_grpcount++;
1313 	return true;
1314 }
1315 
1316 
1317 // WS (ALL RWYS?|RWY ?\d\d[LCR]?)?
scanWindShear()1318 bool SGMetar::scanWindShear()
1319 {
1320 	char *m = _m;
1321 	if (strncmp(m, "WS", 2))
1322 		return false;
1323 	m += 2;
1324 	if (!scanBoundary(&m))
1325 		return false;
1326 
1327 	if (!strncmp(m, "ALL", 3)) {
1328 		m += 3;
1329 		if (!scanBoundary(&m))
1330 			return false;
1331 		if (strncmp(m, "RWY", 3))
1332 			return false;
1333 		m += 3;
1334 		if (*m == 'S')
1335 			m++;
1336 		if (!scanBoundary(&m))
1337 			return false;
1338 		_runways["ALL"]._wind_shear = true;
1339 		_m = m;
1340 		return true;
1341 	}
1342 
1343 	char id[4], *mm;
1344 	int i, cnt;
1345 	for (cnt = 0;; cnt++) {			// ??
1346 		if (strncmp(m, "RWY", 3))
1347 			break;
1348 		m += 3;
1349 		scanBoundary(&m);
1350 		mm = m;
1351 		if (!scanNumber(&m, &i, 2))
1352 			return false;
1353 		if (*m == 'L' || *m == 'C' || *m == 'R')
1354 			m++;
1355 		strncpy(id, mm, i = m - mm);
1356 		id[i] = '\0';
1357 		if (!scanBoundary(&m))
1358 			return false;
1359 		_runways[id]._wind_shear = true;
1360 	}
1361 	if (!cnt)
1362 		_runways["ALL"]._wind_shear = true;
1363 	_m = m;
1364 	return true;
1365 }
1366 
1367 
scanTrendForecast()1368 bool SGMetar::scanTrendForecast()
1369 {
1370 	char *m = _m;
1371 	if (strncmp(m, "NOSIG", 5))
1372 		return false;
1373 
1374 	m += 5;
1375 	if (!scanBoundary(&m))
1376 		return false;
1377 	_m = m;
1378 	return true;
1379 }
1380 
1381 
1382 // (BLU|WHT|GRN|YLO|AMB|RED)
1383 static const struct Token colors[] = {
1384 	{ "BLU", "Blue" },      // 2500 ft,  8.0 km
1385 	{ "WHT", "White" },     // 1500 ft,  5.0 km
1386 	{ "GRN", "Green" },     //  700 ft,  3.7 km
1387 	{ "YLO", "Yellow" },	//  300 ft,  1.6 km
1388 	{ "AMB", "Amber" },     //  200 ft,  0.8 km
1389 	{ "RED", "Red" },       // <200 ft, <0.8 km
1390 	{ 0, 0 }
1391 };
1392 
1393 
scanColorState()1394 bool SGMetar::scanColorState()
1395 {
1396 	char *m = _m;
1397 	const struct Token *a;
1398 	if (!(a = scanToken(&m, colors)))
1399 		return false;
1400 	if (!scanBoundary(&m))
1401 		return false;
1402 	//printf(Y"Code %s\n"N, a->text);
1403 	_m = m;
1404 	return true;
1405 }
1406 
1407 
scanRemark()1408 bool SGMetar::scanRemark()
1409 {
1410 	if (strncmp(_m, "RMK", 3))
1411 		return false;
1412 	_m += 3;
1413 	if (!scanBoundary(&_m))
1414 		return false;
1415 
1416 	while (*_m) {
1417 		if (!scanRunwayReport()) {
1418 			while (*_m && !isspace(*_m))
1419 				_m++;
1420 			scanBoundary(&_m);
1421 		}
1422 	}
1423 	return true;
1424 }
1425 
1426 
scanRemainder()1427 bool SGMetar::scanRemainder()
1428 {
1429 	char *m = _m;
1430 	if (!(strncmp(m, "NOSIG", 5))) {
1431 		m += 5;
1432 		if (scanBoundary(&m))
1433 			_m = m; //_comment.push_back("No significant tendency");
1434 	}
1435 
1436 	if (!scanBoundary(&m))
1437 		return false;
1438 	_m = m;
1439 	return true;
1440 }
1441 
1442 
scanBoundary(char ** s)1443 bool SGMetar::scanBoundary(char **s)
1444 {
1445 	if (**s && !isspace(**s))
1446 		return false;
1447 	while (isspace(**s))
1448 		(*s)++;
1449 	return true;
1450 }
1451 
1452 
scanNumber(char ** src,int * num,int min,int max)1453 int SGMetar::scanNumber(char **src, int *num, int min, int max)
1454 {
1455 	int i;
1456 	char *s = *src;
1457 	*num = 0;
1458 	for (i = 0; i < min; i++) {
1459 		if (!isdigit(*s))
1460 			return 0;
1461 		else
1462 			*num = *num * 10 + *s++ - '0';
1463 	}
1464 	for (; i < max && isdigit(*s); i++)
1465 		*num = *num * 10 + *s++ - '0';
1466 	*src = s;
1467 	return i;
1468 }
1469 
1470 
1471 // find longest match of str in list
scanToken(char ** str,const struct Token * list)1472 const struct Token *SGMetar::scanToken(char **str, const struct Token *list)
1473 {
1474 	const struct Token *longest = 0;
1475 	int maxlen = 0, len;
1476 	const char *s;
1477 	for (int i = 0; (s = list[i].id); i++) {
1478 		len = strlen(s);
1479 		if (!strncmp(s, *str, len) && len > maxlen) {
1480 			maxlen = len;
1481 			longest = &list[i];
1482 		}
1483 	}
1484 	*str += maxlen;
1485 	return longest;
1486 }
1487 
1488 
set(double alt,Coverage cov)1489 void SGMetarCloud::set(double alt, Coverage cov)
1490 {
1491 	_altitude = alt;
1492 	if (cov != -1)
1493 		_coverage = cov;
1494 }
1495 
getCoverage(const std::string & coverage)1496 SGMetarCloud::Coverage SGMetarCloud::getCoverage( const std::string & coverage )
1497 {
1498 	if( coverage == "clear" ) return COVERAGE_CLEAR;
1499 	if( coverage == "few" ) return COVERAGE_FEW;
1500 	if( coverage == "scattered" ) return COVERAGE_SCATTERED;
1501 	if( coverage == "broken" ) return COVERAGE_BROKEN;
1502 	if( coverage == "overcast" ) return COVERAGE_OVERCAST;
1503 	return COVERAGE_NIL;
1504 }
1505 
1506 const char * SGMetarCloud::COVERAGE_NIL_STRING = "nil";
1507 const char * SGMetarCloud::COVERAGE_CLEAR_STRING = "clear";
1508 const char * SGMetarCloud::COVERAGE_FEW_STRING = "few";
1509 const char * SGMetarCloud::COVERAGE_SCATTERED_STRING = "scattered";
1510 const char * SGMetarCloud::COVERAGE_BROKEN_STRING = "broken";
1511 const char * SGMetarCloud::COVERAGE_OVERCAST_STRING = "overcast";
1512 
set(double dist,int dir,int mod,int tend)1513 void SGMetarVisibility::set(double dist, int dir, int mod, int tend)
1514 {
1515 	_distance = dist;
1516 	if (dir != -1)
1517 		_direction = dir;
1518 	if (mod != -1)
1519 		_modifier = mod;
1520 	if (tend != 1)
1521 		_tendency = tend;
1522 }
1523 
1524 #undef NaN
1525