1 // apt_loader.cxx -- a front end loader of the apt.dat file.  This loader
2 //                   populates the runway and basic classes.
3 //
4 // Written by Curtis Olson, started August 2000.
5 //
6 // Copyright (C) 2000  Curtis L. Olson  - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23 
24 
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28 
29 #include "apt_loader.hxx"
30 
31 #include <simgear/compiler.h>
32 
33 #include <stdlib.h> // atof(), atoi()
34 #include <string.h> // memchr()
35 #include <ctype.h> // isspace()
36 #include <cerrno>
37 
38 #include <simgear/constants.h>
39 #include <simgear/debug/logstream.hxx>
40 #include <simgear/io/iostreams/sgstream.hxx>
41 #include <simgear/misc/strutils.hxx>
42 #include <simgear/structure/exception.hxx>
43 #include <simgear/misc/sg_path.hxx>
44 
45 #include <cstddef>              // std::size_t
46 #include <string>
47 #include <vector>
48 #include <utility>              // std::pair, std::move()
49 
50 #include "airport.hxx"
51 #include "runways.hxx"
52 #include "pavement.hxx"
53 #include <Navaids/NavDataCache.hxx>
54 #include <Navaids/positioned.hxx>
55 #include <ATC/CommStation.hxx>
56 
57 #include <iostream>
58 #include <sstream>              // std::istringstream
59 
60 using std::vector;
61 using std::string;
62 
63 namespace strutils = simgear::strutils;
64 
65 
fptypeFromRobinType(unsigned int aType)66 static FGPositioned::Type fptypeFromRobinType(unsigned int aType)
67 {
68   switch (aType) {
69   case 1: return FGPositioned::AIRPORT;
70   case 16: return FGPositioned::SEAPORT;
71   case 17: return FGPositioned::HELIPORT;
72   default:
73     SG_LOG(SG_GENERAL, SG_ALERT, "unsupported type:" << aType);
74     throw sg_range_exception("Unsupported airport type", "fptypeFromRobinType");
75   }
76 }
77 
78 namespace flightgear
79 {
APTLoader()80 APTLoader::APTLoader()
81   :  last_apt_id(""),
82      last_apt_elev(0.0),
83      currentAirportPosID(0),
84      cache(NavDataCache::instance())
85 { }
86 
~APTLoader()87 APTLoader::~APTLoader() { }
88 
readAptDatFile(const SGPath & aptdb_file,std::size_t bytesReadSoFar,std::size_t totalSizeOfAllAptDatFiles)89 void APTLoader::readAptDatFile(const SGPath &aptdb_file,
90                                std::size_t bytesReadSoFar,
91                                std::size_t totalSizeOfAllAptDatFiles)
92 {
93   string apt_dat = aptdb_file.utf8Str(); // full path to the file being parsed
94   sg_gzifstream in(aptdb_file, std::ios_base::in | std::ios_base::binary, true);
95 
96   if ( !in.is_open() ) {
97     const std::string errMsg = simgear::strutils::error_string(errno);
98     SG_LOG( SG_GENERAL, SG_ALERT,
99             "Cannot open file '" << apt_dat << "': " << errMsg );
100     throw sg_io_exception("Cannot open file (" + errMsg + ")",
101                           sg_location(aptdb_file));
102   }
103 
104   string line;
105 
106   unsigned int rowCode = 0;     // terminology used in the apt.dat format spec
107   unsigned int line_num = 0;
108   // "airport identifier": terminology used in the apt.dat format spec. It is
109   // often an ICAO code, but not always.
110   string currentAirportId;
111   // Boolean used to make sure we don't try to load the same airport several
112   // times. Defaults to true only to ensure we don't add garbage to
113   // 'airportInfoMap' under the key "" (empty airport identifier) in case the
114   // apt.dat file doesn't have a start-of-airport row code (1, 16 or 17) after
115   // its header---which would be invalid, anyway.
116   bool skipAirport = true;
117 
118   // Read the apt.dat header (two lines)
119   while ( line_num < 2 && std::getline(in, line) ) {
120     // 'line' may end with an \r character (tested on Linux, only \n was
121     // stripped: std::getline() only discards the _native_ line terminator)
122     line_num++;
123 
124     if ( line_num == 1 ) {
125       std::string stripped_line = simgear::strutils::strip(line);
126       // First line indicates IBM ("I") or Macintosh ("A") line endings.
127       if ( stripped_line != "I" && stripped_line != "A" ) {
128         std::string pb = "invalid first line (neither 'I' nor 'A')";
129         SG_LOG( SG_GENERAL, SG_ALERT, aptdb_file << ": " << pb);
130         throw sg_format_exception("cannot parse '" + apt_dat + "': " + pb,
131                                   stripped_line);
132       }
133     } else {     // second line of the file
134       vector<string> fields(strutils::split(line, 0, 1));
135 
136       if (fields.empty()) {
137         string errMsg = "unable to parse format version: empty line";
138         SG_LOG(SG_GENERAL, SG_ALERT, apt_dat << ": " << errMsg);
139         throw sg_format_exception("cannot parse '" + apt_dat + "': " + errMsg,
140                                   string());
141       } else {
142         unsigned int aptDatFormatVersion =
143           strutils::readNonNegativeInt<unsigned int>(fields[0]);
144         SG_LOG(SG_GENERAL, SG_INFO,
145                "apt.dat format version (" << apt_dat << "): " <<
146                aptDatFormatVersion);
147       }
148     }
149   } // end of the apt.dat header
150 
151   throwExceptionIfStreamError(in, aptdb_file);
152 
153   while ( std::getline(in, line) ) {
154     // 'line' may end with an \r character, see above
155     line_num++;
156 
157     if ( isBlankOrCommentLine(line) )
158       continue;
159 
160     if ((line_num % 100) == 0) {
161       // every 100 lines
162       unsigned int percent = ((bytesReadSoFar + in.approxOffset()) * 100)
163                              / totalSizeOfAllAptDatFiles;
164       cache->setRebuildPhaseProgress(
165         NavDataCache::REBUILD_READING_APT_DAT_FILES, percent);
166     }
167 
168     // Extract the first field into 'rowCode'
169     rowCode = atoi(line.c_str());
170 
171     if ( rowCode == 1  /* Airport */ ||
172          rowCode == 16 /* Seaplane base */ ||
173          rowCode == 17 /* Heliport */ ) {
174       vector<string> tokens(simgear::strutils::split(line));
175       if (tokens.size() < 6) {
176         SG_LOG( SG_GENERAL, SG_WARN,
177                 apt_dat << ":"  << line_num << ": invalid airport header "
178                 "(at least 6 fields are required)" );
179         skipAirport = true; // discard everything until the next airport header
180         continue;
181       }
182 
183       currentAirportId = tokens[4]; // often an ICAO, but not always
184       // Check if the airport is already in 'airportInfoMap'; get the
185       // existing entry, if any, otherwise insert a new one.
186       std::pair<AirportInfoMapType::iterator, bool>
187         insertRetval = airportInfoMap.insert(
188           AirportInfoMapType::value_type(currentAirportId, RawAirportInfo()));
189       skipAirport = !insertRetval.second;
190 
191       if ( skipAirport ) {
192         SG_LOG( SG_GENERAL, SG_INFO,
193                 apt_dat << ":"  << line_num << ": skipping airport " <<
194                 currentAirportId << " (already defined earlier)" );
195       } else {
196         // We haven't seen this airport yet in any apt.dat file
197         RawAirportInfo& airportInfo = insertRetval.first->second;
198         airportInfo.file = aptdb_file;
199         airportInfo.rowCode = rowCode;
200         airportInfo.firstLineNum = line_num;
201         airportInfo.firstLineTokens = std::move(tokens);
202       }
203     } else if ( rowCode == 99 ) {
204       SG_LOG( SG_GENERAL, SG_DEBUG,
205               apt_dat << ":"  << line_num << ": code 99 found "
206               "(normally at end of file)" );
207     } else if ( !skipAirport ) {
208       // Line belonging to an already started, and not skipped airport entry;
209       // just append it.
210       airportInfoMap[currentAirportId].otherLines.emplace_back(
211         line_num, rowCode, line);
212     }
213   } // of file reading loop
214 
215   throwExceptionIfStreamError(in, aptdb_file);
216 }
217 
isCommLine(const int code)218 static bool isCommLine(const int code)
219 {
220     return ((code >= 50) && (code <= 56)) || ((code >= 1050) && (code <= 1056));
221 }
222 
loadAirports()223 void APTLoader::loadAirports()
224 {
225   AirportInfoMapType::size_type nbLoadedAirports = 0;
226   AirportInfoMapType::size_type nbAirports = airportInfoMap.size();
227 
228   // Loop over all airports found in all apt.dat files
229   for (AirportInfoMapType::const_iterator it = airportInfoMap.begin();
230        it != airportInfoMap.end(); it++) {
231     // Full path to the apt.dat file this airport info comes from
232     const string aptDat = it->second.file.utf8Str();
233     last_apt_id = it->first;    // this is just the current airport identifier
234     // The first line for this airport was already split over whitespace, but
235     // remains to be parsed for the most part.
236     parseAirportLine(it->second.rowCode, it->second.firstLineTokens);
237     const LinesList& lines = it->second.otherLines;
238 
239     // Loop over the second and subsequent lines
240     for (LinesList::const_iterator linesIt = lines.begin();
241          linesIt != lines.end(); linesIt++) {
242       // Beware that linesIt->str may end with an '\r' character, see above!
243       unsigned int rowCode = linesIt->rowCode;
244 
245       if ( rowCode == 10 ) { // Runway v810
246         parseRunwayLine810(aptDat, linesIt->number,
247                            simgear::strutils::split(linesIt->str));
248       } else if ( rowCode == 100 ) { // Runway v850
249         parseRunwayLine850(aptDat, linesIt->number,
250                            simgear::strutils::split(linesIt->str));
251       } else if ( rowCode == 101 ) { // Water Runway v850
252         parseWaterRunwayLine850(aptDat, linesIt->number,
253                                 simgear::strutils::split(linesIt->str));
254       } else if ( rowCode == 102 ) { // Helipad v850
255         parseHelipadLine850(aptDat, linesIt->number,
256                             simgear::strutils::split(linesIt->str));
257       } else if ( rowCode == 18 ) {
258         // beacon entry (ignore)
259       } else if ( rowCode == 14 ) {  // Viewpoint/control tower
260         parseViewpointLine(aptDat, linesIt->number,
261                            simgear::strutils::split(linesIt->str));
262       } else if ( rowCode == 19 ) {
263         // windsock entry (ignore)
264       } else if ( rowCode == 20 ) {
265         // Taxiway sign (ignore)
266       } else if ( rowCode == 21 ) {
267         // lighting objects (ignore)
268       } else if ( rowCode == 15 ) {
269         // custom startup locations (ignore)
270       } else if ( rowCode == 0 ) {
271         // ??
272       } else if ( isCommLine(rowCode)) {
273         parseCommLine(aptDat, linesIt->number, rowCode,
274                       simgear::strutils::split(linesIt->str));
275       } else if ( rowCode == 110 ) {
276         pavement = true;
277         parsePavementLine850(simgear::strutils::split(linesIt->str, 0, 4));
278       } else if ( rowCode >= 111 && rowCode <= 114 ) {
279         if ( pavement )
280           parsePavementNodeLine850(aptDat, linesIt->number, rowCode,
281                                    simgear::strutils::split(linesIt->str));
282       } else if ( rowCode >= 115 && rowCode <= 116 ) {
283         // other pavement nodes (ignore)
284       } else if ( rowCode == 120 ) {
285         pavement = false;
286       } else if ( rowCode == 130 ) {
287         pavement = false;
288       } else if ( rowCode >= 1000 ) {
289         // airport traffic flow (ignore)
290       } else {
291         std::ostringstream oss;
292         string cleanedLine = cleanLine(linesIt->str);
293         oss << aptDat << ":" << linesIt->number << ": unknown row code " <<
294           rowCode;
295         SG_LOG( SG_GENERAL, SG_ALERT, oss.str() << " (" << cleanedLine << ")" );
296         throw sg_format_exception(oss.str(), cleanedLine);
297       }
298     } // of loop over the second and subsequent apt.dat lines for the airport
299 
300     finishAirport(aptDat);
301     nbLoadedAirports++;
302 
303     if ((nbLoadedAirports % 300) == 0) {
304       // Every 300 airports
305       unsigned int percent = nbLoadedAirports * 100 / nbAirports;
306       cache->setRebuildPhaseProgress(NavDataCache::REBUILD_LOADING_AIRPORTS,
307                                      percent);
308     }
309   } // of loop over 'airportInfoMap'
310 
311   SG_LOG( SG_GENERAL, SG_INFO,
312           "Loaded data for " << nbLoadedAirports << " airports" );
313 }
314 
315 // Tell whether an apt.dat line is blank or a comment line
isBlankOrCommentLine(const std::string & line)316 bool APTLoader::isBlankOrCommentLine(const std::string& line)
317 {
318   size_t pos = line.find_first_not_of(" \t");
319   return ( pos == std::string::npos ||
320            line[pos] == '\r' ||
321            line.find("##", pos) == pos );
322 }
323 
cleanLine(const std::string & line)324 std::string APTLoader::cleanLine(const std::string& line)
325 {
326   std::string res = line;
327 
328   // Lines obtained from readAptDatFile() may end with \r, which can be quite
329   // confusing when printed to the terminal.
330   for (std::string::reverse_iterator it = res.rbegin();
331        it != res.rend() && *it == '\r'; /* empty */)
332   { // The beauty of C++ iterators...
333     it = std::string::reverse_iterator(res.erase( (it+1).base() ));
334   }
335 
336   return res;
337 }
338 
throwExceptionIfStreamError(const sg_gzifstream & input_stream,const SGPath & path)339 void APTLoader::throwExceptionIfStreamError(const sg_gzifstream& input_stream,
340                                             const SGPath& path)
341 {
342   if (input_stream.bad()) {
343     const std::string errMsg = simgear::strutils::error_string(errno);
344 
345     SG_LOG( SG_NAVAID, SG_ALERT,
346             "Error while reading '" << path.utf8Str() << "': " << errMsg );
347     throw sg_io_exception("Error reading file (" + errMsg + ")",
348                           sg_location(path));
349   }
350 }
351 
finishAirport(const string & aptDat)352 void APTLoader::finishAirport(const string& aptDat)
353 {
354   if (currentAirportPosID == 0) {
355     return;
356   }
357 
358   if (!rwy_count) {
359     currentAirportPosID = 0;
360     SG_LOG( SG_GENERAL, SG_ALERT, "Error in '" << aptDat <<
361             "': no runways for " << last_apt_id << ", skipping." );
362     return;
363   }
364 
365   double lat = rwy_lat_accum / (double)rwy_count;
366   double lon = rwy_lon_accum / (double)rwy_count;
367 
368   SGGeod pos(SGGeod::fromDegFt(lon, lat, last_apt_elev));
369   cache->updatePosition(currentAirportPosID, pos);
370 
371   currentAirportPosID = 0;
372 }
373 
374 // 'rowCode' is passed to avoid decoding it twice, since that work was already
375 // done in order to detect the start of the new airport.
parseAirportLine(unsigned int rowCode,const vector<string> & token)376 void APTLoader::parseAirportLine(unsigned int rowCode,
377                                  const vector<string>& token)
378 {
379   // The algorithm in APTLoader::readAptDatFile() ensures this is at least 5.
380   vector<string>::size_type lastIndex = token.size() - 1;
381   const string& id(token[4]);
382   double elev = atof( token[1].c_str() );
383   last_apt_elev = elev;
384 
385   string name;
386   // build the name
387   for ( vector<string>::size_type i = 5; i < lastIndex; ++i ) {
388     name += token[i] + " ";
389   }
390   name += token[lastIndex];
391 
392   // clear runway list for start of next airport
393   rwy_lon_accum = 0.0;
394   rwy_lat_accum = 0.0;
395   rwy_count = 0;
396 
397   currentAirportPosID = cache->insertAirport(fptypeFromRobinType(rowCode),
398                                              id, name);
399 }
400 
parseRunwayLine810(const string & aptDat,unsigned int lineNum,const vector<string> & token)401 void APTLoader::parseRunwayLine810(const string& aptDat, unsigned int lineNum,
402                                    const vector<string>& token)
403 {
404   if (token.size() < 11) {
405     SG_LOG( SG_GENERAL, SG_WARN,
406             aptDat << ":" << lineNum << ": invalid v810 runway line " <<
407             "(row code 10): at least 11 fields are required" );
408     return;
409   }
410 
411   double lat = atof( token[1].c_str() );
412   double lon = atof( token[2].c_str() );
413   rwy_lat_accum += lat;
414   rwy_lon_accum += lon;
415   rwy_count++;
416 
417   const string& rwy_no(token[3]);
418 
419   double heading = atof( token[4].c_str() );
420   double length = atoi( token[5].c_str() );
421   double width = atoi( token[8].c_str() );
422   length *= SG_FEET_TO_METER;
423   width *= SG_FEET_TO_METER;
424 
425   // adjust lat / lon to the start of the runway/taxiway, not the middle
426   SGGeod pos_1 = SGGeodesy::direct( SGGeod::fromDegFt(lon, lat, last_apt_elev),
427                                     heading, -length/2 );
428 
429   last_rwy_heading = heading;
430 
431   int surface_code = atoi( token[10].c_str() );
432 
433   if (rwy_no[0] == 'x') {  // Taxiway
434     cache->insertRunway(
435       FGPositioned::TAXIWAY, rwy_no, pos_1, currentAirportPosID,
436       heading, length, width, 0.0, 0.0, surface_code);
437   } else if (rwy_no[0] == 'H') {  // Helipad
438     SGGeod pos(SGGeod::fromDegFt(lon, lat, last_apt_elev));
439     cache->insertRunway(FGPositioned::HELIPAD, rwy_no, pos, currentAirportPosID,
440                         heading, length, width, 0.0, 0.0, surface_code);
441   } else {
442     // (pair of) runways
443     string rwy_displ_threshold = token[6];
444     vector<string> displ
445       = simgear::strutils::split( rwy_displ_threshold, "." );
446     double displ_thresh1 = atof( displ[0].c_str() );
447     double displ_thresh2 = atof( displ[1].c_str() );
448     displ_thresh1 *= SG_FEET_TO_METER;
449     displ_thresh2 *= SG_FEET_TO_METER;
450 
451     string rwy_stopway = token[7];
452     vector<string> stop
453       = simgear::strutils::split( rwy_stopway, "." );
454     double stopway1 = atof( stop[0].c_str() );
455     double stopway2 = atof( stop[1].c_str() );
456     stopway1 *= SG_FEET_TO_METER;
457     stopway2 *= SG_FEET_TO_METER;
458 
459     SGGeod pos_2 = SGGeodesy::direct( pos_1, heading, length );
460 
461     PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no, pos_1,
462                                            currentAirportPosID, heading, length,
463                                            width, displ_thresh1, stopway1,
464                                            surface_code);
465 
466     PositionedID reciprocal = cache->insertRunway(
467       FGPositioned::RUNWAY,
468       FGRunway::reverseIdent(rwy_no), pos_2,
469       currentAirportPosID,
470       SGMiscd::normalizePeriodic(0, 360, heading + 180.0),
471       length, width, displ_thresh2, stopway2,
472       surface_code);
473 
474     cache->setRunwayReciprocal(rwy, reciprocal);
475   }
476 }
477 
parseRunwayLine850(const string & aptDat,unsigned int lineNum,const vector<string> & token)478 void APTLoader::parseRunwayLine850(const string& aptDat, unsigned int lineNum,
479                                    const vector<string>& token)
480 {
481   if (token.size() < 26) {
482     SG_LOG( SG_GENERAL, SG_WARN,
483             aptDat << ":" << lineNum << ": invalid v850 runway line " <<
484             "(row code 100): at least 26 fields are required" );
485     return;
486   }
487 
488   double width = atof( token[1].c_str() );
489   int surface_code = atoi( token[2].c_str() );
490 
491   double lat_1 = atof( token[9].c_str() );
492   double lon_1 = atof( token[10].c_str() );
493   SGGeod pos_1(SGGeod::fromDegFt(lon_1, lat_1, 0.0));
494   rwy_lat_accum += lat_1;
495   rwy_lon_accum += lon_1;
496   rwy_count++;
497 
498   double lat_2 = atof( token[18].c_str() );
499   double lon_2 = atof( token[19].c_str() );
500   SGGeod pos_2(SGGeod::fromDegFt(lon_2, lat_2, 0.0));
501   rwy_lat_accum += lat_2;
502   rwy_lon_accum += lon_2;
503   rwy_count++;
504 
505   double length, heading_1, heading_2;
506   SGGeodesy::inverse( pos_1, pos_2, heading_1, heading_2, length );
507 
508   last_rwy_heading = heading_1;
509 
510   const string& rwy_no_1(token[8]);
511   const string& rwy_no_2(token[17]);
512   if ( rwy_no_1.empty() || rwy_no_2.empty() ) // these tests are weird...
513     return;
514 
515   double displ_thresh1 = atof( token[11].c_str() );
516   double displ_thresh2 = atof( token[20].c_str() );
517 
518   double stopway1 = atof( token[12].c_str() );
519   double stopway2 = atof( token[21].c_str() );
520 
521   PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no_1, pos_1,
522                                          currentAirportPosID, heading_1, length,
523                                          width, displ_thresh1, stopway1,
524                                          surface_code);
525 
526   PositionedID reciprocal = cache->insertRunway(
527     FGPositioned::RUNWAY,
528     rwy_no_2, pos_2,
529     currentAirportPosID, heading_2, length,
530     width, displ_thresh2, stopway2,
531     surface_code);
532 
533   cache->setRunwayReciprocal(rwy, reciprocal);
534 }
535 
parseWaterRunwayLine850(const string & aptDat,unsigned int lineNum,const vector<string> & token)536 void APTLoader::parseWaterRunwayLine850(const string& aptDat,
537                                         unsigned int lineNum,
538                                         const vector<string>& token)
539 {
540   if (token.size() < 9) {
541     SG_LOG( SG_GENERAL, SG_WARN,
542             aptDat << ":" << lineNum << ": invalid v850 water runway line " <<
543             "(row code 101): at least 9 fields are required" );
544     return;
545   }
546 
547   double width = atof( token[1].c_str() );
548 
549   double lat_1 = atof( token[4].c_str() );
550   double lon_1 = atof( token[5].c_str() );
551   SGGeod pos_1(SGGeod::fromDegFt(lon_1, lat_1, 0.0));
552   rwy_lat_accum += lat_1;
553   rwy_lon_accum += lon_1;
554   rwy_count++;
555 
556   double lat_2 = atof( token[7].c_str() );
557   double lon_2 = atof( token[8].c_str() );
558   SGGeod pos_2(SGGeod::fromDegFt(lon_2, lat_2, 0.0));
559   rwy_lat_accum += lat_2;
560   rwy_lon_accum += lon_2;
561   rwy_count++;
562 
563   double length, heading_1, heading_2;
564   SGGeodesy::inverse( pos_1, pos_2, heading_1, heading_2, length );
565 
566   last_rwy_heading = heading_1;
567 
568   const string& rwy_no_1(token[3]);
569   const string& rwy_no_2(token[6]);
570 
571   PositionedID rwy = cache->insertRunway(FGPositioned::RUNWAY, rwy_no_1, pos_1,
572                                          currentAirportPosID, heading_1, length,
573                                          width, 0.0, 0.0, 13);
574 
575   PositionedID reciprocal = cache->insertRunway(
576     FGPositioned::RUNWAY,
577     rwy_no_2, pos_2,
578     currentAirportPosID, heading_2, length,
579     width, 0.0, 0.0, 13);
580 
581   cache->setRunwayReciprocal(rwy, reciprocal);
582 }
583 
parseHelipadLine850(const string & aptDat,unsigned int lineNum,const vector<string> & token)584 void APTLoader::parseHelipadLine850(const string& aptDat, unsigned int lineNum,
585                                     const vector<string>& token)
586 {
587   if (token.size() < 12) {
588     SG_LOG( SG_GENERAL, SG_WARN,
589             aptDat << ":" << lineNum << ": invalid v850 helipad line " <<
590             "(row code 102): at least 12 fields are required" );
591     return;
592   }
593 
594   double length = atof( token[5].c_str() );
595   double width = atof( token[6].c_str() );
596 
597   double lat = atof( token[2].c_str() );
598   double lon = atof( token[3].c_str() );
599   SGGeod pos(SGGeod::fromDegFt(lon, lat, 0.0));
600   rwy_lat_accum += lat;
601   rwy_lon_accum += lon;
602   rwy_count++;
603 
604   double heading = atof( token[4].c_str() );
605 
606   last_rwy_heading = heading;
607 
608   const string& rwy_no(token[1]);
609   int surface_code = atoi( token[7].c_str() );
610 
611   cache->insertRunway(FGPositioned::HELIPAD, rwy_no, pos,
612                       currentAirportPosID, heading, length,
613                       width, 0.0, 0.0, surface_code);
614 }
615 
parseViewpointLine(const string & aptDat,unsigned int lineNum,const vector<string> & token)616 void APTLoader::parseViewpointLine(const string& aptDat, unsigned int lineNum,
617                                    const vector<string>& token)
618 {
619   if (token.size() < 5) {
620     SG_LOG( SG_GENERAL, SG_WARN,
621             aptDat << ":" << lineNum << ": invalid viewpoint line "
622             "(row code 14): at least 5 fields are required" );
623   } else {
624     double lat = atof(token[1].c_str());
625     double lon = atof(token[2].c_str());
626     double elev = atof(token[3].c_str());
627     tower = SGGeod::fromDegFt(lon, lat, elev + last_apt_elev);
628     cache->insertTower(currentAirportPosID, tower);
629   }
630 }
631 
parsePavementLine850(const vector<string> & token)632 void APTLoader::parsePavementLine850(const vector<string>& token)
633 {
634   if ( token.size() >= 5 ) {
635     pavement_ident = token[4];
636     if ( !pavement_ident.empty() &&
637          pavement_ident[pavement_ident.size()-1] == '\r' )
638       pavement_ident.erase( pavement_ident.size()-1 );
639   } else {
640     pavement_ident = "xx";
641   }
642 }
643 
parsePavementNodeLine850(const string & aptDat,unsigned int lineNum,int rowCode,const vector<string> & token)644 void APTLoader::parsePavementNodeLine850(const string& aptDat,
645                                          unsigned int lineNum, int rowCode,
646                                          const vector<string>& token)
647 {
648   static const unsigned int minNbTokens[] = {3, 5, 3, 5};
649   assert(111 <= rowCode && rowCode <= 114);
650 
651   if (token.size() < minNbTokens[rowCode-111]) {
652     SG_LOG( SG_GENERAL, SG_WARN,
653             aptDat << ":" << lineNum << ": invalid v850 node line " <<
654             "(row code " << rowCode << "): at least " <<
655             minNbTokens[rowCode-111] << " fields are required" );
656     return;
657   }
658 
659   double lat = atof( token[1].c_str() );
660   double lon = atof( token[2].c_str() );
661   SGGeod pos(SGGeod::fromDegFt(lon, lat, 0.0));
662 
663   FGPavement* pvt = 0;
664   if ( !pavement_ident.empty() ) {
665     pvt = new FGPavement( 0, pavement_ident, pos );
666     pavements.push_back( pvt );
667     pavement_ident = "";
668   } else {
669     pvt = pavements.back();
670   }
671   if ( rowCode == 112 || rowCode == 114 ) {
672     double lat_b = atof( token[3].c_str() );
673     double lon_b = atof( token[4].c_str() );
674     SGGeod pos_b(SGGeod::fromDegFt(lon_b, lat_b, 0.0));
675     pvt->addBezierNode(pos, pos_b, rowCode == 114);
676   } else {
677     pvt->addNode(pos, rowCode == 113);
678   }
679 }
680 
parseCommLine(const string & aptDat,unsigned int lineNum,unsigned int rowCode,const vector<string> & token)681 void APTLoader::parseCommLine(const string& aptDat,
682                               unsigned int lineNum, unsigned int rowCode,
683                               const vector<string>& token)
684 {
685   if (token.size() < 3) {
686     SG_LOG( SG_GENERAL, SG_WARN,
687             aptDat << ":" << lineNum << ": invalid Comm Frequency line " <<
688             "(row code " << rowCode << "): at least 3 fields are required" );
689     return;
690   } else if (rwy_count <= 0) {
691     SG_LOG( SG_GENERAL, SG_ALERT,
692             aptDat << ":" << lineNum << ": no runways, skipping Comm " <<
693             "Frequency line for " << last_apt_id );
694     // There used to be no 'return' here. Not sure how useful this is, but
695     // clearly this code block doesn't make sense without it, so here it is.
696     return;
697   }
698 
699   SGGeod pos = SGGeod::fromDegFt(rwy_lon_accum / (double)rwy_count,
700                                  rwy_lat_accum / (double)rwy_count,
701                                  last_apt_elev);
702 
703   const bool isAPT1000Code = rowCode > 1000;
704   if (isAPT1000Code) {
705       rowCode -= 1000;
706   }
707 
708   // short int representing tens of kHz, or just kHz directly
709   int freqKhz = std::stoi(token[1]);
710   if (isAPT1000Code) {
711       const int channel = freqKhz % 25;
712       if (channel != 0 && channel != 5 && channel != 10 && channel != 15) {
713           SG_LOG(SG_GENERAL, SG_ALERT,
714                  aptDat << ":" << lineNum << ": skipping invalid 8.333 kHz Frequency " << freqKhz << " for " << last_apt_id);
715           return;
716       }
717   } else {
718       freqKhz *= 10;
719       const int remainder = freqKhz % 100;
720       // this is to ensure frequencies such as 126.12 become 126.125
721       if (remainder == 20 || remainder == 70) {
722           freqKhz += 5;
723       }
724 
725       if (freqKhz % 25) {
726           SG_LOG(SG_GENERAL, SG_ALERT,
727                  aptDat << ":" << lineNum << ": skipping invalid 25 kHz Frequency " << freqKhz << " for " << last_apt_id);
728           return;
729       }
730   }
731 
732   if (freqKhz < 118000 || freqKhz >= 137000) {
733       SG_LOG(SG_GENERAL, SG_ALERT,
734              aptDat << ":" << lineNum << ": skipping out of range Frequency " << freqKhz << " for " << last_apt_id);
735       return;
736   }
737 
738   int rangeNm = 50;
739   FGPositioned::Type ty;
740 
741   switch (rowCode) {
742   case 50:
743     ty = FGPositioned::FREQ_AWOS;
744     for (size_t i = 2; i < token.size(); ++i)
745     {
746       if (token[i] == "ATIS")
747       {
748         ty = FGPositioned::FREQ_ATIS;
749         break;
750       }
751     }
752     break;
753 
754   case 51:    ty = FGPositioned::FREQ_UNICOM; break;
755   case 52:    ty = FGPositioned::FREQ_CLEARANCE; break;
756   case 53:    ty = FGPositioned::FREQ_GROUND; break;
757   case 54:    ty = FGPositioned::FREQ_TOWER; break;
758   case 55:
759   case 56:    ty = FGPositioned::FREQ_APP_DEP; break;
760   default:
761     throw sg_range_exception("unsupported apt.dat comm station type");
762   }
763 
764   // Name can contain whitespace. All tokens after the second token are
765   // part of the name.
766   string name = token[2];
767   for (size_t i = 3; i < token.size(); ++i)
768     name += ' ' + token[i];
769 
770   cache->insertCommStation(ty, name, pos, freqKhz, rangeNm,
771                            currentAirportPosID);
772 }
773 
774 // The 'metar.dat' file lists the airports that have METAR available.
metarDataLoad(const SGPath & metar_file)775 bool metarDataLoad(const SGPath& metar_file)
776 {
777   sg_gzifstream metar_in( metar_file );
778   if ( !metar_in.is_open() ) {
779     SG_LOG( SG_GENERAL, SG_ALERT, "Cannot open file: " << metar_file );
780     return false;
781   }
782 
783   NavDataCache* cache = NavDataCache::instance();
784 
785   string ident;
786   while ( metar_in ) {
787     metar_in >> ident;
788     if ( ident == "#" || ident == "//" ) {
789       metar_in >> skipeol;
790     } else {
791       cache->setAirportMetar(ident, true);
792     }
793   }
794 
795   return true;
796 }
797 
798 } // of namespace flightgear
799