1
0
Fork 0

Support merging of arbitrary apt.dat[.gz] files

It is now allowed to have the same airport appear in several apt.dat
files ($scenery_path/NavData/apt/*.dat[.gz] for each scenery path, plus
the default $FG_ROOT/Airports/apt.dat.gz, coming last). Airports found
in earlier files(*) take precedence over those found later, in case
several apt.dat files define the same airports. Airports that are
skipped due to this mechanism are logged with
SG_LOG(SG_GENERAL, SG_INFO, ...).

(*) using 1) FG_SCENERY order (followed by $FG_ROOT/Airports/apt.dat.gz)
    and   2) lexicographic order inside each $scenery_path/NavData/apt
             folder

With this commit, APTLoader::parseAPT() is replaced by two methods:
readAptDatFile() and loadAirports():
  - APTLoader::readAptDatFile() reads airport definitions from an
    apt.dat file into APTLoader's 'airportInfoMap' member variable,
    discarding duplicate definitions due to overlapping apt.dat files
    ('airportInfoMap' is an std::unordered_map instance in C++11 and
    later, an std::map otherwise);
  - APTLoader::loadAirports() reads each airport definition from
    'airportInfoMap' and loads it into the NavCache, the same way as
    APTLoader::parseAPT() used to do.

The airportDBLoad() function is not useful anymore, and is thus removed
(in NavDataCache::doRebuild(), APTLoader::readAptDatFile() is now called
once per apt.dat file, but APTLoader::loadAirports() is only called
once at the end, after duplicate airports have been discarded; the class
interface is much better suited to this scheme, because it can cleanly
retain the state between these calls).

By the way, this commit fixes an old bug: APTLoader's member variable
'last_apt_id' was used in several places but never assigned to, except
in APTLoader::APTLoader() as the empty string.

Thanks to Alan Teeder for his feedback and testing.
This commit is contained in:
Florent Rougon 2016-05-23 00:43:03 +02:00 committed by James Turner
parent 670cf9a894
commit 516a5cf016
3 changed files with 192 additions and 82 deletions

View file

@ -44,6 +44,7 @@
#include <string>
#include <vector>
#include <utility> // std::pair, std::move()
#include "airport.hxx"
#include "runways.hxx"
@ -85,7 +86,7 @@ APTLoader::APTLoader()
APTLoader::~APTLoader() { }
void APTLoader::parseAPT(const SGPath &aptdb_file)
void APTLoader::readAptDatFile(const SGPath &aptdb_file)
{
string apt_dat = aptdb_file.utf8Str(); // full path to the file being parsed
sg_gzifstream in(aptdb_file);
@ -103,6 +104,15 @@ void APTLoader::parseAPT(const SGPath &aptdb_file)
unsigned int line_id = 0;
unsigned int line_num = 0;
// "airport identifier": terminology used in the apt.dat format spec. It is
// often an ICAO code, but not always.
string currentAirportId;
// Boolean used to make sure we don't try to load the same airport several
// times. Defaults to true only to ensure we don't add garbage to
// 'airportInfoMap' under the key "" (empty airport identifier) in case the
// apt.dat file doesn't have a start-of-airport row code (1, 16 or 17) after
// its header---which would be invalid, anyway.
bool skipAirport = true;
// Read the apt.dat header (two lines)
while ( line_num < 2 && std::getline(in, line) ) {
@ -150,20 +160,91 @@ void APTLoader::parseAPT(const SGPath &aptdb_file)
if ( line_id == 1 /* Airport */ ||
line_id == 16 /* Seaplane base */ ||
line_id == 17 /* Heliport */ ) {
parseAirportLine(apt_dat, simgear::strutils::split(line));
} else if ( line_id == 10 ) { // Runway v810
parseRunwayLine810(simgear::strutils::split(line));
vector<string> tokens(simgear::strutils::split(line));
if (tokens.size() < 6) {
SG_LOG( SG_GENERAL, SG_WARN,
apt_dat << ":" << line_num << ": invalid airport header "
"(at least 6 fields are required)" );
skipAirport = true; // discard everything until the next airport header
continue;
}
currentAirportId = tokens[4]; // often an ICAO, but not always
// Check if the airport is already in 'airportInfoMap'; get the
// existing entry, if any, otherwise insert a new one.
std::pair<AirportInfoMapType::iterator, bool>
insertRetval = airportInfoMap.insert(
AirportInfoMapType::value_type(currentAirportId, RawAirportInfo()));
skipAirport = !insertRetval.second;
if ( skipAirport ) {
SG_LOG( SG_GENERAL, SG_INFO,
apt_dat << ":" << line_num << ": skipping airport " <<
currentAirportId << " (already defined earlier)" );
} else {
// We haven't seen this airport yet in any apt.dat file
RawAirportInfo& airportInfo = insertRetval.first->second;
airportInfo.file = aptdb_file;
airportInfo.rowCode = line_id;
airportInfo.firstLineNum = line_num;
airportInfo.firstLineTokens =
#if __cplusplus >= 201103L
std::move(tokens); // requires C++11, untested
#else
tokens;
#endif
}
} else if ( line_id == 99 ) {
SG_LOG( SG_GENERAL, SG_DEBUG,
apt_dat << ":" << line_num << ": code 99 found "
"(normally at end of file)" );
} else if ( !skipAirport ) {
// Line belonging to an already started, and not skipped airport entry;
// just append it.
airportInfoMap[currentAirportId].otherLines.
#if __cplusplus >= 201103L
emplace_back(line_num, line_id, line); // requires C++11, untested
#else
push_back(Line(line_num, line_id, line));
#endif
}
} // of file reading loop
throwExceptionIfStreamError(in, aptdb_file);
}
void APTLoader::loadAirports()
{
// Loop over all airports found in all apt.dat files
for (AirportInfoMapType::const_iterator it = airportInfoMap.begin();
it != airportInfoMap.end(); it++) {
// Full path to the apt.dat file this airport info comes from
const string aptDat = it->second.file.utf8Str();
last_apt_id = it->first; // this is just the current airport identifier
// The first line for this airport was already split over whitespace, but
// remains to be parsed for the most part.
parseAirportLine(it->second.rowCode, it->second.firstLineTokens);
const LinesList& lines = it->second.otherLines;
// Loop over the second and subsequent lines
for (LinesList::const_iterator linesIt = lines.begin();
linesIt != lines.end(); linesIt++) {
// Beware that linesIt->str may end with an '\r' character, see above!
unsigned int line_id = linesIt->rowCode;
if ( line_id == 10 ) { // Runway v810
parseRunwayLine810(simgear::strutils::split(linesIt->str));
} else if ( line_id == 100 ) { // Runway v850
parseRunwayLine850(simgear::strutils::split(line));
parseRunwayLine850(simgear::strutils::split(linesIt->str));
} else if ( line_id == 101 ) { // Water Runway v850
parseWaterRunwayLine850(simgear::strutils::split(line));
parseWaterRunwayLine850(simgear::strutils::split(linesIt->str));
} else if ( line_id == 102 ) { // Helipad v850
parseHelipadLine850(simgear::strutils::split(line));
parseHelipadLine850(simgear::strutils::split(linesIt->str));
} else if ( line_id == 18 ) {
// beacon entry (ignore)
} else if ( line_id == 14 ) {
// control tower entry
vector<string> token(simgear::strutils::split(line));
vector<string> token(simgear::strutils::split(linesIt->str));
double lat = atof( token[1].c_str() );
double lon = atof( token[2].c_str() );
@ -181,13 +262,14 @@ void APTLoader::parseAPT(const SGPath &aptdb_file)
} else if ( line_id == 0 ) {
// ??
} else if ( line_id >= 50 && line_id <= 56) {
parseCommLine(apt_dat, line_id, simgear::strutils::split(line));
parseCommLine(aptDat, line_id, simgear::strutils::split(linesIt->str));
} else if ( line_id == 110 ) {
pavement = true;
parsePavementLine850(simgear::strutils::split(line, 0, 4));
parsePavementLine850(simgear::strutils::split(linesIt->str, 0, 4));
} else if ( line_id >= 111 && line_id <= 114 ) {
if ( pavement )
parsePavementNodeLine850(line_id, simgear::strutils::split(line));
parsePavementNodeLine850(line_id,
simgear::strutils::split(linesIt->str));
} else if ( line_id >= 115 && line_id <= 116 ) {
// other pavement nodes (ignore)
} else if ( line_id == 120 ) {
@ -196,19 +278,18 @@ void APTLoader::parseAPT(const SGPath &aptdb_file)
pavement = false;
} else if ( line_id >= 1000 ) {
// airport traffic flow (ignore)
} else if ( line_id == 99 ) {
SG_LOG( SG_GENERAL, SG_DEBUG,
apt_dat << ": code 99 found (normally at end of file)" );
} else {
std::ostringstream oss;
oss << apt_dat << ":" << line_num << ": unknown row code " << line_id;
SG_LOG( SG_GENERAL, SG_ALERT, oss.str() << " (" << line << ")" );
throw sg_format_exception(oss.str(), line);
}
oss << aptDat << ":" << linesIt->number << ": unknown row code " <<
line_id;
SG_LOG( SG_GENERAL, SG_ALERT,
oss.str() << " (" << linesIt->str << ")" );
throw sg_format_exception(oss.str(), linesIt->str);
}
} // of loop over the second and subsequent apt.dat lines for the airport
throwExceptionIfStreamError(in, aptdb_file);
finishAirport(apt_dat);
finishAirport(aptDat);
} // of loop over 'airportInfoMap'
}
// Tell whether an apt.dat line is blank or a comment line
@ -253,15 +334,13 @@ void APTLoader::finishAirport(const string& aptDat)
currentAirportID = 0;
}
void APTLoader::parseAirportLine(const string& aptDat,
// 'rowCode' is passed to avoid decoding it twice, since that work was already
// done in order to detect the start of the new airport.
void APTLoader::parseAirportLine(unsigned int rowCode,
const vector<string>& token)
{
const string& id(token[4]);
double elev = atof( token[1].c_str() );
// finish the previous airport
finishAirport(aptDat);
last_apt_elev = elev;
string name;
@ -276,8 +355,7 @@ void APTLoader::parseAirportLine(const string& aptDat,
rwy_lat_accum = 0.0;
rwy_count = 0;
int robinType = atoi(token[0].c_str());
currentAirportID = cache->insertAirport(fptypeFromRobinType(robinType),
currentAirportID = cache->insertAirport(fptypeFromRobinType(rowCode),
id, name);
}
@ -553,16 +631,7 @@ void APTLoader::parseCommLine(const string& aptDat, int lineId,
"), skipping" );
}
// Load the airport data base from the specified aptdb file. The
// metar file is used to mark the airports as having metar available
// or not.
bool airportDBLoad( const SGPath &aptdb_file )
{
APTLoader ld;
ld.parseAPT(aptdb_file);
return true;
}
// The 'metar.dat' file lists the airports that have METAR available.
bool metarDataLoad(const SGPath& metar_file)
{
sg_gzifstream metar_in( metar_file );

View file

@ -28,13 +28,18 @@
#include <string>
#include <vector>
#if __cplusplus >= 201103L
#include <unordered_map>
#else
#include <map>
#endif
#include <simgear/compiler.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/SGGeod.hxx>
#include <simgear/misc/sg_path.hxx>
#include <Navaids/positioned.hxx>
// Forward declarations
class SGPath;
class NavDataCache;
class sg_gzifstream;
class FGPavement;
@ -48,9 +53,47 @@ public:
APTLoader();
~APTLoader();
void parseAPT(const SGPath &aptdb_file);
// Read the specified apt.dat file into 'airportInfoMap'
void readAptDatFile(const SGPath& aptdb_file);
// Read all airports gathered in 'airportInfoMap' and load them into the
// navdata cache (even in case of overlapping apt.dat files,
// 'airportInfoMap' has only one entry per airport).
void loadAirports();
private:
struct Line
{
Line(unsigned int number_, unsigned int rowCode_, std::string str_)
: number(number_), rowCode(rowCode_), str(str_) { }
unsigned int number;
unsigned int rowCode; // Terminology of the apt.dat spec
std::string str;
};
typedef std::vector<Line> LinesList;
struct RawAirportInfo
{
// apt.dat file where the airport was defined
SGPath file;
// Row code for the airport (1, 16 or 17)
unsigned int rowCode;
// Line number in the apt.dat file where the airport definition starts
unsigned int firstLineNum;
// The whitespace-separated strings comprising the first line of the airport
// definition
std::vector<std::string> firstLineTokens;
// Subsequent lines of the airport definition (one element per line)
LinesList otherLines;
};
#if __cplusplus >= 201103L
typedef std::unordered_map<std::string, RawAirportInfo> AirportInfoMapType;
#else
typedef std::map<std::string, RawAirportInfo> AirportInfoMapType;
#endif
typedef SGSharedPtr<FGPavement> FGPavementPtr;
APTLoader(const APTLoader&); // disable copy constructor
@ -60,7 +103,7 @@ private:
bool isBlankOrCommentLine(const std::string& line);
void throwExceptionIfStreamError(const sg_gzifstream& input_stream,
const SGPath& path);
void parseAirportLine(const std::string& aptDat,
void parseAirportLine(unsigned int rowCode,
const std::vector<std::string>& token);
void finishAirport(const std::string& aptDat);
void parseRunwayLine810(const std::vector<std::string>& token);
@ -73,6 +116,8 @@ private:
void parseCommLine(const std::string& aptDat, int lineId,
const std::vector<std::string>& token);
std::vector<std::string> token;
AirportInfoMapType airportInfoMap;
double rwy_lat_accum;
double rwy_lon_accum;
double last_rwy_heading;
@ -90,12 +135,6 @@ private:
PositionedID currentAirportID;
};
// Load the airport data base from the specified aptdb file. The
// metar file is used to mark the airports as having metar available
// or not.
bool airportDBLoad(const SGPath& path);
bool metarDataLoad(const SGPath& path);
} // of namespace flighgear

View file

@ -1339,16 +1339,18 @@ void NavDataCache::doRebuild()
SGTimeStamp st;
{
Transaction txn(this);
APTLoader aptLoader;
string_list aptDatFiles;
st.stamp();
for (PathList::const_iterator it = d->aptDatPaths.begin();
it != d->aptDatPaths.end(); it++) {
aptDatFiles.push_back(it->realpath().utf8Str());
airportDBLoad(*it);
aptLoader.readAptDatFile(*it);
stampCacheFile(*it); // this uses the realpath() of the file
}
aptLoader.loadAirports(); // load airport data into the NavCache
// Store the list of apt.dat files we have loaded
writeOrderedStringListProperty(
datTypeStr[DATFILETYPE_APT] + ".dat files", aptDatFiles,