X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FNavaids%2FNavDataCache.cxx;h=f6756613bd6c19130d5c308b44c36282add9852f;hb=5c659b3970e9a4542519e0d528016c08f7ecb6c1;hp=5974aae584a5f3aaa72705510e64250564914da3;hpb=5ac3de21e3e41b9e1723e705978b01ea36d406f6;p=flightgear.git diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index 5974aae58..f6756613b 100644 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -23,11 +23,6 @@ # include "config.h" #endif -// to ensure compatability between sqlite3_int64 and PositionedID, -// force the type used by sqlite to match PositionedID explicitly -#define SQLITE_INT64_TYPE int64_t -#define SQLITE_UINT64_TYPE uint64_t - #include "NavDataCache.hxx" // std @@ -37,9 +32,23 @@ // boost #include -#include "sqlite3.h" + +#ifdef SYSTEM_SQLITE +// the standard sqlite3.h doesn't give a way to set SQLITE_UINT64_TYPE, +// so we have to hope sizeof(int64_t) matches sizeof(sqlite3_int64). +// otherwise things will go bad quickly. + #include "sqlite3.h" +#else +// to ensure compatability between sqlite3_int64 and PositionedID, +// force the type used by sqlite to match PositionedID explicitly +#define SQLITE_INT64_TYPE int64_t +#define SQLITE_UINT64_TYPE uint64_t + + #include "fg_sqlite3.h" +#endif // SimGear +#include #include #include #include @@ -49,9 +58,11 @@ #include #include
+#include
+#include
#include "markerbeacon.hxx" #include "navrecord.hxx" -#include +#include #include #include #include "fix.hxx" @@ -60,16 +71,22 @@ #include "PositionedOctree.hxx" #include #include +#include "poidb.hxx" +#include +#include +#include "CacheSchema.h" using std::string; -#define SG_NAVCACHE SG_GENERAL +#define SG_NAVCACHE SG_NAVAID //#define LAZY_OCTREE_UPDATES 1 namespace { -const int SCHEMA_VERSION = 4; - +const int MAX_RETRIES = 10; + +const int CACHE_SIZE_KBYTES= 32 * 1024; + // bind a std::string to a sqlite statement. The std::string must live the // entire duration of the statement execution - do not pass a temporary // std::string, or the compiler may delete it, freeing the C-string storage, @@ -161,7 +178,7 @@ public: SGTimeStamp st; st.stamp(); _cache->doRebuild(); - SG_LOG(SG_GENERAL, SG_INFO, "cache rebuild took:" << st.elapsedMSec() << "msec"); + SG_LOG(SG_NAVCACHE, SG_INFO, "cache rebuild took:" << st.elapsedMSec() << "msec"); SGGuard g(_lock); _isFinished = true; @@ -193,27 +210,31 @@ public: outer(o), db(NULL), path(p), + readOnly(false), cacheHits(0), - cacheMisses(0) + cacheMisses(0), + transactionLevel(0), + transactionAborted(false) { } ~NavDataCachePrivate() { - BOOST_FOREACH(sqlite3_stmt_ptr stmt, prepared) { - sqlite3_finalize(stmt); - } - prepared.clear(); - - sqlite3_close(db); + close(); } void init() { SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path); - sqlite3_open_v2(path.c_str(), &db, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); - + + readOnly = fgGetBool("/sim/fghome-readonly", false); + + int openFlags = readOnly ? SQLITE_OPEN_READONLY : + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + // see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055 + // for the UTF8 / path logic here + std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str()); + sqlite3_open_v2(pathUtf8.c_str(), &db, openFlags, NULL); sqlite3_stmt_ptr checkTables = prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'"); @@ -223,15 +244,15 @@ public: execSelect(checkTables); bool didCreate = false; - if (sqlite3_column_int(checkTables, 0) == 0) { + if (!readOnly && (sqlite3_column_int(checkTables, 0) == 0)) { SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables"); initTables(); didCreate = true; } readPropertyQuery = prepare("SELECT value FROM properties WHERE key=?"); - writePropertyQuery = prepare("INSERT OR REPLACE INTO properties " - "(key, value) VALUES (?,?)"); + writePropertyQuery = prepare("INSERT INTO properties (key, value) VALUES (?,?)"); + clearProperty = prepare("DELETE FROM properties WHERE key=?1"); if (didCreate) { writeIntProperty("schema-version", SCHEMA_VERSION); @@ -243,16 +264,30 @@ public: } } + // see http://www.sqlite.org/pragma.html#pragma_cache_size + // for the details, small cache would cause thrashing. + std::ostringstream q; + q << "PRAGMA cache_size=-" << CACHE_SIZE_KBYTES << ";"; + runSQL(q.str()); prepareQueries(); } + void close() + { + BOOST_FOREACH(sqlite3_stmt_ptr stmt, prepared) { + sqlite3_finalize(stmt); + } + prepared.clear(); + sqlite3_close(db); + } + void checkCacheFile() { SG_LOG(SG_NAVCACHE, SG_INFO, "running DB integrity check"); SGTimeStamp st; st.stamp(); - sqlite3_stmt_ptr stmt = prepare("PRAGMA integrity_check(1)"); + sqlite3_stmt_ptr stmt = prepare("PRAGMA quick_check(1)"); if (!execSelect(stmt)) { throw sg_exception("DB integrity check failed to run"); } @@ -265,6 +300,8 @@ public: SG_LOG(SG_NAVCACHE, SG_INFO, "NavDataCache integrity check took:" << st.elapsedMSec()); finalize(stmt); } + + bool isCachedFileModified(const SGPath& path, bool verbose); void callSqlite(int result, const string& sql) { @@ -290,7 +327,7 @@ public: try { execSelect(stmt); - } catch (sg_exception& e) { + } catch (sg_exception&) { sqlite3_finalize(stmt); throw; // re-throw } @@ -334,13 +371,29 @@ public: bool stepSelect(sqlite3_stmt_ptr stmt) { - int result = sqlite3_step(stmt); - if (result == SQLITE_ROW) { - return true; // at least one result row - } + int retries = 0; + int result; + while (retries < MAX_RETRIES) { + result = sqlite3_step(stmt); + if (result == SQLITE_ROW) { + return true; // at least one result row + } + + if (result == SQLITE_DONE) { + return false; // no result rows + } + + if (result != SQLITE_BUSY) { + break; + } + + SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on select, will retry:" << retries); + SGTimeStamp::sleepForMSec(++retries * 10); + } // of retry loop for DB locked - if (result == SQLITE_DONE) { - return false; // no result rows + if (retries >= MAX_RETRIES) { + SG_LOG(SG_NAVCACHE, SG_ALERT, "exceeded maximum number of SQLITE_BUSY retries"); + return false; } string errMsg; @@ -349,8 +402,8 @@ public: SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite API abuse"); } else { errMsg = sqlite3_errmsg(db); - SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg - << " while running:\n\t" << sqlite3_sql(stmt)); + SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " (" << result + << ") while running:\n\t" << sqlite3_sql(stmt)); } throw sg_exception("Sqlite error:" + errMsg, sqlite3_sql(stmt)); @@ -367,107 +420,38 @@ public: sqlite3_int64 execInsert(sqlite3_stmt_ptr stmt) { execSelect(stmt); - return sqlite3_last_insert_rowid(db); + sqlite3_int64 rowid = sqlite3_last_insert_rowid(db); + reset(stmt); + return rowid; } void execUpdate(sqlite3_stmt_ptr stmt) { execSelect(stmt); + reset(stmt); } - + void initTables() { - runSQL("CREATE TABLE properties (" - "key VARCHAR," - "value VARCHAR" - ")"); - - runSQL("CREATE TABLE stat_cache (" - "path VARCHAR unique," - "stamp INT" - ")"); - - runSQL("CREATE TABLE positioned (" - "type INT," - "ident VARCHAR collate nocase," - "name VARCHAR collate nocase," - "airport INT64," - "lon FLOAT," - "lat FLOAT," - "elev_m FLOAT," - "octree_node INT," - "cart_x FLOAT," - "cart_y FLOAT," - "cart_z FLOAT" - ")"); - - runSQL("CREATE INDEX pos_octree ON positioned(octree_node)"); - runSQL("CREATE INDEX pos_ident ON positioned(ident collate nocase)"); - runSQL("CREATE INDEX pos_name ON positioned(name collate nocase)"); - // allow efficient querying of 'all ATIS at this airport' or - // 'all towers at this airport' - runSQL("CREATE INDEX pos_apt_type ON positioned(airport, type)"); - - runSQL("CREATE TABLE airport (" - "has_metar BOOL" - ")" - ); - - runSQL("CREATE TABLE comm (" - "freq_khz INT," - "range_nm INT" - ")" - ); - - runSQL("CREATE INDEX comm_freq ON comm(freq_khz)"); - - runSQL("CREATE TABLE runway (" - "heading FLOAT," - "length_ft FLOAT," - "width_m FLOAT," - "surface INT," - "displaced_threshold FLOAT," - "stopway FLOAT," - "reciprocal INT64," - "ils INT64" - ")" - ); - - runSQL("CREATE TABLE navaid (" - "freq INT," - "range_nm INT," - "multiuse FLOAT," - "runway INT64," - "colocated INT64" - ")" - ); - - runSQL("CREATE INDEX navaid_freq ON navaid(freq)"); - - runSQL("CREATE TABLE octree (children INT)"); - - runSQL("CREATE TABLE airway (" - "ident VARCHAR collate nocase," - "network INT" // high-level or low-level - ")"); - - runSQL("CREATE INDEX airway_ident ON airway(ident)"); - - runSQL("CREATE TABLE airway_edge (" - "network INT," - "airway INT64," - "a INT64," - "b INT64" - ")"); - - runSQL("CREATE INDEX airway_edge_from ON airway_edge(a)"); + string_list commands = simgear::strutils::split(SCHEMA_SQL, ";"); + BOOST_FOREACH(std::string sql, commands) { + if (sql.empty()) { + continue; + } + + runSQL(sql); + } // of commands in scheme loop } void prepareQueries() { - clearProperty = prepare("DELETE FROM properties WHERE key=?1"); writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)"); + beginTransactionStmt = prepare("BEGIN"); + commitTransactionStmt = prepare("COMMIT"); + rollbackTransactionStmt = prepare("ROLLBACK"); + + #define POSITIONED_COLS "rowid, type, ident, name, airport, lon, lat, elev_m, octree_node" #define AND_TYPED "AND type>=?2 AND type <=?3" statCacheCheck = prepare("SELECT stamp FROM stat_cache WHERE path=?"); @@ -491,7 +475,7 @@ public: setRunwayReciprocal = prepare("UPDATE runway SET reciprocal=?2 WHERE rowid=?1"); setRunwayILS = prepare("UPDATE runway SET ils=?2 WHERE rowid=?1"); - updateRunwayThreshold = prepare("UPDATE runway SET heading=?2, displaced_threshold=?3, stopway=?4 WHERE rowid=?1"); + setNavaidColocated = prepare("UPDATE navaid SET colocated=?2 WHERE rowid=?1"); insertPositionedQuery = prepare("INSERT INTO positioned " "(type, ident, name, airport, lon, lat, elev_m, octree_node, " @@ -503,7 +487,6 @@ public: insertAirport = prepare("INSERT INTO airport (rowid, has_metar) VALUES (?, ?)"); insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); - updateILS = prepare("UPDATE navaid SET multiuse=?2 WHERE rowid=?1"); insertCommStation = prepare("INSERT INTO comm (rowid, freq_khz, range_nm)" " VALUES (?, ?, ?)"); @@ -512,6 +495,8 @@ public: " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"); runwayLengthFtQuery = prepare("SELECT length_ft FROM runway WHERE rowid=?1"); + removePOIQuery = prepare("DELETE FROM positioned WHERE type=?1 AND ident=?2"); + // query statement findClosestWithIdent = prepare("SELECT rowid FROM positioned WHERE ident=?1 " AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)"); @@ -528,6 +513,9 @@ public: findNavsByFreqNoPos = prepare("SELECT positioned.rowid FROM positioned, navaid WHERE " "positioned.rowid=navaid.rowid AND freq=?1 " AND_TYPED); + findNavaidForRunway = prepare("SELECT positioned.rowid FROM positioned, navaid WHERE " + "positioned.rowid=navaid.rowid AND runway=?1 AND type=?2"); + // for an octree branch, return the child octree nodes which exist, // described as a bit-mask getOctreeChildren = prepare("SELECT children FROM octree WHERE rowid=?1"); @@ -548,6 +536,11 @@ public: sqlite3_bind_int(searchAirports, 2, FGPositioned::AIRPORT); sqlite3_bind_int(searchAirports, 3, FGPositioned::SEAPORT); + getAllAirports = prepare("SELECT ident, name FROM positioned WHERE type>=?1 AND type <=?2"); + sqlite3_bind_int(getAllAirports, 1, FGPositioned::AIRPORT); + sqlite3_bind_int(getAllAirports, 2, FGPositioned::SEAPORT); + + getAirportItemByIdent = prepare("SELECT rowid FROM positioned WHERE airport=?1 AND ident=?2 AND type=?3"); findAirportRunway = prepare("SELECT airport, rowid FROM positioned WHERE ident=?2 AND type=?3 AND airport=" @@ -567,6 +560,7 @@ public: sqlite3_bind_int(findILS, 4, FGPositioned::ILS); sqlite3_bind_int(findILS, 5, FGPositioned::LOC); + // airways findAirway = prepare("SELECT rowid FROM airway WHERE network=?1 AND ident=?2"); insertAirway = prepare("INSERT INTO airway (ident, network) " "VALUES (?1, ?2)"); @@ -577,33 +571,77 @@ public: isPosInAirway = prepare("SELECT rowid FROM airway_edge WHERE network=?1 AND a=?2"); airwayEdgesFrom = prepare("SELECT airway, b FROM airway_edge WHERE network=?1 AND a=?2"); + + // parking / taxi-node graph + insertTaxiNode = prepare("INSERT INTO taxi_node (rowid, hold_type, on_runway, pushback) VALUES(?1, ?2, ?3, 0)"); + insertParkingPos = prepare("INSERT INTO parking (rowid, heading, radius, gate_type, airlines) " + "VALUES (?1, ?2, ?3, ?4, ?5)"); + setParkingPushBack = prepare("UPDATE parking SET pushback=?2 WHERE rowid=?1"); + + loadTaxiNodeStmt = prepare("SELECT hold_type, on_runway FROM taxi_node WHERE rowid=?1"); + loadParkingPos = prepare("SELECT heading, radius, gate_type, airlines, pushback FROM parking WHERE rowid=?1"); + taxiEdgesFrom = prepare("SELECT b FROM groundnet_edge WHERE a=?1"); + pushbackEdgesFrom = prepare("SELECT b FROM groundnet_edge, taxi_node WHERE " + "a=?1 AND groundnet_edge.b = taxi_node.rowid AND pushback=1"); + + insertTaxiEdge = prepare("INSERT INTO groundnet_edge (airport, a,b) VALUES(?1, ?2, ?3)"); + + markTaxiNodeAsPushback = prepare("UPDATE taxi_node SET pushback=1 WHERE rowid=?1"); + airportTaxiNodes = prepare("SELECT rowid FROM positioned WHERE (type=?2 OR type=?3) AND airport=?1"); + sqlite3_bind_int(airportTaxiNodes, 2, FGPositioned::PARKING); + sqlite3_bind_int(airportTaxiNodes, 3, FGPositioned::TAXI_NODE); + + airportPushbackNodes = prepare("SELECT positioned.rowid FROM positioned, taxi_node WHERE "\ + "airport=?1 AND positioned.rowid=taxi_node.rowid AND pushback=1 " + "AND (type=?2 OR type=?3)"); + sqlite3_bind_int(airportPushbackNodes, 2, FGPositioned::PARKING); + sqlite3_bind_int(airportPushbackNodes, 3, FGPositioned::TAXI_NODE); + + findNearestTaxiNode = prepare("SELECT positioned.rowid FROM positioned, taxi_node WHERE " + "positioned.rowid = taxi_node.rowid AND airport=?1 " + "ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?2, ?3, ?4) " + "LIMIT 1"); + + findNearestRunwayTaxiNode = prepare("SELECT positioned.rowid FROM positioned, taxi_node WHERE " + "positioned.rowid = taxi_node.rowid AND airport=?1 " + "AND on_runway=1 " + "ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?2, ?3, ?4) "); + + findAirportParking = prepare("SELECT positioned.rowid FROM positioned, parking WHERE " + "airport=?1 AND type=?4 AND " + "radius >= ?2 AND gate_type = ?3 AND " + "parking.rowid=positioned.rowid"); + sqlite3_bind_int(findAirportParking, 4, FGPositioned::PARKING); } void writeIntProperty(const string& key, int value) { + sqlite_bind_stdstring(clearProperty, 1, key); + execUpdate(clearProperty); + sqlite_bind_stdstring(writePropertyQuery, 1, key); sqlite3_bind_int(writePropertyQuery, 2, value); - execSelect(writePropertyQuery); + execUpdate(writePropertyQuery); } - FGPositioned* loadFromStmt(sqlite3_stmt_ptr query); + FGPositioned* loadById(sqlite_int64 rowId, sqlite3_int64& aptId); FGAirport* loadAirport(sqlite_int64 rowId, FGPositioned::Type ty, const string& id, const string& name, const SGGeod& pos) { - reset(loadAirportStmt); sqlite3_bind_int64(loadAirportStmt, 1, rowId); execSelect1(loadAirportStmt); - bool hasMetar = sqlite3_column_int(loadAirportStmt, 0); + bool hasMetar = (sqlite3_column_int(loadAirportStmt, 0) > 0); + reset(loadAirportStmt); + return new FGAirport(rowId, id, pos, name, hasMetar, ty); } FGRunwayBase* loadRunway(sqlite3_int64 rowId, FGPositioned::Type ty, const string& id, const SGGeod& pos, PositionedID apt) { - reset(loadRunwayStmt); sqlite3_bind_int(loadRunwayStmt, 1, rowId); execSelect1(loadRunwayStmt); @@ -613,14 +651,18 @@ public: int surface = sqlite3_column_int(loadRunwayStmt, 3); if (ty == FGPositioned::TAXIWAY) { + reset(loadRunwayStmt); return new FGTaxiway(rowId, id, pos, heading, lengthM, widthM, surface); + } else if (ty == FGPositioned::HELIPAD) { + reset(loadRunwayStmt); + return new FGHelipad(rowId, apt, id, pos, heading, lengthM, widthM, surface); } else { double displacedThreshold = sqlite3_column_double(loadRunwayStmt, 4); double stopway = sqlite3_column_double(loadRunwayStmt, 5); PositionedID reciprocal = sqlite3_column_int64(loadRunwayStmt, 6); PositionedID ils = sqlite3_column_int64(loadRunwayStmt, 7); FGRunway* r = new FGRunway(rowId, apt, id, pos, heading, lengthM, widthM, - displacedThreshold, stopway, surface, false); + displacedThreshold, stopway, surface); if (reciprocal > 0) { r->setReciprocalRunway(reciprocal); @@ -630,6 +672,7 @@ public: r->setILS(ils); } + reset(loadRunwayStmt); return r; } } @@ -639,12 +682,12 @@ public: const SGGeod& pos, PositionedID airport) { - reset(loadCommStation); sqlite3_bind_int64(loadCommStation, 1, rowId); execSelect1(loadCommStation); int range = sqlite3_column_int(loadCommStation, 0); int freqKhz = sqlite3_column_int(loadCommStation, 1); + reset(loadCommStation); CommStation* c = new CommStation(rowId, name, ty, pos, freqKhz, range); c->setAirport(airport); @@ -655,7 +698,6 @@ public: FGPositioned::Type ty, const string& id, const string& name, const SGGeod& pos) { - reset(loadNavaid); sqlite3_bind_int64(loadNavaid, 1, rowId); execSelect1(loadNavaid); @@ -664,15 +706,57 @@ public: if ((ty == FGPositioned::OM) || (ty == FGPositioned::IM) || (ty == FGPositioned::MM)) { + reset(loadNavaid); return new FGMarkerBeaconRecord(rowId, ty, runway, pos); } int rangeNm = sqlite3_column_int(loadNavaid, 0), - freq = sqlite3_column_int(loadNavaid, 1); + freq = sqlite3_column_int(loadNavaid, 1); double mulituse = sqlite3_column_double(loadNavaid, 2); - //sqlite3_int64 colocated = sqlite3_column_int64(loadNavaid, 4); + PositionedID colocated = sqlite3_column_int64(loadNavaid, 4); + reset(loadNavaid); + + FGNavRecord* n = + (ty == FGPositioned::MOBILE_TACAN) + ? new FGMobileNavRecord + (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway) + : new FGNavRecord + (rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway); + + if (colocated) + n->setColocatedDME(colocated); + + return n; + } + + FGPositioned* loadParking(sqlite3_int64 rowId, + const string& name, const SGGeod& pos, + PositionedID airport) + { + sqlite3_bind_int64(loadParkingPos, 1, rowId); + execSelect1(loadParkingPos); + + double heading = sqlite3_column_double(loadParkingPos, 0); + int radius = sqlite3_column_int(loadParkingPos, 1); + string aircraftType((char*) sqlite3_column_text(loadParkingPos, 2)); + string airlines((char*) sqlite3_column_text(loadParkingPos, 3)); + PositionedID pushBack = sqlite3_column_int64(loadParkingPos, 4); + reset(loadParkingPos); + + return new FGParking(rowId, pos, heading, radius, name, aircraftType, airlines, pushBack); + } + + FGPositioned* loadTaxiNode(sqlite3_int64 rowId, const SGGeod& pos, + PositionedID airport) + { + sqlite3_bind_int64(loadTaxiNodeStmt, 1, rowId); + execSelect1(loadTaxiNodeStmt); - return new FGNavRecord(rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway); + int hold_type = sqlite3_column_int(loadTaxiNodeStmt, 0); + bool onRunway = sqlite3_column_int(loadTaxiNodeStmt, 1); + reset(loadTaxiNodeStmt); + + return new FGTaxiNode(rowId, pos, onRunway, hold_type); } PositionedID insertPositioned(FGPositioned::Type ty, const string& ident, @@ -681,7 +765,6 @@ public: { SGVec3d cartPos(SGVec3d::fromGeod(pos)); - reset(insertPositionedQuery); sqlite3_bind_int(insertPositionedQuery, 1, ty); sqlite_bind_stdstring(insertPositionedQuery, 2, ident); sqlite_bind_stdstring(insertPositionedQuery, 3, name); @@ -702,21 +785,21 @@ public: sqlite3_bind_double(insertPositionedQuery, 10, cartPos.y()); sqlite3_bind_double(insertPositionedQuery, 11, cartPos.z()); - PositionedID r = execInsert(insertPositionedQuery); + PositionedID r = execInsert(insertPositionedQuery); return r; } - FGPositioned::List findAllByString(const string& s, const string& column, + FGPositionedList findAllByString(const string& s, const string& column, FGPositioned::Filter* filter, bool exact) { string query = s; - if (!exact) query += "*"; + if (!exact) query += "%"; // build up SQL query text string matchTerm = exact ? "=?1" : " LIKE ?1"; string sql = "SELECT rowid FROM positioned WHERE " + column + matchTerm; if (filter) { - sql += AND_TYPED; + sql += " " AND_TYPED; } // find or prepare a suitable statement frrm the SQL @@ -726,14 +809,13 @@ public: findByStringDict[sql] = stmt; } - reset(stmt); sqlite_bind_stdstring(stmt, 1, query); if (filter) { sqlite3_bind_int(stmt, 2, filter->minType()); sqlite3_bind_int(stmt, 3, filter->maxType()); } - FGPositioned::List result; + FGPositionedList result; // run the prepared SQL while (stepSelect(stmt)) { @@ -745,6 +827,7 @@ public: result.push_back(pos); } + reset(stmt); return result; } @@ -754,21 +837,22 @@ public: while (stepSelect(query)) { result.push_back(sqlite3_column_int64(query, 0)); } + reset(query); return result; } double runwayLengthFt(PositionedID rwy) { - reset(runwayLengthFtQuery); sqlite3_bind_int64(runwayLengthFtQuery, 1, rwy); execSelect1(runwayLengthFtQuery); - return sqlite3_column_double(runwayLengthFtQuery, 0); + double length = sqlite3_column_double(runwayLengthFtQuery, 0); + reset(runwayLengthFtQuery); + return length; } void flushDeferredOctreeUpdates() { BOOST_FOREACH(Octree::Branch* nd, deferredOctreeUpdates) { - reset(updateOctreeChildren); sqlite3_bind_int64(updateOctreeChildren, 1, nd->guid()); sqlite3_bind_int(updateOctreeChildren, 2, nd->childMask()); execUpdate(updateOctreeChildren); @@ -776,18 +860,34 @@ public: deferredOctreeUpdates.clear(); } + + void removePositionedWithIdent(FGPositioned::Type ty, const std::string& aIdent) + { + sqlite3_bind_int(removePOIQuery, 1, ty); + sqlite_bind_stdstring(removePOIQuery, 2, aIdent); + execUpdate(removePOIQuery); + reset(removePOIQuery); + } NavDataCache* outer; sqlite3* db; SGPath path; - + bool readOnly; + /// the actual cache of ID -> instances. This holds an owning reference, /// so once items are in the cache they will never be deleted until /// the cache drops its reference PositionedCache cache; unsigned int cacheHits, cacheMisses; - - SGPath aptDatPath, metarDatPath, navDatPath, fixDatPath, + + /** + * record the levels of open transaction objects we have + */ + unsigned int transactionLevel; + bool transactionAborted; + sqlite3_stmt_ptr beginTransactionStmt, commitTransactionStmt, rollbackTransactionStmt; + + SGPath aptDatPath, metarDatPath, navDatPath, fixDatPath, poiDatPath, carrierDatPath, airwayDatPath; sqlite3_stmt_ptr readPropertyQuery, writePropertyQuery, @@ -798,17 +898,18 @@ public: sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway, insertCommStation, insertNavaid; - sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, - setAirportPos, updateRunwayThreshold, updateILS; + sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, setNavaidColocated, + setAirportPos; + sqlite3_stmt_ptr removePOIQuery; sqlite3_stmt_ptr findClosestWithIdent; // octree (spatial index) related queries sqlite3_stmt_ptr getOctreeChildren, insertOctree, updateOctreeChildren, getOctreeLeafChildren; - sqlite3_stmt_ptr searchAirports; + sqlite3_stmt_ptr searchAirports, getAllAirports; sqlite3_stmt_ptr findCommByFreq, findNavsByFreq, - findNavsByFreqNoPos; + findNavsByFreqNoPos, findNavaidForRunway; sqlite3_stmt_ptr getAirportItems, getAirportItemByIdent; sqlite3_stmt_ptr findAirportRunway, findILS; @@ -819,6 +920,12 @@ public: sqlite3_stmt_ptr findAirway, insertAirwayEdge, isPosInAirway, airwayEdgesFrom, insertAirway; +// groundnet (parking, taxi node graph) + sqlite3_stmt_ptr loadTaxiNodeStmt, loadParkingPos, insertTaxiNode, insertParkingPos; + sqlite3_stmt_ptr taxiEdgesFrom, pushbackEdgesFrom, insertTaxiEdge, markTaxiNodeAsPushback, + airportTaxiNodes, airportPushbackNodes, findNearestTaxiNode, findAirportParking, + setParkingPushBack, findNearestRunwayTaxiNode; + // since there's many permutations of ident/name queries, we create // them programtically, but cache the exact query by its raw SQL once // used. @@ -834,21 +941,28 @@ public: std::auto_ptr rebuilder; }; - ////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// -FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr query) +FGPositioned* NavDataCache::NavDataCachePrivate::loadById(sqlite3_int64 rowid, + sqlite3_int64& aptId) { - execSelect1(query); - sqlite3_int64 rowid = sqlite3_column_int64(query, 0); - FGPositioned::Type ty = (FGPositioned::Type) sqlite3_column_int(query, 1); - string ident = (char*) sqlite3_column_text(query, 2); - string name = (char*) sqlite3_column_text(query, 3); - sqlite3_int64 aptId = sqlite3_column_int64(query, 4); - double lon = sqlite3_column_double(query, 5); - double lat = sqlite3_column_double(query, 6); - double elev = sqlite3_column_double(query, 7); + sqlite3_bind_int64(loadPositioned, 1, rowid); + execSelect1(loadPositioned); + + assert(rowid == sqlite3_column_int64(loadPositioned, 0)); + FGPositioned::Type ty = (FGPositioned::Type) sqlite3_column_int(loadPositioned, 1); + + PositionedID prowid = static_cast(rowid); + string ident = (char*) sqlite3_column_text(loadPositioned, 2); + string name = (char*) sqlite3_column_text(loadPositioned, 3); + aptId = sqlite3_column_int64(loadPositioned, 4); + double lon = sqlite3_column_double(loadPositioned, 5); + double lat = sqlite3_column_double(loadPositioned, 6); + double elev = sqlite3_column_double(loadPositioned, 7); SGGeod pos = SGGeod::fromDegM(lon, lat, elev); + + reset(loadPositioned); switch (ty) { case FGPositioned::AIRPORT: @@ -857,9 +971,10 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q return loadAirport(rowid, ty, ident, name, pos); case FGPositioned::TOWER: - return new AirportTower(rowid, aptId, ident, pos); + return new AirportTower(prowid, aptId, ident, pos); case FGPositioned::RUNWAY: + case FGPositioned::HELIPAD: case FGPositioned::TAXIWAY: return loadRunway(rowid, ty, ident, pos, aptId); @@ -874,26 +989,18 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q case FGPositioned::DME: case FGPositioned::TACAN: case FGPositioned::MOBILE_TACAN: - { - if (aptId > 0) { - FGAirport* apt = (FGAirport*) outer->loadById(aptId); - if (apt->validateILSData()) { - SG_LOG(SG_NAVCACHE, SG_INFO, "re-loaded ILS data for " << apt->ident()); - // queried data above is probably invalid, force us to go around again - // (the next time through, validateILSData will return false) - return outer->loadById(rowid); - } - } - return loadNav(rowid, ty, ident, name, pos); - } case FGPositioned::FIX: return new FGFix(rowid, ident, pos); case FGPositioned::WAYPOINT: + case FGPositioned::COUNTRY: + case FGPositioned::CITY: + case FGPositioned::TOWN: + case FGPositioned::VILLAGE: { - FGPositioned* wpt = new FGPositioned(rowid, FGPositioned::WAYPOINT, ident, pos); + FGPositioned* wpt = new FGPositioned(rowid, ty, ident, pos); return wpt; } @@ -907,11 +1014,47 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadFromStmt(sqlite3_stmt_ptr q case FGPositioned::FREQ_UNICOM: return loadComm(rowid, ty, ident, name, pos, aptId); + case FGPositioned::TAXI_NODE: + return loadTaxiNode(rowid, pos, aptId); + + case FGPositioned::PARKING: + return loadParking(rowid, ident, pos, aptId); + default: return NULL; } } - + +bool NavDataCache::NavDataCachePrivate::isCachedFileModified(const SGPath& path, bool verbose) +{ + if (!path.exists()) { + throw sg_io_exception("isCachedFileModified: Missing file:" + path.str()); + } + + sqlite_bind_temp_stdstring(statCacheCheck, 1, path.str()); + bool isModified = true; + sgDebugPriority logLevel = verbose ? SG_WARN : SG_DEBUG; + if (execSelect(statCacheCheck)) { + time_t modtime = sqlite3_column_int64(statCacheCheck, 0); + time_t delta = std::labs(modtime - path.modTime()); + if (delta != 0) + { + SG_LOG(SG_NAVCACHE, logLevel, "NavCache: rebuild required for " << path << + ". Timestamps: " << modtime << " != " << path.modTime()); + } + else + { + SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: no rebuild required for " << path); + } + + isModified = (delta != 0); + } else { + SG_LOG(SG_NAVCACHE, logLevel, "NavCache: initial build required for " << path); + } + + reset(statCacheCheck); + return isModified; +} static NavDataCache* static_instance = NULL; @@ -919,7 +1062,16 @@ NavDataCache::NavDataCache() { const int MAX_TRIES = 3; SGPath homePath(globals->get_fg_home()); - homePath.append("navdata.cache"); + + std::ostringstream os; + string_list versionParts = simgear::strutils::split(VERSION, "."); + if (versionParts.size() < 2) { + os << "navdata.cache"; + } else { + os << "navdata_" << versionParts[0] << "_" << versionParts[1] << ".cache"; + } + + homePath.append(os.str()); for (int t=0; t < MAX_TRIES; ++t) { try { @@ -932,7 +1084,11 @@ NavDataCache::NavDataCache() SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what() << " (attempt " << t << ")"); d.reset(); - homePath.remove(); + + // only wipe the existing if not readonly + if (!fgGetBool("/sim/fghome-readonly", false)) { + homePath.remove(); + } } } // of retry loop @@ -952,6 +1108,9 @@ NavDataCache::NavDataCache() d->fixDatPath = SGPath(globals->get_fg_root()); d->fixDatPath.append("Navaids/fix.dat.gz"); + + d->poiDatPath = SGPath(globals->get_fg_root()); + d->poiDatPath.append("Navaids/poi.dat.gz"); d->carrierDatPath = SGPath(globals->get_fg_root()); d->carrierDatPath.append("Navaids/carrier_nav.dat.gz"); @@ -964,7 +1123,6 @@ NavDataCache::~NavDataCache() { assert(static_instance == this); static_instance = NULL; - SG_LOG(SG_NAVCACHE, SG_INFO, "closing the navcache"); d.reset(); } @@ -979,19 +1137,49 @@ NavDataCache* NavDataCache::instance() bool NavDataCache::isRebuildRequired() { - if (isCachedFileModified(d->aptDatPath) || - isCachedFileModified(d->metarDatPath) || - isCachedFileModified(d->navDatPath) || - isCachedFileModified(d->fixDatPath) || - isCachedFileModified(d->airwayDatPath)) + if (d->readOnly) { + return false; + } + + if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache"); + return true; + } + + if (d->isCachedFileModified(d->aptDatPath, true) || + d->isCachedFileModified(d->metarDatPath, true) || + d->isCachedFileModified(d->navDatPath, true) || + d->isCachedFileModified(d->fixDatPath, true) || + d->isCachedFileModified(d->carrierDatPath, true) || +// since POI loading is disabled on Windows, don't check for it +// this caused: https://code.google.com/p/flightgear-bugs/issues/detail?id=1227 +#ifndef SG_WINDOWS + d->isCachedFileModified(d->poiDatPath, true) || +#endif + d->isCachedFileModified(d->airwayDatPath, true)) { - SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: rebuild required"); + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: main cache rebuild required"); return true; } - SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no rebuild required"); + dropGroundnetsIfRequired(); + + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required"); return false; } + +bool NavDataCache::dropGroundnetsIfRequired() +{ + string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); + if (readStringProperty("scenery_paths") != sceneryPaths) { + SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: scenery paths changed, dropping ground nets"); + dropAllGroundnets(); + writeStringProperty("scenery_paths", sceneryPaths); + return true; + } + + return false; +} bool NavDataCache::rebuild() { @@ -1011,92 +1199,124 @@ bool NavDataCache::rebuild() void NavDataCache::doRebuild() { try { - d->runSQL("BEGIN"); - d->runSQL("DELETE FROM positioned"); - d->runSQL("DELETE FROM airport"); - d->runSQL("DELETE FROM runway"); - d->runSQL("DELETE FROM navaid"); - d->runSQL("DELETE FROM comm"); - d->runSQL("DELETE FROM octree"); - d->runSQL("DELETE FROM airway"); - d->runSQL("DELETE FROM airway_edge"); - - // initialise the root octree node - d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)"); + d->close(); // completely close the sqlite object + d->path.remove(); // remove the file on disk + d->init(); // start again from scratch + // initialise the root octree node + d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)"); + SGTimeStamp st; - st.stamp(); - - airportDBLoad(d->aptDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "apt.dat load took:" << st.elapsedMSec()); - - metarDataLoad(d->metarDatPath); - stampCacheFile(d->aptDatPath); - stampCacheFile(d->metarDatPath); - - st.stamp(); - loadFixes(d->fixDatPath); - stampCacheFile(d->fixDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "fix.dat load took:" << st.elapsedMSec()); - - st.stamp(); - navDBInit(d->navDatPath); - stampCacheFile(d->navDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "nav.dat load took:" << st.elapsedMSec()); - - loadCarrierNav(d->carrierDatPath); - stampCacheFile(d->carrierDatPath); - - st.stamp(); - Airway::load(d->airwayDatPath); - stampCacheFile(d->airwayDatPath); - SG_LOG(SG_NAVCACHE, SG_INFO, "awy.dat load took:" << st.elapsedMSec()); - - d->flushDeferredOctreeUpdates(); - - d->runSQL("COMMIT"); + { + Transaction txn(this); + + st.stamp(); + airportDBLoad(d->aptDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "apt.dat load took:" << st.elapsedMSec()); + + metarDataLoad(d->metarDatPath); + stampCacheFile(d->aptDatPath); + stampCacheFile(d->metarDatPath); + + st.stamp(); + loadFixes(d->fixDatPath); + stampCacheFile(d->fixDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "fix.dat load took:" << st.elapsedMSec()); + + st.stamp(); + navDBInit(d->navDatPath); + stampCacheFile(d->navDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "nav.dat load took:" << st.elapsedMSec()); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "stage 1 commit took:" << st.elapsedMSec()); + } + +#ifdef SG_WINDOWS + SG_LOG(SG_NAVCACHE, SG_ALERT, "SKIPPING POI load on Windows"); +#else + { + Transaction txn(this); + + st.stamp(); + poiDBInit(d->poiDatPath); + stampCacheFile(d->poiDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "poi.dat load took:" << st.elapsedMSec()); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "POI commit took:" << st.elapsedMSec()); + } +#endif + + { + Transaction txn(this); + loadCarrierNav(d->carrierDatPath); + stampCacheFile(d->carrierDatPath); + + st.stamp(); + Airway::load(d->airwayDatPath); + stampCacheFile(d->airwayDatPath); + SG_LOG(SG_NAVCACHE, SG_INFO, "awy.dat load took:" << st.elapsedMSec()); + + d->flushDeferredOctreeUpdates(); + + string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";"); + writeStringProperty("scenery_paths", sceneryPaths); + + st.stamp(); + txn.commit(); + SG_LOG(SG_NAVCACHE, SG_INFO, "final commit took:" << st.elapsedMSec()); + + } + } catch (sg_exception& e) { SG_LOG(SG_NAVCACHE, SG_ALERT, "caught exception rebuilding navCache:" << e.what()); - // abandon the DB transation completely - d->runSQL("ROLLBACK"); } } int NavDataCache::readIntProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + int result = 0; if (d->execSelect(d->readPropertyQuery)) { - return sqlite3_column_int(d->readPropertyQuery, 0); + result = sqlite3_column_int(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readIntProperty: unknown:" << key); - return 0; // no such property } + + d->reset(d->readPropertyQuery); + return result; } double NavDataCache::readDoubleProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + double result = 0.0; if (d->execSelect(d->readPropertyQuery)) { - return sqlite3_column_double(d->readPropertyQuery, 0); + result = sqlite3_column_double(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readDoubleProperty: unknown:" << key); - return 0.0; // no such property } + + d->reset(d->readPropertyQuery); + return result; } string NavDataCache::readStringProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); + string result; if (d->execSelect(d->readPropertyQuery)) { - return (char*) sqlite3_column_text(d->readPropertyQuery, 0); + result = (char*) sqlite3_column_text(d->readPropertyQuery, 0); } else { SG_LOG(SG_NAVCACHE, SG_WARN, "readStringProperty: unknown:" << key); - return string(); // no such property } + + d->reset(d->readPropertyQuery); + return result; } void NavDataCache::writeIntProperty(const string& key, int value) @@ -1106,40 +1326,42 @@ void NavDataCache::writeIntProperty(const string& key, int value) void NavDataCache::writeStringProperty(const string& key, const string& value) { - d->reset(d->writePropertyQuery); + sqlite_bind_stdstring(d->clearProperty, 1, key); + d->execUpdate(d->clearProperty); + sqlite_bind_stdstring(d->writePropertyQuery, 1, key); sqlite_bind_stdstring(d->writePropertyQuery, 2, value); - d->execSelect(d->writePropertyQuery); + d->execUpdate(d->writePropertyQuery); } void NavDataCache::writeDoubleProperty(const string& key, const double& value) { - d->reset(d->writePropertyQuery); + sqlite_bind_stdstring(d->clearProperty, 1, key); + d->execUpdate(d->clearProperty); + sqlite_bind_stdstring(d->writePropertyQuery, 1, key); sqlite3_bind_double(d->writePropertyQuery, 2, value); - d->execSelect(d->writePropertyQuery); + d->execUpdate(d->writePropertyQuery); } string_list NavDataCache::readStringListProperty(const string& key) { - d->reset(d->readPropertyQuery); sqlite_bind_stdstring(d->readPropertyQuery, 1, key); string_list result; while (d->stepSelect(d->readPropertyQuery)) { result.push_back((char*) sqlite3_column_text(d->readPropertyQuery, 0)); } + d->reset(d->readPropertyQuery); return result; } void NavDataCache::writeStringListProperty(const string& key, const string_list& values) { - d->reset(d->clearProperty); sqlite_bind_stdstring(d->clearProperty, 1, key); d->execUpdate(d->clearProperty); BOOST_FOREACH(string value, values) { - d->reset(d->writePropertyMulti); sqlite_bind_stdstring(d->writePropertyMulti, 1, key); sqlite_bind_stdstring(d->writePropertyMulti, 2, value); d->execInsert(d->writePropertyMulti); @@ -1148,30 +1370,86 @@ void NavDataCache::writeStringListProperty(const string& key, const string_list& bool NavDataCache::isCachedFileModified(const SGPath& path) const { - if (!path.exists()) { - throw sg_io_exception("isCachedFileModified: Missing file:" + path.str()); - } - - d->reset(d->statCacheCheck); - sqlite_bind_temp_stdstring(d->statCacheCheck, 1, path.str()); - if (d->execSelect(d->statCacheCheck)) { - time_t modtime = sqlite3_column_int64(d->statCacheCheck, 0); - return (modtime != path.modTime()); - } else { - return true; - } + return d->isCachedFileModified(path, false); } void NavDataCache::stampCacheFile(const SGPath& path) { - d->reset(d->stampFileCache); sqlite_bind_temp_stdstring(d->stampFileCache, 1, path.str()); sqlite3_bind_int64(d->stampFileCache, 2, path.modTime()); d->execInsert(d->stampFileCache); } +void NavDataCache::beginTransaction() +{ + if (d->transactionLevel == 0) { + d->transactionAborted = false; + d->stepSelect(d->beginTransactionStmt); + sqlite3_reset(d->beginTransactionStmt); + } + + ++d->transactionLevel; +} + +void NavDataCache::commitTransaction() +{ + assert(d->transactionLevel > 0); + if (--d->transactionLevel == 0) { + // if a nested transaction aborted, we might end up here, but must + // still abort the entire transaction. That's bad, but safer than + // committing. + sqlite3_stmt_ptr q = d->transactionAborted ? d->rollbackTransactionStmt : d->commitTransactionStmt; + + int retries = 0; + int result; + while (retries < MAX_RETRIES) { + result = sqlite3_step(q); + if (result == SQLITE_DONE) { + break; + } + + // see http://www.sqlite.org/c3ref/get_autocommit.html for a hint + // what's going on here: autocommit in inactive inside BEGIN, so if + // it's active, the DB was rolled-back + if (sqlite3_get_autocommit(d->db)) { + SG_LOG(SG_NAVCACHE, SG_ALERT, "commit: was rolled back!" << retries); + d->transactionAborted = true; + break; + } + + if (result != SQLITE_BUSY) { + break; + } + + SGTimeStamp::sleepForMSec(++retries * 10); + SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache contention on commit, will retry:" << retries); + } // of retry loop for DB busy + + string errMsg; + if (result != SQLITE_DONE) { + errMsg = sqlite3_errmsg(d->db); + SG_LOG(SG_NAVCACHE, SG_ALERT, "Sqlite error:" << errMsg << " for " << result + << " while running:\n\t" << sqlite3_sql(q)); + } + + sqlite3_reset(q); + } +} + +void NavDataCache::abortTransaction() +{ + SG_LOG(SG_NAVCACHE, SG_WARN, "NavCache: aborting transaction"); + + assert(d->transactionLevel > 0); + if (--d->transactionLevel == 0) { + d->stepSelect(d->rollbackTransactionStmt); + sqlite3_reset(d->rollbackTransactionStmt); + } + + d->transactionAborted = true; +} -FGPositioned* NavDataCache::loadById(PositionedID rowid) +FGPositionedRef NavDataCache::loadById(PositionedID rowid) { if (rowid == 0) { return NULL; @@ -1183,12 +1461,16 @@ FGPositioned* NavDataCache::loadById(PositionedID rowid) return it->second; // cache it } - d->reset(d->loadPositioned); - sqlite3_bind_int64(d->loadPositioned, 1, rowid); - FGPositioned* pos = d->loadFromStmt(d->loadPositioned); - + sqlite3_int64 aptId; + FGPositioned* pos = d->loadById(rowid, aptId); d->cache.insert(it, PositionedCache::value_type(rowid, pos)); d->cacheMisses++; + + // when we loaded an ILS, we must apply per-airport changes + if ((pos->type() == FGPositioned::ILS) && (aptId > 0)) { + FGAirport* apt = FGPositioned::loadById(aptId); + apt->validateILSData(); + } return pos; } @@ -1203,7 +1485,6 @@ PositionedID NavDataCache::insertAirport(FGPositioned::Type ty, const string& id 0 /* airport */, false /* spatial index */); - d->reset(d->insertAirport); sqlite3_bind_int64(d->insertAirport, 1, rowId); d->execInsert(d->insertAirport); @@ -1212,14 +1493,26 @@ PositionedID NavDataCache::insertAirport(FGPositioned::Type ty, const string& id void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos) { + if (d->cache.find(item) != d->cache.end()) { + SG_LOG(SG_NAVCACHE, SG_DEBUG, "updating position of an item in the cache"); + d->cache[item]->modifyPosition(pos); + } + SGVec3d cartPos(SGVec3d::fromGeod(pos)); - d->reset(d->setAirportPos); sqlite3_bind_int(d->setAirportPos, 1, item); sqlite3_bind_double(d->setAirportPos, 2, pos.getLongitudeDeg()); sqlite3_bind_double(d->setAirportPos, 3, pos.getLatitudeDeg()); sqlite3_bind_double(d->setAirportPos, 4, pos.getElevationM()); +// bug 905; the octree leaf may change here, but the leaf may already be +// loaded, and caching its children. (Either the old or new leaf!). Worse, +// we may be called here as a result of loading one of those leaf's children. +// instead of dealing with all those possibilites, such as modifying +// the in-memory leaf's STL child container, we simply leave the runtime +// structures alone. This is fine providing items do no move very far, since +// all the spatial searches ultimately use the items' real cartesian position, +// which was updated above. Octree::Leaf* octreeLeaf = Octree::global_spatialOctree->findLeafForPos(cartPos); sqlite3_bind_int64(d->setAirportPos, 5, octreeLeaf->guid()); @@ -1245,11 +1538,10 @@ NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident, { // only runways are spatially indexed; don't bother indexing taxiways // or pavements - bool spatialIndex = (ty == FGPositioned::RUNWAY); + bool spatialIndex = ( ty == FGPositioned::RUNWAY || ty == FGPositioned::HELIPAD); sqlite3_int64 rowId = d->insertPositioned(ty, cleanRunwayNo(ident), "", pos, apt, spatialIndex); - d->reset(d->insertRunway); sqlite3_bind_int64(d->insertRunway, 1, rowId); sqlite3_bind_double(d->insertRunway, 2, heading); sqlite3_bind_double(d->insertRunway, 3, length); @@ -1263,13 +1555,11 @@ NavDataCache::insertRunway(FGPositioned::Type ty, const string& ident, void NavDataCache::setRunwayReciprocal(PositionedID runway, PositionedID recip) { - d->reset(d->setRunwayReciprocal); sqlite3_bind_int64(d->setRunwayReciprocal, 1, runway); sqlite3_bind_int64(d->setRunwayReciprocal, 2, recip); d->execUpdate(d->setRunwayReciprocal); // and the opposite direction too! - d->reset(d->setRunwayReciprocal); sqlite3_bind_int64(d->setRunwayReciprocal, 2, runway); sqlite3_bind_int64(d->setRunwayReciprocal, 1, recip); d->execUpdate(d->setRunwayReciprocal); @@ -1277,32 +1567,15 @@ void NavDataCache::setRunwayReciprocal(PositionedID runway, PositionedID recip) void NavDataCache::setRunwayILS(PositionedID runway, PositionedID ils) { - d->reset(d->setRunwayILS); sqlite3_bind_int64(d->setRunwayILS, 1, runway); sqlite3_bind_int64(d->setRunwayILS, 2, ils); d->execUpdate(d->setRunwayILS); -} - -void NavDataCache::updateRunwayThreshold(PositionedID runwayID, const SGGeod &aThreshold, - double aHeading, double aDisplacedThreshold, - double aStopway) -{ -// update the runway information - d->reset(d->updateRunwayThreshold); - sqlite3_bind_int64(d->updateRunwayThreshold, 1, runwayID); - sqlite3_bind_double(d->updateRunwayThreshold, 2, aHeading); - sqlite3_bind_double(d->updateRunwayThreshold, 3, aDisplacedThreshold); - sqlite3_bind_double(d->updateRunwayThreshold, 4, aStopway); - d->execUpdate(d->updateRunwayThreshold); - -// compute the new runway center, based on the threshold lat/lon and length, - double offsetFt = (0.5 * d->runwayLengthFt(runwayID)); - SGGeod newCenter; - double dummy; - SGGeodesy::direct(aThreshold, aHeading, offsetFt * SG_FEET_TO_METER, newCenter, dummy); -// now update the positional data - updatePosition(runwayID, newCenter); + // and the in-memory one + if (d->cache.find(runway) != d->cache.end()) { + FGRunway* instance = (FGRunway*) d->cache[runway].ptr(); + instance->setILS(ils); + } } PositionedID @@ -1318,22 +1591,27 @@ NavDataCache::insertNavaid(FGPositioned::Type ty, const string& ident, sqlite3_int64 rowId = d->insertPositioned(ty, ident, name, pos, apt, spatialIndex); - d->reset(d->insertNavaid); sqlite3_bind_int64(d->insertNavaid, 1, rowId); sqlite3_bind_int(d->insertNavaid, 2, freq); sqlite3_bind_int(d->insertNavaid, 3, range); sqlite3_bind_double(d->insertNavaid, 4, multiuse); sqlite3_bind_int64(d->insertNavaid, 5, runway); + sqlite3_bind_int64(d->insertNavaid, 6, 0); return d->execInsert(d->insertNavaid); } -void NavDataCache::updateILS(PositionedID ils, const SGGeod& newPos, double aHdg) +void NavDataCache::setNavaidColocated(PositionedID navaid, PositionedID colocatedDME) { - d->reset(d->updateILS); - sqlite3_bind_int64(d->updateILS, 1, ils); - sqlite3_bind_double(d->updateILS, 2, aHdg); - d->execUpdate(d->updateILS); - updatePosition(ils, newPos); + // Update DB entries... + sqlite3_bind_int64(d->setNavaidColocated, 1, navaid); + sqlite3_bind_int64(d->setNavaidColocated, 2, colocatedDME); + d->execUpdate(d->setNavaidColocated); + + // ...and the in-memory copy of the navrecord + if (d->cache.find(navaid) != d->cache.end()) { + FGNavRecord* rec = (FGNavRecord*) d->cache[navaid].get(); + rec->setColocatedDME(colocatedDME); + } } PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty, @@ -1341,7 +1619,6 @@ PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty, PositionedID apt) { sqlite3_int64 rowId = d->insertPositioned(ty, "", name, pos, apt, true); - d->reset(d->insertCommStation); sqlite3_bind_int64(d->insertCommStation, 1, rowId); sqlite3_bind_int(d->insertCommStation, 2, freq); sqlite3_bind_int(d->insertCommStation, 3, range); @@ -1353,36 +1630,48 @@ PositionedID NavDataCache::insertFix(const std::string& ident, const SGGeod& aPo return d->insertPositioned(FGPositioned::FIX, ident, string(), aPos, 0, true); } -PositionedID NavDataCache::createUserWaypoint(const std::string& ident, const SGGeod& aPos) +PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos) { - return d->insertPositioned(FGPositioned::WAYPOINT, ident, string(), aPos, 0, + return d->insertPositioned(ty, ident, string(), aPos, 0, true /* spatial index */); } + +bool NavDataCache::removePOI(FGPositioned::Type ty, const std::string& aIdent) +{ + d->removePositionedWithIdent(ty, aIdent); + // should remove from the live cache too? + + return true; +} void NavDataCache::setAirportMetar(const string& icao, bool hasMetar) { - d->reset(d->setAirportMetar); sqlite_bind_stdstring(d->setAirportMetar, 1, icao); sqlite3_bind_int(d->setAirportMetar, 2, hasMetar); d->execUpdate(d->setAirportMetar); } -FGPositioned::List NavDataCache::findAllWithIdent(const string& s, - FGPositioned::Filter* filter, bool exact) +//------------------------------------------------------------------------------ +FGPositionedList NavDataCache::findAllWithIdent( const string& s, + FGPositioned::Filter* filter, + bool exact ) { return d->findAllByString(s, "ident", filter, exact); } -FGPositioned::List NavDataCache::findAllWithName(const string& s, - FGPositioned::Filter* filter, bool exact) +//------------------------------------------------------------------------------ +FGPositionedList NavDataCache::findAllWithName( const string& s, + FGPositioned::Filter* filter, + bool exact ) { return d->findAllByString(s, "name", filter, exact); } - -FGPositionedRef NavDataCache::findClosestWithIdent(const string& aIdent, - const SGGeod& aPos, FGPositioned::Filter* aFilter) + +//------------------------------------------------------------------------------ +FGPositionedRef NavDataCache::findClosestWithIdent( const string& aIdent, + const SGGeod& aPos, + FGPositioned::Filter* aFilter ) { - d->reset(d->findClosestWithIdent); sqlite_bind_stdstring(d->findClosestWithIdent, 1, aIdent); if (aFilter) { sqlite3_bind_int(d->findClosestWithIdent, 2, aFilter->minType()); @@ -1397,30 +1686,34 @@ FGPositionedRef NavDataCache::findClosestWithIdent(const string& aIdent, sqlite3_bind_double(d->findClosestWithIdent, 5, cartPos.y()); sqlite3_bind_double(d->findClosestWithIdent, 6, cartPos.z()); + FGPositionedRef result; + while (d->stepSelect(d->findClosestWithIdent)) { FGPositioned* pos = loadById(sqlite3_column_int64(d->findClosestWithIdent, 0)); if (aFilter && !aFilter->pass(pos)) { continue; } - return pos; + result = pos; + break; } - return NULL; // no matches at all + d->reset(d->findClosestWithIdent); + return result; } int NavDataCache::getOctreeBranchChildren(int64_t octreeNodeId) { - d->reset(d->getOctreeChildren); sqlite3_bind_int64(d->getOctreeChildren, 1, octreeNodeId); d->execSelect1(d->getOctreeChildren); - return sqlite3_column_int(d->getOctreeChildren, 0); + int children = sqlite3_column_int(d->getOctreeChildren, 0); + d->reset(d->getOctreeChildren); + return children; } void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) { - d->reset(d->insertOctree); sqlite3_bind_int64(d->insertOctree, 1, nd->guid()); d->execInsert(d->insertOctree); @@ -1430,7 +1723,6 @@ void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) // lowest three bits of node ID are 0..7 index of the child in the parent int childIndex = nd->guid() & 0x07; - d->reset(d->updateOctreeChildren); sqlite3_bind_int64(d->updateOctreeChildren, 1, pr->guid()); // mask has bit N set where child N exists int childMask = 1 << childIndex; @@ -1442,7 +1734,6 @@ void NavDataCache::defineOctreeNode(Octree::Branch* pr, Octree::Node* nd) TypedPositionedVec NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) { - d->reset(d->getOctreeLeafChildren); sqlite3_bind_int64(d->getOctreeLeafChildren, 1, octreeNodeId); TypedPositionedVec r; @@ -1453,6 +1744,7 @@ NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) sqlite3_column_int64(d->getOctreeLeafChildren, 0))); } + d->reset(d->getOctreeLeafChildren); return r; } @@ -1464,14 +1756,19 @@ NavDataCache::getOctreeLeafChildren(int64_t octreeNodeId) */ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) { - d->reset(d->searchAirports); - string s = "%" + aFilter + "%"; - sqlite_bind_stdstring(d->searchAirports, 1, s); - + sqlite3_stmt_ptr stmt; unsigned int numMatches = 0, numAllocated = 16; - char** result = (char**) malloc(sizeof(char*) * numAllocated); + string searchTerm("%" + aFilter + "%"); + if (aFilter.empty()) { + stmt = d->getAllAirports; + numAllocated = 4096; // start much larger for all airports + } else { + stmt = d->searchAirports; + sqlite_bind_stdstring(stmt, 1, searchTerm); + } - while (d->stepSelect(d->searchAirports)) { + char** result = (char**) malloc(sizeof(char*) * numAllocated); + while (d->stepSelect(stmt)) { if ((numMatches + 1) >= numAllocated) { numAllocated <<= 1; // double in size! // reallocate results array @@ -1489,18 +1786,18 @@ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) // which gives a grand total of 7 + name-length + icao-length. // note the ident can be three letters (non-ICAO local strip), four // (default ICAO) or more (extended format ICAO) - int nameLength = sqlite3_column_bytes(d->searchAirports, 1); - int icaoLength = sqlite3_column_bytes(d->searchAirports, 0); + int nameLength = sqlite3_column_bytes(stmt, 1); + int icaoLength = sqlite3_column_bytes(stmt, 0); char* entry = (char*) malloc(7 + nameLength + icaoLength); char* dst = entry; *dst++ = ' '; - memcpy(dst, sqlite3_column_text(d->searchAirports, 1), nameLength); + memcpy(dst, sqlite3_column_text(stmt, 1), nameLength); dst += nameLength; *dst++ = ' '; *dst++ = ' '; *dst++ = ' '; *dst++ = '('; - memcpy(dst, sqlite3_column_text(d->searchAirports, 0), icaoLength); + memcpy(dst, sqlite3_column_text(stmt, 0), icaoLength); dst += icaoLength; *dst++ = ')'; *dst++ = 0; @@ -1509,13 +1806,13 @@ char** NavDataCache::searchAirportNamesAndIdents(const std::string& aFilter) } result[numMatches] = NULL; // end of list marker + d->reset(stmt); return result; } FGPositionedRef NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter) { - d->reset(d->findCommByFreq); sqlite3_bind_int(d->findCommByFreq, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findCommByFreq, 2, aFilter->minType()); @@ -1529,18 +1826,25 @@ NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filt sqlite3_bind_double(d->findCommByFreq, 4, cartPos.x()); sqlite3_bind_double(d->findCommByFreq, 5, cartPos.y()); sqlite3_bind_double(d->findCommByFreq, 6, cartPos.z()); + FGPositionedRef result; - if (!d->execSelect(d->findCommByFreq)) { - return NULL; + while (d->execSelect(d->findCommByFreq)) { + FGPositioned* p = loadById(sqlite3_column_int64(d->findCommByFreq, 0)); + if (aFilter && !aFilter->pass(p)) { + continue; + } + + result = p; + break; } - return loadById(sqlite3_column_int64(d->findCommByFreq, 0)); + d->reset(d->findCommByFreq); + return result; } PositionedIDVec NavDataCache::findNavaidsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filter* aFilter) { - d->reset(d->findNavsByFreq); sqlite3_bind_int(d->findNavsByFreq, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findNavsByFreq, 2, aFilter->minType()); @@ -1561,7 +1865,6 @@ NavDataCache::findNavaidsByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::F PositionedIDVec NavDataCache::findNavaidsByFreq(int freqKhz, FGPositioned::Filter* aFilter) { - d->reset(d->findNavsByFreqNoPos); sqlite3_bind_int(d->findNavsByFreqNoPos, 1, freqKhz); if (aFilter) { sqlite3_bind_int(d->findNavsByFreqNoPos, 2, aFilter->minType()); @@ -1582,7 +1885,6 @@ NavDataCache::airportItemsOfType(PositionedID apt,FGPositioned::Type ty, maxTy = ty; // single-type range } - d->reset(d->getAirportItems); sqlite3_bind_int64(d->getAirportItems, 1, apt); sqlite3_bind_int(d->getAirportItems, 2, ty); sqlite3_bind_int(d->getAirportItems, 3, maxTy); @@ -1594,16 +1896,17 @@ PositionedID NavDataCache::airportItemWithIdent(PositionedID apt, FGPositioned::Type ty, const std::string& ident) { - d->reset(d->getAirportItemByIdent); sqlite3_bind_int64(d->getAirportItemByIdent, 1, apt); sqlite_bind_stdstring(d->getAirportItemByIdent, 2, ident); sqlite3_bind_int(d->getAirportItemByIdent, 3, ty); + PositionedID result = 0; - if (!d->execSelect(d->getAirportItemByIdent)) { - return 0; + if (d->execSelect(d->getAirportItemByIdent)) { + result = sqlite3_column_int64(d->getAirportItemByIdent, 0); } - return sqlite3_column_int64(d->getAirportItemByIdent, 0); + d->reset(d->getAirportItemByIdent); + return result; } AirportRunwayPair @@ -1619,55 +1922,62 @@ NavDataCache::findAirportRunway(const std::string& aName) return AirportRunwayPair(); } - d->reset(d->findAirportRunway); + AirportRunwayPair result; sqlite_bind_stdstring(d->findAirportRunway, 1, parts[0]); - sqlite_bind_stdstring(d->findAirportRunway, 2, parts[1]); - if (!d->execSelect(d->findAirportRunway)) { + sqlite_bind_stdstring(d->findAirportRunway, 2, cleanRunwayNo(parts[1])); + + if (d->execSelect(d->findAirportRunway)) { + result = AirportRunwayPair(sqlite3_column_int64(d->findAirportRunway, 0), + sqlite3_column_int64(d->findAirportRunway, 1)); + + } else { SG_LOG(SG_NAVCACHE, SG_WARN, "findAirportRunway: unknown airport/runway:" << aName); - return AirportRunwayPair(); } - // success, extract the IDs and continue - return AirportRunwayPair(sqlite3_column_int64(d->findAirportRunway, 0), - sqlite3_column_int64(d->findAirportRunway, 1)); + d->reset(d->findAirportRunway); + return result; } PositionedID -NavDataCache::findILS(PositionedID airport, const string& runway, const string& navIdent) +NavDataCache::findILS(PositionedID airport, const string& aRunway, const string& navIdent) { - d->reset(d->findILS); + string runway(cleanRunwayNo(aRunway)); + sqlite_bind_stdstring(d->findILS, 1, navIdent); sqlite3_bind_int64(d->findILS, 2, airport); sqlite_bind_stdstring(d->findILS, 3, runway); - - if (!d->execSelect(d->findILS)) { - return 0; + PositionedID result = 0; + if (d->execSelect(d->findILS)) { + result = sqlite3_column_int64(d->findILS, 0); } - return sqlite3_column_int64(d->findILS, 0); + d->reset(d->findILS); + return result; } int NavDataCache::findAirway(int network, const string& aName) { - d->reset(d->findAirway); sqlite3_bind_int(d->findAirway, 1, network); sqlite_bind_stdstring(d->findAirway, 2, aName); + + int airway = 0; if (d->execSelect(d->findAirway)) { // already exists - return sqlite3_column_int(d->findAirway, 0); + airway = sqlite3_column_int(d->findAirway, 0); + } else { + sqlite_bind_stdstring(d->insertAirway, 1, aName); + sqlite3_bind_int(d->insertAirway, 2, network); + airway = d->execInsert(d->insertAirway); } - d->reset(d->insertAirway); - sqlite_bind_stdstring(d->insertAirway, 1, aName); - sqlite3_bind_int(d->insertAirway, 2, network); - return d->execInsert(d->insertAirway); + d->reset(d->findAirway); + return airway; } void NavDataCache::insertEdge(int network, int airwayID, PositionedID from, PositionedID to) { // assume all edges are bidirectional for the moment for (int i=0; i<2; ++i) { - d->reset(d->insertAirwayEdge); sqlite3_bind_int(d->insertAirwayEdge, 1, network); sqlite3_bind_int(d->insertAirwayEdge, 2, airwayID); sqlite3_bind_int64(d->insertAirwayEdge, 3, from); @@ -1680,16 +1990,16 @@ void NavDataCache::insertEdge(int network, int airwayID, PositionedID from, Posi bool NavDataCache::isInAirwayNetwork(int network, PositionedID pos) { - d->reset(d->isPosInAirway); sqlite3_bind_int(d->isPosInAirway, 1, network); sqlite3_bind_int64(d->isPosInAirway, 2, pos); bool ok = d->execSelect(d->isPosInAirway); + d->reset(d->isPosInAirway); + return ok; } AirwayEdgeVec NavDataCache::airwayEdgesFrom(int network, PositionedID pos) { - d->reset(d->airwayEdgesFrom); sqlite3_bind_int(d->airwayEdgesFrom, 1, network); sqlite3_bind_int64(d->airwayEdgesFrom, 2, pos); @@ -1700,8 +2010,211 @@ AirwayEdgeVec NavDataCache::airwayEdgesFrom(int network, PositionedID pos) sqlite3_column_int64(d->airwayEdgesFrom, 1) )); } + + d->reset(d->airwayEdgesFrom); + return result; +} + +PositionedID NavDataCache::findNavaidForRunway(PositionedID runway, FGPositioned::Type ty) +{ + sqlite3_bind_int64(d->findNavaidForRunway, 1, runway); + sqlite3_bind_int(d->findNavaidForRunway, 2, ty); + + PositionedID result = 0; + if (d->execSelect(d->findNavaidForRunway)) { + result = sqlite3_column_int64(d->findNavaidForRunway, 0); + } + + d->reset(d->findNavaidForRunway); + return result; +} + +PositionedID +NavDataCache::insertParking(const std::string& name, const SGGeod& aPos, + PositionedID aAirport, + double aHeading, int aRadius, const std::string& aAircraftType, + const std::string& aAirlines) +{ + sqlite3_int64 rowId = d->insertPositioned(FGPositioned::PARKING, name, "", aPos, aAirport, false); + +// we need to insert a row into the taxi_node table, otherwise we can't maintain +// the appropriate pushback flag. + sqlite3_bind_int64(d->insertTaxiNode, 1, rowId); + sqlite3_bind_int(d->insertTaxiNode, 2, 0); + sqlite3_bind_int(d->insertTaxiNode, 3, 0); + d->execInsert(d->insertTaxiNode); + + sqlite3_bind_int64(d->insertParkingPos, 1, rowId); + sqlite3_bind_double(d->insertParkingPos, 2, aHeading); + sqlite3_bind_int(d->insertParkingPos, 3, aRadius); + sqlite_bind_stdstring(d->insertParkingPos, 4, aAircraftType); + sqlite_bind_stdstring(d->insertParkingPos, 5, aAirlines); + return d->execInsert(d->insertParkingPos); +} + +void NavDataCache::setParkingPushBackRoute(PositionedID parking, PositionedID pushBackNode) +{ + sqlite3_bind_int64(d->setParkingPushBack, 1, parking); + sqlite3_bind_int64(d->setParkingPushBack, 2, pushBackNode); + d->execUpdate(d->setParkingPushBack); +} + +PositionedID +NavDataCache::insertTaxiNode(const SGGeod& aPos, PositionedID aAirport, int aHoldType, bool aOnRunway) +{ + sqlite3_int64 rowId = d->insertPositioned(FGPositioned::TAXI_NODE, string(), string(), aPos, aAirport, false); + sqlite3_bind_int64(d->insertTaxiNode, 1, rowId); + sqlite3_bind_int(d->insertTaxiNode, 2, aHoldType); + sqlite3_bind_int(d->insertTaxiNode, 3, aOnRunway); + return d->execInsert(d->insertTaxiNode); +} + +void NavDataCache::insertGroundnetEdge(PositionedID aAirport, PositionedID from, PositionedID to) +{ + sqlite3_bind_int64(d->insertTaxiEdge, 1, aAirport); + sqlite3_bind_int64(d->insertTaxiEdge, 2, from); + sqlite3_bind_int64(d->insertTaxiEdge, 3, to); + d->execInsert(d->insertTaxiEdge); +} + +PositionedIDVec NavDataCache::groundNetNodes(PositionedID aAirport, bool onlyPushback) +{ + sqlite3_stmt_ptr q = onlyPushback ? d->airportPushbackNodes : d->airportTaxiNodes; + sqlite3_bind_int64(q, 1, aAirport); + return d->selectIds(q); +} + +void NavDataCache::markGroundnetAsPushback(PositionedID nodeId) +{ + sqlite3_bind_int64(d->markTaxiNodeAsPushback, 1, nodeId); + d->execUpdate(d->markTaxiNodeAsPushback); +} + +static double headingDifferenceDeg(double crs1, double crs2) +{ + double diff = crs2 - crs1; + SG_NORMALIZE_RANGE(diff, -180.0, 180.0); + return diff; +} + +PositionedID NavDataCache::findGroundNetNode(PositionedID airport, const SGGeod& aPos, + bool onRunway, FGRunway* aRunway) +{ + sqlite3_stmt_ptr q = onRunway ? d->findNearestRunwayTaxiNode : d->findNearestTaxiNode; + sqlite3_bind_int64(q, 1, airport); + + SGVec3d cartPos(SGVec3d::fromGeod(aPos)); + sqlite3_bind_double(q, 2, cartPos.x()); + sqlite3_bind_double(q, 3, cartPos.y()); + sqlite3_bind_double(q, 4, cartPos.z()); + + PositionedID result = 0; + while (d->execSelect(q)) { + PositionedID id = sqlite3_column_int64(q, 0); + if (!aRunway) { + result = id; + break; + } + + // ensure found node lies on the runway + FGPositionedRef node = loadById(id); + double course = SGGeodesy::courseDeg(node->geod(), aRunway->end()); + if (fabs(headingDifferenceDeg(course, aRunway->headingDeg())) < 3.0 ) { + result = id; + break; + } + } + + d->reset(q); return result; } +PositionedIDVec NavDataCache::groundNetEdgesFrom(PositionedID pos, bool onlyPushback) +{ + sqlite3_stmt_ptr q = onlyPushback ? d->pushbackEdgesFrom : d->taxiEdgesFrom; + sqlite3_bind_int64(q, 1, pos); + return d->selectIds(q); +} + +PositionedIDVec NavDataCache::findAirportParking(PositionedID airport, const std::string& flightType, + int radius) +{ + sqlite3_bind_int64(d->findAirportParking, 1, airport); + sqlite3_bind_int(d->findAirportParking, 2, radius); + sqlite_bind_stdstring(d->findAirportParking, 3, flightType); + + return d->selectIds(d->findAirportParking); +} + +void NavDataCache::dropGroundnetFor(PositionedID aAirport) +{ + sqlite3_stmt_ptr q = d->prepare("DELETE FROM parking WHERE rowid IN (SELECT rowid FROM positioned WHERE type=?1 AND airport=?2)"); + sqlite3_bind_int(q, 1, FGPositioned::PARKING); + sqlite3_bind_int64(q, 2, aAirport); + d->execUpdate(q); + + q = d->prepare("DELETE FROM taxi_node WHERE rowid IN (SELECT rowid FROM positioned WHERE (type=?1 OR type=?2) AND airport=?3)"); + sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE); + sqlite3_bind_int(q, 2, FGPositioned::PARKING); + sqlite3_bind_int64(q, 3, aAirport); + d->execUpdate(q); + + q = d->prepare("DELETE FROM positioned WHERE (type=?1 OR type=?2) AND airport=?3"); + sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE); + sqlite3_bind_int(q, 2, FGPositioned::PARKING); + sqlite3_bind_int64(q, 3, aAirport); + d->execUpdate(q); + + q = d->prepare("DELETE FROM groundnet_edge WHERE airport=?1"); + sqlite3_bind_int64(q, 1, aAirport); + d->execUpdate(q); +} + +void NavDataCache::dropAllGroundnets() +{ + SG_LOG(SG_NAVCACHE, SG_INFO, "dropping ground-net data"); + beginTransaction(); + d->runSQL("DELETE FROM groundnet_edge"); + d->runSQL("DELETE FROM parking"); + d->runSQL("DELETE FROM taxi_node"); + + sqlite3_stmt_ptr q = d->prepare("DELETE FROM positioned WHERE (type=?1 OR type=?2)"); + sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE); + sqlite3_bind_int(q, 2, FGPositioned::PARKING); + d->execUpdate(q); + commitTransaction(); +} + +bool NavDataCache::isReadOnly() const +{ + return d->readOnly; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Transaction RAII object + +NavDataCache::Transaction::Transaction(NavDataCache* cache) : + _instance(cache), + _committed(false) +{ + assert(cache); + _instance->beginTransaction(); +} + +NavDataCache::Transaction::~Transaction() +{ + if (!_committed) { + SG_LOG(SG_NAVCACHE, SG_INFO, "aborting cache transaction!"); + _instance->abortTransaction(); + } +} + +void NavDataCache::Transaction::commit() +{ + assert(!_committed); + _committed = true; + _instance->commitTransaction(); +} + } // of namespace flightgear