1
0
Fork 0

Improve parsing of nav.dat and carrier_nav.dat

- Handle file I/O and parsing errors. This allows current
  <http://gateway.x-plane.com/navaids/LatestNavFix.zip> to be loaded
  instead of failing in an endless loop.

  Read the fields of a record with std::getline() followed by
  simgear::strutils::split(), itself followed by calls to std::stoi(),
  std::stof() and std::stod(). Stream extraction (>>) isn't very good
  here, because it can read for instance an int *and* a float from the
  string "3.14", i.e. extract 3 followed by 0.14 (thus falsifying the
  number of fields found...).

  Check the number of fields (not 100 % reliable since the last field,
  the navaid name, typically contains spaces---but we can detect
  some situations where the number of fields is definitely too low).

- Fix line numbering (sgstream.cxx's skipcomment() isn't fit for this
  purpose, because it can gobble any number of lines without the line
  number being increased).

- Don't use a hardcoded number of lines for the nav.dat loading
  percentage indicator; rely on sg_gzifstream::approxOffset() instead.

- Make navDBInit() and loadCarrierNav() throw an sg_io_exception upon
  I/O errors (not for parsing errors that only affect a record). They
  don't return a bool anymore (which wasn't checked by their only
  caller, anyway).

- Use the 'rowCode' variable name instead of 'rawType' for consistency
  with the *.dat specs.

- Small change in tests: (elev_ft < 0.01) replaced with (elev_ft <= 0).
  Doesn't change the behavior, since 'elev_ft' is an int. Can be
  reverted if someone really prefers the float comparison, I don't mind
  that much about it.

- Add missing headers and a few comments.

This commit does not change the contents added to the NavCache with
FlightGear's current $FG_ROOT/Navaids/nav.dat.gz, except for 4 bogus
navaids which are not added anymore (at LIBV, OPJA, RCFN and RCYU): see
<https://sourceforge.net/p/flightgear/mailman/message/35526617/>. They
are instead properly reported as coming from invalid nav.dat lines.
This commit is contained in:
Florent Rougon 2016-12-06 08:08:22 +01:00
parent 280cd52368
commit a2bf424118
2 changed files with 144 additions and 68 deletions

View file

