]> git.mxchange.org Git - flightgear.git/blobdiff - src/Navaids/NavDataCache.cxx
Fix distance-along-path computation
[flightgear.git] / src / Navaids / NavDataCache.cxx
index bcc82a2609c7e7a44a62599ec2707f8fa79a00a9..f6756613bd6c19130d5c308b44c36282add9852f 100644 (file)
 # 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
 // boost
 #include <boost/foreach.hpp>
 
-#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 <simgear/sg_inlines.h>
 #include <simgear/structure/exception.hxx>
 #include <simgear/debug/logstream.hxx>
 #include <simgear/bucket/newbucket.hxx>
 #include <simgear/threads/SGGuard.hxx>
 
 #include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
+#include <Main/options.hxx>
 #include "markerbeacon.hxx"
 #include "navrecord.hxx"
-#include <Airports/simple.hxx>
+#include <Airports/airport.hxx>
 #include <Airports/runways.hxx>
 #include <ATC/CommStation.hxx>
 #include "fix.hxx"
 #include "PositionedOctree.hxx"
 #include <Airports/apt_loader.hxx>
 #include <Navaids/airways.hxx>
+#include "poidb.hxx"
+#include <Airports/parking.hxx>
+#include <Airports/gnnode.hxx>
+#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<SGMutex> 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);
     
-    return new FGNavRecord(rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway);
+    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);
+    
+    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<RebuildThread> 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<PositionedID>(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<FGAirport>(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,6 +1826,7 @@ 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;
   
   while (d->execSelect(d->findCommByFreq)) {
     FGPositioned* p = loadById(sqlite3_column_int64(d->findCommByFreq, 0));
@@ -1536,16 +1834,17 @@ NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filt
       continue;
     }
     
-    return p;
+    result = p;
+    break;
   }
   
-  return NULL;
+  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());
@@ -1566,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());
@@ -1587,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);
@@ -1599,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
@@ -1624,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);
@@ -1685,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);
   
@@ -1705,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