FlightGear next
navdb.cxx
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
3 * SPDX_FileComment: top level navaids management routines
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "config.h"
8
9#include <string>
10#include <vector>
11#include <istream>
12#include <cmath>
13#include <cstddef> // std::size_t
14#include <cerrno>
15#include <limits>
16#include <stdexcept>
17
18#include "navdb.hxx"
19
20#include <simgear/compiler.h>
21#include <simgear/constants.h>
22#include <simgear/debug/logstream.hxx>
23#include <simgear/math/SGGeod.hxx>
24#include <simgear/math/SGMathFwd.hxx>
25#include <simgear/math/SGVec3.hxx>
26#include <simgear/misc/strutils.hxx>
27#include <simgear/misc/sg_path.hxx>
28#include <simgear/structure/exception.hxx>
29#include <simgear/io/iostreams/sgstream.hxx>
30#include <simgear/props/props_io.hxx>
31#include <simgear/sg_inlines.h>
32
33#include "navlist.hxx"
34#include <Main/globals.hxx>
36#include <Airports/airport.hxx>
37#include <Airports/runways.hxx>
39#include <Main/fg_props.hxx>
41#include <Navaids/navrecord.hxx>
42
43using std::string;
44using std::vector;
45
46namespace strutils = simgear::strutils;
47
48// Duplicate navaids with the same ident will be removed if the disance
49// between them is less than this.
50static const double DUPLICATE_DETECTION_RADIUS_NM = 10;
51
52
53static void throwExceptionIfStreamError(const std::istream& inputStream,
54 const SGPath& path)
55{
56 if (inputStream.bad()) {
57 const std::string errMsg = simgear::strutils::error_string(errno);
58
59 SG_LOG(SG_NAVAID, SG_ALERT,
60 "Error while reading '" << path.utf8Str() << "': " << errMsg);
61 throw sg_io_exception("Error reading file (" + errMsg + ")",
62 sg_location(path));
63 }
64}
65
68{
69 switch (aTy) {
70 case 2: return FGPositioned::NDB;
71 case 3: return FGPositioned::VOR;
72 case 4: return FGPositioned::ILS;
73 case 5: return FGPositioned::LOC;
74 case 6: return FGPositioned::GS;
75 case 7: return FGPositioned::OM;
76 case 8: return FGPositioned::MM;
77 case 9: return FGPositioned::IM;
78 case 12:
79 case 13: return FGPositioned::DME;
80 default: return FGPositioned::INVALID;
81 }
82}
83
84static double defaultNavRange(const string& ident, FGPositioned::Type type)
85{
86 // Ranges are included with the latest data format, no need to
87 // assign our own defaults, unless the range is not set for some
88 // reason.
89 SG_LOG(SG_NAVAID, SG_DEBUG, "navaid " << ident << " has no range set, using defaults");
90 switch (type) {
94
99
102
106
107 default:
109 }
110}
111
112
113namespace flightgear
114{
115
116static bool isNearby(const SGGeod& pos1, const SGGeod& pos2) {
117 double distNm = dist(SGVec3d::fromGeod(pos1),
118 SGVec3d::fromGeod(pos2)) * SG_METER_TO_NM;
119 return distNm <= DUPLICATE_DETECTION_RADIUS_NM;
120}
121
123 const std::string& name,
124 const SGGeod& pos, int freq)
125{
126 if ((type >= FGPositioned::ILS) && (type <= FGPositioned::IM)) {
129 PositionedID navaidId = cache->findNavaidForRunway(AR.second, type);
130 return navaidId != 0;
131 } else if (type == FGPositioned::DME) {
132 FGNavRecord* navRecord = dynamic_cast<FGNavRecord*>(ref.ptr());
133 return navRecord->get_freq() == freq;
134 } else {
135 return true;
136 }
137}
138
139// Parse a line from a file such as nav.dat or carrier_nav.dat. Load the
140// corresponding data into the NavDataCache.
141PositionedID NavLoader::processNavLine(
142 const string& line, const string& utf8Path, unsigned int lineNum,
143 FGPositioned::Type type, unsigned int version)
144{
145 NavDataCache* cache = NavDataCache::instance();
146 int rowCode, elev_ft, freq, range;
147 // 'multiuse': different meanings depending on the record's row code
148 double lat, lon, multiuse;
149 // Short identifier and longer name for a navaid (e.g., 'OLN' and
150 // 'LFPO 02 OM')
151 string ident, name, arpt_code;
152
153 if (simgear::strutils::starts_with(line, "#")) {
154 // carrier_nav.dat has a comment line using this syntax...
155 return 0;
156 }
157
158 int num_splits;
159 if (version < 1100) {
160 // At most 9 fields (the ninth field may contain spaces)
161 num_splits = 8;
162 } else {
163 // At most 11 fields (the eleventh field may contain spaces)
164 num_splits = 10;
165 }
166
167 vector<string> fields(simgear::strutils::split(line, 0, num_splits));
168 vector<string>::size_type nbFields = fields.size();
169 static const string endOfData = "99"; // special code in the nav.dat spec
170
171 if (nbFields == 0) { // blank line
172 return 0;
173 } else if (nbFields == 1) {
174 if (fields[0] != endOfData) {
175 SG_LOG( SG_NAVAID, SG_WARN,
176 utf8Path << ":" << lineNum << ": malformed line: only one "
177 "field, but it is not '99'" );
178 }
179
180 return 0;
181 } else if (nbFields < 9) {
182 SG_LOG( SG_NAVAID, SG_WARN,
183 utf8Path << ":" << lineNum << ": invalid line "
184 "(at least 9 fields are required)" );
185 return 0;
186 }
187
188 // When their string argument can't be properly converted, std::stoi(),
189 // std::stof() and std::stod() all raise an exception which is always a
190 // subclass of std::logic_error.
191 try {
192 rowCode = std::stoi(fields[0]);
193 lat = std::stod(fields[1]);
194 lon = std::stod(fields[2]);
195 elev_ft = std::stoi(fields[3]);
196 freq = std::stoi(fields[4]);
197 range = std::stoi(fields[5]);
198 multiuse = std::stod(fields[6]);
199 ident = fields[7];
200 if (version >= 1100) {
201 // Convert names to the format present in 810 version.
202
203 // 1. fields[9] is ICAO region code, we skip over it.
204 // 2. For NDB, VOR and DMEs not associated with ILS,
205 // fields[8] is always ENRT, we skip over this too, to match
206 // the naming with version 810.
207 if ((rowCode == 2 || rowCode == 3 || rowCode == 12 || rowCode == 13)
208 && fields[8] == "ENRT") {
209 name = fields[10];
210 } else {
211 name = fields[8] + " " + fields[10];
212 }
213 } else {
214 name = fields[8];
215 }
216 // Canonicalize name, removing whitespace from the beginning, the end
217 // and extraneous spaces between tokens.
218 name = simgear::strutils::simplify(name);
219 } catch (const std::logic_error& exc) {
220 // On my system using GNU libstdc++, exc.what() is limited to the function
221 // name (e.g., 'stod')!
222 SG_LOG( SG_NAVAID, SG_WARN,
223 utf8Path << ":" << lineNum << ": unable to parse (" <<
224 exc.what() << "): '" <<
225 simgear::strutils::stripTrailingNewlines(line) << "'" );
226 return 0;
227 }
228
229 SGGeod pos(SGGeod::fromDegFt(lon, lat, static_cast<double>(elev_ft)));
230
231 // The type can be forced by our caller, but normally we use the value
232 // supplied in the .dat file.
233 if (type == FGPositioned::INVALID) {
234 type = mapRobinTypeToFGPType(rowCode);
235 if (type == FGPositioned::INVALID) {
236 static std::set<int> ignoredCodes;
237 if (ignoredCodes.insert(rowCode).second) {
238 SG_LOG(SG_NAVAID, SG_WARN,
239 utf8Path << ":" << lineNum << ": unrecognized row code "
240 << rowCode << ", ignoring this line and all further lines "
241 << "with the same code");
242 }
243 return 0;
244 }
245 }
246
247 // Silently multiply ADF frequencies by 100 so that ADF
248 // vs. NAV/LOC frequency lookups can use the same code.
249 if (type == FGPositioned::NDB) {
250 freq *= 100;
251 }
252
253 //
254 // Deduplication rules:
255 //
256 // 1. Navaid files are loaded according to the order in $FG_SCENERY,
257 // followed by the default file in $FG_ROOT/Navaids.
258 //
259 // 2. Navaids from each of these files are considered one by one and added
260 // to the cache, except a navaid is *not* added if another one lies within
261 // DUPLICATE_DETECTION_RADIUS_NM and:
262 //
263 // - it has the same name, type and ident, or
264 // - it has the same type and ident, and:
265 // * is either attached to the same runway (for navaid types LOC, ILS,
266 // GS, IM, MM, OM), or
267 // * has type FGPositioned::DME and the same frequency
268 // (this ensures that colocated DMEs and TACANs are *not* considered
269 // as duplicates, since they normally have different frequencies)
270 //
271 // For this logic to work reasonably, each set of nav.dat files in a given
272 // scenery path must be self-contained regarding the colocated navaids --
273 // if one of the colocated navaids is present, the other one must be too,
274 // and the files must be sorted by row codes as mandated in XP-NAV1100 spec.
275 //
276
277 // First, eliminate nearby with the same name, type and ident.
278 auto loadedNavsKey = std::make_tuple(type, ident, name);
279 auto matchingNavs = _loadedNavs.equal_range(loadedNavsKey);
280 for (auto it = matchingNavs.first; it != matchingNavs.second; ++it) {
281 if (isNearby(pos, it->second)) {
282 SG_LOG(SG_NAVAID, SG_INFO,
283 utf8Path << ":" << lineNum << ": skipping navaid '" <<
284 name << "' (already defined nearby)");
285 return 0;
286 }
287 }
288 _loadedNavs.emplace(loadedNavsKey, pos);
289
290 // Then, eliminate nearby with the same type and ident.
291 FGPositioned::TypeFilter dupTypeFilter(type);
293 &dupTypeFilter);
294 if (ref.valid()) {
295 if (isNearby(pos, ref->geod())
296 && canBeDuplicate(ref, type, name, pos, freq)) {
297 SG_LOG(SG_NAVAID, SG_INFO,
298 utf8Path << ":" << lineNum << ": skipping navaid '" <<
299 name << "' (nearby suspected duplicate '" << ref->name() << "')");
300 return 0;
301 }
302 }
303
304 if ((type >= FGPositioned::OM) && (type <= FGPositioned::IM)) {
305 AirportRunwayPair arp(cache->findAirportRunway(name));
306
307 if (!arp.first || !arp.second) {
308 SG_LOG(SG_NAVAID, SG_INFO,
309 utf8Path << ":" << lineNum << ": couldn't find matching runway " <<
310 "for marker '" << name << "', skipping.");
311 return 0;
312 }
313
314 if (arp.second && (elev_ft <= 0)) {
315 // snap to runway elevation
316 FGPositionedRef runway = cache->loadById(arp.second);
317 assert(runway);
318 pos.setElevationFt(runway->geod().getElevationFt());
319 }
320
321 return cache->insertNavaid(type, string(), name, pos, 0, 0, 0,
322 arp.first, arp.second);
323 }
324
325 if (range < 1) {
326 range = defaultNavRange(ident, type);
327 }
328
330 FGRunwayRef runway;
331 PositionedID navaid_dme = 0;
332
333 if (type == FGPositioned::DME) {
334 // complication here: the database doesn't record TACAN sites at all,
335 // we simply infer their existence from DMEs whose name includes 'VORTAC'
336 // or 'TACAN' (since all TACAN stations include DME)
337 // hence the cache never actually includes entries of type TACAN
338 FGPositioned::TypeFilter f(FGPositioned::INVALID);
339 if ( name.find("VOR-DME") != std::string::npos ) {
340 f.addType(FGPositioned::VOR);
341 } else if ( name.find("DME-ILS") != std::string::npos ) {
342 f.addType(FGPositioned::ILS);
343 f.addType(FGPositioned::LOC);
344 } else if ( name.find("VORTAC") != std::string::npos ) {
345 f.addType(FGPositioned::VOR);
346 } else if ( name.find("NDB-DME") != std::string::npos ) {
347 f.addType(FGPositioned::NDB);
348 }
349
350 if (f.maxType() > 0) {
352 if (ref.valid()) {
353 string_list dme_part = simgear::strutils::split(name , 0 ,1);
354 string_list navaid_part = simgear::strutils::split(ref.get()->name(), 0 ,1);
355
356 if ( simgear::strutils::uppercase(navaid_part[0]) == simgear::strutils::uppercase(dme_part[0]) ) {
357 navaid_dme = ref.get()->guid();
358 } else {
359 SG_LOG(SG_NAVAID, SG_INFO,
360 utf8Path << ":" << lineNum << ": DME '" << ident << "' (" <<
361 name << "): while looking for a colocated navaid, found " <<
362 "that the closest match has wrong name: '" <<
363 ref->ident() << "' (" << ref->name() << ")");
364 }
365 } else {
366 SG_LOG(SG_NAVAID, SG_INFO,
367 utf8Path << ":" << lineNum << ": couldn't find any colocated "
368 "navaid for DME '" << ident << "' (" << name << ")");
369 }
370 } // of have a co-located navaid to locate
371 }
372
373 if ((type >= FGPositioned::ILS) && (type <= FGPositioned::GS)) {
374 arp = cache->findAirportRunway(name);
375 if (!arp.first || !arp.second) {
376 SG_LOG(SG_NAVAID, SG_INFO,
377 utf8Path << ":" << lineNum << ": couldn't find matching runway " <<
378 "for ILS/LOC/GS navaid '" << name << "', ignoring it.");
379 return 0;
380 }
381 if (arp.second) {
382 runway = FGPositioned::loadById<FGRunway>(arp.second);
383 assert(runway);
384#if 0
385 // code is disabled since it's causing some problems, see
386 // https://sourceforge.net/p/flightgear/codetickets/926/
387 if (elev_ft <= 0) {
388 // snap to runway elevation
389 pos.setElevationFt(runway->geod().getElevationFt());
390 }
391#endif
392 } // of found runway in the DB
393 } // of type is runway-related
394
395 bool isLoc = (type == FGPositioned::ILS) || (type == FGPositioned::LOC);
396 PositionedID r = cache->insertNavaid(type, ident, name, pos, freq, range,
397 multiuse, arp.first, arp.second);
398
399 if (isLoc) {
400 cache->setRunwayILS(arp.second, r);
401 }
402
403 if (navaid_dme) {
404 cache->setNavaidColocated(navaid_dme, r);
405 }
406
407 return r;
408}
409
410// load and initialize the navigational databases
412 std::size_t bytesReadSoFar,
413 std::size_t totalSizeOfAllDatFiles)
414{
416 const SGPath path = sceneryLocation.datPath;
417 const string utf8Path = path.utf8Str();
418 sg_gzifstream in(path);
419
420 if ( !in.is_open() ) {
421 throw sg_io_exception(
422 "Cannot open file (" + simgear::strutils::error_string(errno) + ")",
423 sg_location(path));
424 }
425
426 string line;
427
428 // Skip the first two lines
429 for (int i = 0; i < 2; i++) {
430 std::getline(in, line);
432 }
433
434 unsigned int lineNumber;
435 unsigned int version;
436 vector<string> fields(simgear::strutils::split(line, 0, 1));
437
438 try {
439 if (fields.empty()) {
440 throw sg_format_exception();
441 }
442 version = strutils::readNonNegativeInt<unsigned int>(fields[0]);
443 } catch (const sg_exception& exc) {
444 std::string strippedLine = simgear::strutils::stripTrailingNewlines(line);
445 std::string errMsg = utf8Path + ": ";
446
447 if (fields.empty()) {
448 errMsg += "unable to parse format version: empty line";
449 SG_LOG(SG_NAVAID, SG_ALERT, errMsg);
450 } else {
451 errMsg += "unable to parse format version";
452 SG_LOG(SG_NAVAID, SG_ALERT,
453 errMsg << " (" << exc.what() << "): " << strippedLine);
454 }
455
456 throw sg_format_exception(errMsg, strippedLine);
457 }
458
459 SG_LOG(SG_NAVAID, SG_INFO,
460 "nav.dat format version (" << utf8Path << "): " << version);
461
462 for (lineNumber = 3; std::getline(in, line); lineNumber++) {
463 processNavLine(line, utf8Path, lineNumber, FGPositioned::INVALID, version);
464
465 if ((lineNumber % 100) == 0) {
466 // every 100 lines
467 unsigned int percent = ((bytesReadSoFar + in.approxOffset()) * 100)
468 / totalSizeOfAllDatFiles;
470 }
471
472 } // of stream data loop
473
475}
476
477void NavLoader::loadCarrierNav(const SGPath& path)
478{
479 SG_LOG( SG_NAVAID, SG_DEBUG, "Opening file: " << path );
480 const string utf8Path = path.utf8Str();
481 sg_gzifstream in(path);
482
483 if ( !in.is_open() ) {
484 throw sg_io_exception(
485 "Cannot open file (" + simgear::strutils::error_string(errno) + ")",
486 sg_location(path));
487 }
488
489 string line;
490 unsigned int lineNumber;
491
492 for (lineNumber = 1; std::getline(in, line); lineNumber++) {
493 // Force the navaid type to be MOBILE_TACAN
494 processNavLine(line, utf8Path, lineNumber, FGPositioned::MOBILE_TACAN);
495 }
496
498}
499
500bool NavLoader::loadTacan(const SGPath& path, FGTACANList *channellist)
501{
502 SG_LOG( SG_NAVAID, SG_DEBUG, "opening file: " << path );
503 sg_gzifstream inchannel( path );
504
505 if ( !inchannel.is_open() ) {
506 SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
507 return false;
508 }
509
510 // skip first line
511 inchannel >> skipeol;
512 while ( ! inchannel.eof() ) {
514 inchannel >> (*r);
515 channellist->add ( r );
516
517 } // end while
518
519 return true;
520}
521
522} // of namespace flightgear
#define i(x)
SGSharedPtr< FGRunway > FGRunwayRef
int get_freq() const
Definition navrecord.hxx:76
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
static SGSharedPtr< T > loadById(PositionedID id)
@ DME
important that DME & TACAN are adjacent to keep the TacanFilter efficient - DMEs are proxies for TACA...
bool add(FGTACANRecord *r)
Definition navlist.cxx:316
PositionedID findNavaidForRunway(PositionedID runway, FGPositioned::Type ty)
Given a runway and type, find the corresponding navaid (ILS / GS / OM)
static NavDataCache * instance()
void setRebuildPhaseProgress(RebuildPhase ph, unsigned int percent=0)
AirportRunwayPair findAirportRunway(const std::string &name)
given a navaid name (or similar) from apt.dat / nav.dat, find the corresponding airport and runway ID...
bool loadTacan(const SGPath &path, FGTACANList *channellist)
Definition navdb.cxx:500
void loadNav(const NavDataCache::SceneryLocation &sceneryLocation, std::size_t bytesReadSoFar, std::size_t totalSizeOfAllDatFiles)
Definition navdb.cxx:411
void loadCarrierNav(const SGPath &path)
Definition navdb.cxx:477
const char * name
static const double DUPLICATE_DETECTION_RADIUS_NM
Definition fixlist.cxx:50
std::vector< std::string > string_list
Definition globals.hxx:36
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
static bool canBeDuplicate(FGPositionedRef ref, FGPositioned::Type type, const std::string &name, const SGGeod &pos, int freq)
Definition navdb.cxx:122
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
static const double DUPLICATE_DETECTION_RADIUS_NM
Definition poidb.cxx:46
const char * name
static bool isNearby(const SGGeod &pos1, const SGGeod &pos2)
Definition navdb.cxx:116
std::pair< PositionedID, PositionedID > AirportRunwayPair
a pair of airport ID, runway ID
static FGPositioned::Type mapRobinTypeToFGPType(int aTy)
Definition navdb.cxx:67
static void throwExceptionIfStreamError(const std::istream &inputStream, const SGPath &path)
Definition navdb.cxx:53
static double defaultNavRange(const string &ident, FGPositioned::Type type)
Definition navdb.cxx:84
const double FG_TACAN_DEFAULT_RANGE
Definition navrecord.hxx:39
const double FG_DME_DEFAULT_RANGE
Definition navrecord.hxx:38
const double FG_NAV_DEFAULT_RANGE
Definition navrecord.hxx:36
const double FG_LOC_DEFAULT_RANGE
Definition navrecord.hxx:37
int64_t PositionedID