@ -24,6 +24,13 @@
# include "config.h"
#endif
#include <string>
#include <vector>
#include <istream>
#include <cmath>
#include <cstddef> // std::size_t
#include <cerrno>
#include "navdb.hxx"
#include <simgear/compiler.h>
@ -49,6 +56,19 @@
using std::string;
using std::vector;
static void throwExceptionIfStreamError(const std::istream& inputStream,
const SGPath& path)
{
if (inputStream.bad()) {
const std::string errMsg = simgear::strutils::error_string(errno);
SG_LOG(SG_NAVAID, SG_ALERT,
"Error while reading '" << path.utf8Str() << "': " << errMsg);
throw sg_io_exception("Error reading file (" + errMsg + ")",
sg_location(path));
}
}
static FGPositioned::Type
mapRobinTypeToFGPType(int aTy)
{
@ -134,37 +154,87 @@ static double defaultNavRange(const string& ident, FGPositioned::Type type)
namespace flightgear
{
static PositionedID readNavFromStream(std::istream& aStream,
FGPositioned::Type type = FGPositioned::INVALID)
// Parse a line from a file such as nav.dat or carrier_nav.dat. Load the
// corresponding data into the NavDataCache.
static PositionedID processNavLine(
string& line, const string& utf8Path, unsigned int lineNum,
FGPositioned::Type type = FGPositioned::INVALID)
{
NavDataCache* cache = NavDataCache::instance();
int rawType;
aStream >> rawType;
if( aStream.eof() || (rawType == 99) || (rawType == 0) )
return 0; // happens with, eg, carrier_nav.dat
double lat, lon, elev_ft, multiuse;
int freq, range;
std::string name, ident;
aStream >> lat >> lon >> elev_ft >> freq >> range >> multiuse >> ident;
getline(aStream, name);
SGGeod pos(SGGeod::fromDegFt(lon, lat, elev_ft));
name = simgear::strutils::strip(name);
int rowCode, elev_ft, freq, range;
// 'multiuse': different meanings depending on the record's row code
double lat, lon, multiuse;
// Short identifier and longer name for a navaid (e.g., 'OLN' and
// 'LFPO 02 OM')
string ident, name;
// the type can be forced by our caller, but normally we use th value
// supplied in the .dat file
if (simgear::strutils::starts_with(line, "#")) {
// carrier_nav.dat has a comment line using this syntax...
return 0;
}
// At most 9 fields (the ninth field may contain spaces)
vector<string> fields(simgear::strutils::split(line, 0, 8));
vector<string>::size_type nbFields = fields.size();
static const string endOfData = "99"; // special code in the nav.dat spec
if (nbFields == 0) { // blank line
return 0;
} else if (nbFields == 1) {
if (fields[0] != endOfData) {
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": malformed line: only one "
"field, but it is not '99'" );
}
return 0;
} else if (nbFields < 9) {
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": invalid line "
"(at least 9 fields are required)" );
return 0;
}
// When their string argument can't be properly converted, std::stoi(),
// std::stof() and std::stod() all raise an exception which is always a
// subclass of std::logic_error.
try {
rowCode = std::stoi(fields[0]);
lat = std::stod(fields[1]);
lon = std::stod(fields[2]);
elev_ft = std::stoi(fields[3]);
// The input data is a floating point number, but we are going to feed it
// to NavDataCache::insertNavaid(), which takes an int.
freq = static_cast<int>(std::lround(std::stof(fields[4])));
range = std::stoi(fields[5]);
multiuse = std::stod(fields[6]);
ident = fields[7];
name = simgear::strutils::strip(fields[8]);
} catch (const std::logic_error& exc) {
// On my system using GNU libstdc++, exc.what() is limited to the function
// name (e.g., 'stod')!
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": unable to parse (" <<
exc.what() << "): '" <<
simgear::strutils::stripTrailingNewlines(line) << "'" );
return 0;
}
SGGeod pos(SGGeod::fromDegFt(lon, lat, static_cast<double>(elev_ft)));
// The type can be forced by our caller, but normally we use the value
// supplied in the .dat file.
if (type == FGPositioned::INVALID) {
type = mapRobinTypeToFGPType(rawType);
type = mapRobinTypeToFGPType(rowCode);
}
if (type == FGPositioned::INVALID) {
return 0;
}
// Note: for these types, the XP NAV810 and XP NAV1100 specs differ.
if ((type >= FGPositioned::OM) && (type <= FGPositioned::IM)) {
AirportRunwayPair arp(cache->findAirportRunway(name));
if (arp.second && (elev_ft < 0.01)) {
if (arp.second && (elev_ft <= 0)) {
// snap to runway elevation
FGPositioned* runway = cache->loadById(arp.second);
assert(runway);
@ -225,8 +295,8 @@ static PositionedID readNavFromStream(std::istream& aStream,
assert(runway);
#if 0
// code is disabled since it's causing some problems, see
// http://code.google.com/p/flightgear-bugs/issues/detail?id=926
if (elev_ft < 0.01) {
// https://sourceforge.net/p/flightgear/codetickets/926/
if (elev_ft <= 0) {
// snap to runway elevation
pos.setElevationFt(runway->geod().getElevationFt());
}
@ -259,63 +329,69 @@ static PositionedID readNavFromStream(std::istream& aStream,
return r;
}
// generated by wc -l on uncomrpessed nav.dat
const unsigned int LINES_IN_NAV_DAT = 26775;
// load and initialize the navigational databases
bool navDBInit(const SGPath& path)
void navDBInit(const SGPath& path)
{
sg_gzifstream in( path );
if ( !in.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
return false;
}
NavDataCache* cache = NavDataCache::instance();
const string utf8Path = path.utf8Str();
const std::size_t fileSize = path.sizeInBytes();
sg_gzifstream in(path);
if ( !in.is_open() ) {
throw sg_io_exception(
"Cannot open file (" + simgear::strutils::error_string(errno) + ")",
sg_location(path));
}
autoAlignLocalizers = fgGetBool("/sim/navdb/localizers/auto-align", true);
autoAlignThreshold = fgGetDouble( "/sim/navdb/localizers/auto-align-threshold-deg", 5.0 );
NavDataCache* cache = NavDataCache::instance();
// skip first two lines
// Skip the first two lines
for (int i = 0; i < 2; i++) {
in >> skipeol;
in >> skipeol;
unsigned int lineNumber = 2;
throwExceptionIfStreamError(in, path);
}
while (!in.eof()) {
readNavFromStream(in);
in >> skipcomment;
string line;
unsigned int lineNumber;
++lineNumber;
if ((lineNumber % 100) == 0) {
// every 100 lines
unsigned int percent = (lineNumber * 100) / LINES_IN_NAV_DAT;
cache->setRebuildPhaseProgress(NavDataCache::REBUILD_NAVAIDS, percent);
}
for (lineNumber = 3; std::getline(in, line); lineNumber++) {
processNavLine(line, utf8Path, lineNumber);
} // of stream data loop
return true;
}
bool loadCarrierNav(const SGPath& path)
{
SG_LOG( SG_NAVAID, SG_DEBUG, "opening file: " << path );
sg_gzifstream incarrier( path );
if ( !incarrier.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
return false;
}
while ( ! incarrier.eof() ) {
incarrier >> skipcomment;
// force the type to be MOBILE_TACAN
readNavFromStream(incarrier, FGPositioned::MOBILE_TACAN);
if ((lineNumber % 100) == 0) {
// every 100 lines
unsigned int percent = (in.approxOffset() * 100) / fileSize;
cache->setRebuildPhaseProgress(NavDataCache::REBUILD_NAVAIDS, percent);
}
return true;
} // of stream data loop
throwExceptionIfStreamError(in, path);
}
void loadCarrierNav(const SGPath& path)
{
SG_LOG( SG_NAVAID, SG_DEBUG, "Opening file: " << path );
const string utf8Path = path.utf8Str();
sg_gzifstream in(path);
if ( !in.is_open() ) {
throw sg_io_exception(
"Cannot open file (" + simgear::strutils::error_string(errno) + ")",
sg_location(path));
}
string line;
unsigned int lineNumber;
for (lineNumber = 1; std::getline(in, line); lineNumber++) {
// Force the navaid type to be MOBILE_TACAN
processNavLine(line, utf8Path, lineNumber, FGPositioned::MOBILE_TACAN);
}
throwExceptionIfStreamError(in, path);
}
bool loadTacan(const SGPath& path, FGTACANList *channellist)
{
SG_LOG( SG_NAVAID, SG_DEBUG, "opening file: " << path );

View file

@ -38,9 +38,9 @@ namespace flightgear
{
// load and initialize the navigational databases
bool navDBInit(const SGPath& path);
void navDBInit(const SGPath& path);
bool loadCarrierNav(const SGPath& path);
void loadCarrierNav(const SGPath& path);
bool loadTacan(const SGPath& path, FGTACANList *channellist);