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