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