]> git.mxchange.org Git - flightgear.git/blobdiff - src/Navaids/NavDataCache.cxx
Bug #927 - flightplan XML loading.
[flightgear.git] / src / Navaids / NavDataCache.cxx
index 4b3cbcee19a920110329ab3d673cbba5282df036..05e8c011eec4cd204a65474f44d50f1beef72727 100644 (file)
@@ -45,6 +45,8 @@
 #include <simgear/bucket/newbucket.hxx>
 #include <simgear/misc/sg_path.hxx>
 #include <simgear/misc/strutils.hxx>
+#include <simgear/threads/SGThread.hxx>
+#include <simgear/threads/SGGuard.hxx>
 
 #include <Main/globals.hxx>
 #include "markerbeacon.hxx"
 #include "PositionedOctree.hxx"
 #include <Airports/apt_loader.hxx>
 #include <Navaids/airways.hxx>
+#include <Airports/parking.hxx>
+#include <Airports/gnnode.hxx>
 
 using std::string;
 
-#define SG_NAVCACHE SG_GENERAL
+#define SG_NAVCACHE SG_NAVAID
 //#define LAZY_OCTREE_UPDATES 1
 
 namespace {
 
-const int SCHEMA_VERSION = 3;
+const int SCHEMA_VERSION = 6;
 
 // 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
@@ -131,6 +135,47 @@ static string cleanRunwayNo(const string& aRwyNo)
 namespace flightgear
 {
 
+/**
+ * Thread encapsulating a cache rebuild. This is not used to parallelise
+ * the rebuild - we must still wait until completion before doing other
+ * startup, since many things rely on a complete cache. The thread is used
+ * so we don't block the main event loop for an unacceptable duration,
+ * which causes 'not responding' / spinning beachballs on Windows & Mac
+ */
+class RebuildThread : public SGThread
+{
+public:
+  RebuildThread(NavDataCache* cache) :
+  _cache(cache),
+  _isFinished(false)
+  {
+    
+  }
+  
+  bool isFinished() const
+  {
+    SGGuard<SGMutex> g(_lock);
+    return _isFinished;
+  }
+  
+  virtual void run()
+  {
+    SGTimeStamp st;
+    st.stamp();
+    _cache->doRebuild();
+    SG_LOG(SG_NAVCACHE, SG_INFO, "cache rebuild took:" << st.elapsedMSec() << "msec");
+    
+    SGGuard<SGMutex> g(_lock);
+    _isFinished = true;
+  }
+private:
+  NavDataCache* _cache;
+  mutable SGMutex _lock;
+  bool _isFinished;
+};
+
+////////////////////////////////////////////////////////////////////////////
+  
 typedef std::map<PositionedID, FGPositionedRef> PositionedCache;
   
 class AirportTower : public FGPositioned
@@ -187,8 +232,8 @@ public:
     }
     
     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);
@@ -247,7 +292,7 @@ public:
     
     try {
       execSelect(stmt);
-    } catch (sg_exception& e) {
+    } catch (sg_exception&) {
       sqlite3_finalize(stmt);
       throw; // re-throw
     }
@@ -289,15 +334,32 @@ public:
     return stepSelect(stmt);
   }
   
+  static const int MAX_RETRIES = 10;
+  
   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_LOCKED) {
+        break;
+      }
+      
+      ++retries;
+    } // 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_LOCKED retries");
+      return false;
     }
     
     string errMsg;
@@ -418,10 +480,35 @@ public:
            ")");
     
     runSQL("CREATE INDEX airway_edge_from ON airway_edge(a)");
+    
+    runSQL("CREATE TABLE taxi_node ("
+           "hold_type INT,"
+           "on_runway BOOL,"
+           "pushback BOOL"
+           ")");
+    
+    runSQL("CREATE TABLE parking ("
+           "heading FLOAT,"
+           "radius INT,"
+           "gate_type VARCHAR,"
+           "airlines VARCHAR,"
+           "pushback INT64"
+           ")");
+    
+    runSQL("CREATE TABLE groundnet_edge ("
+           "airport INT64,"
+           "a INT64,"
+           "b INT64"
+           ")");
+    
+    runSQL("CREATE INDEX groundnet_edge_airport ON groundnet_edge(airport)");
+    runSQL("CREATE INDEX groundnet_edge_from ON groundnet_edge(a)");
   }
   
   void prepareQueries()
   {
+    writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)");
+    
 #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=?");
@@ -482,6 +569,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");
@@ -521,6 +611,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)");
@@ -531,10 +622,55 @@ 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)
   {
+    reset(clearProperty);
+    sqlite_bind_stdstring(clearProperty, 1, key);
+    execUpdate(clearProperty);
+    
     sqlite_bind_stdstring(writePropertyQuery, 1, key);
     sqlite3_bind_int(writePropertyQuery, 2, value);
     execSelect(writePropertyQuery);
@@ -550,7 +686,7 @@ public:
     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);
     return new FGAirport(rowId, id, pos, name, hasMetar, ty);
   }
   
@@ -600,7 +736,7 @@ public:
     int range = sqlite3_column_int(loadCommStation, 0);
     int freqKhz = sqlite3_column_int(loadCommStation, 1);
     
-    CommStation* c = new CommStation(rowId, id, ty, pos, freqKhz, range);
+    CommStation* c = new CommStation(rowId, name, ty, pos, freqKhz, range);
     c->setAirport(airport);
     return c;
   }
@@ -629,6 +765,35 @@ public:
     return new FGNavRecord(rowId, ty, id, name, pos, freq, rangeNm, mulituse, runway);
   }
   
+  FGPositioned* loadParking(sqlite3_int64 rowId,
+                            const string& name, const SGGeod& pos,
+                            PositionedID airport)
+  {
+    reset(loadParkingPos);
+    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);
+    
+    return new FGParking(rowId, pos, heading, radius, name, aircraftType, airlines, pushBack);
+  }
+  
+  FGPositioned* loadTaxiNode(sqlite3_int64 rowId, const SGGeod& pos,
+                             PositionedID airport)
+  {
+    reset(loadTaxiNodeStmt);
+    sqlite3_bind_int64(loadTaxiNodeStmt, 1, rowId);
+    execSelect1(loadTaxiNodeStmt);
+    
+    int hold_type = sqlite3_column_int(loadTaxiNodeStmt, 0);
+    bool onRunway = sqlite3_column_int(loadTaxiNodeStmt, 1);
+    return new FGTaxiNode(rowId, pos, onRunway, hold_type);
+  }
+  
   PositionedID insertPositioned(FGPositioned::Type ty, const string& ident,
                                 const string& name, const SGGeod& pos, PositionedID apt,
                                 bool spatialIndex)
@@ -664,13 +829,13 @@ public:
                                      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
@@ -748,6 +913,7 @@ public:
     stampFileCache, statCacheCheck,
     loadAirportStmt, loadCommStation, loadPositioned, loadNavaid,
     loadRunwayStmt;
+  sqlite3_stmt_ptr writePropertyMulti, clearProperty;
   
   sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway,
   insertCommStation, insertNavaid;
@@ -761,7 +927,7 @@ public:
 
   sqlite3_stmt_ptr searchAirports;
   sqlite3_stmt_ptr findCommByFreq, findNavsByFreq,
-  findNavsByFreqNoPos;
+  findNavsByFreqNoPos, findNavaidForRunway;
   sqlite3_stmt_ptr getAirportItems, getAirportItemByIdent;
   sqlite3_stmt_ptr findAirportRunway,
     findILS;
@@ -772,6 +938,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.
@@ -781,6 +953,10 @@ public:
   StmtVec prepared;
   
   std::set<Octree::Branch*> deferredOctreeUpdates;
+  
+  // if we're performing a rebuild, the thread that is doing the work.
+  // otherwise, NULL
+  std::auto_ptr<RebuildThread> rebuilder;
 };
 
   //////////////////////////////////////////////////////////////////////
@@ -856,6 +1032,12 @@ 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;
   }
@@ -878,10 +1060,10 @@ NavDataCache::NavDataCache()
     // reached this point with no exception, success
       break;
     } catch (sg_exception& e) {
-      SG_LOG(SG_NAVCACHE, SG_WARN, "NavCache: init failed:" << e.what()
+      SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what()
              << " (attempt " << t << ")");
-      homePath.remove();
       d.reset();
+      homePath.remove();
     }
   } // of retry loop
     
@@ -934,15 +1116,36 @@ bool NavDataCache::isRebuildRequired()
       isCachedFileModified(d->fixDatPath) ||
       isCachedFileModified(d->airwayDatPath))
   {
-    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");
+  string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";");  
+  if (readStringProperty("scenery_paths") != sceneryPaths) {
+    SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: scenery paths changed, main cache rebuild required");
+    return true;
+  }
+    
+  SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: no main cache rebuild required");
   return false;
 }
   
-void NavDataCache::rebuild()
+bool NavDataCache::rebuild()
+{
+  if (!d->rebuilder.get()) {
+    d->rebuilder.reset(new RebuildThread(this));
+    d->rebuilder->start();
+  }
+  
+// poll the rebuild thread
+  bool fin = d->rebuilder->isFinished();
+  if (fin) {
+    d->rebuilder.reset(); // all done!
+  }
+  return fin;
+}
+  
+void NavDataCache::doRebuild()
 {
   try {
     d->runSQL("BEGIN");
@@ -954,6 +1157,10 @@ void NavDataCache::rebuild()
     d->runSQL("DELETE FROM octree");
     d->runSQL("DELETE FROM airway");
     d->runSQL("DELETE FROM airway_edge");
+    d->runSQL("DELETE FROM taxi_node");
+    d->runSQL("DELETE FROM parking");
+    d->runSQL("DELETE FROM groundnet_edge");
+    d->runSQL("DELETE FROM stat_cache");
     
   // initialise the root octree node
     d->runSQL("INSERT INTO octree (rowid, children) VALUES (1, 0)");
@@ -988,6 +1195,9 @@ void NavDataCache::rebuild()
     
     d->flushDeferredOctreeUpdates();
     
+    string sceneryPaths = simgear::strutils::join(globals->get_fg_scenery(), ";");
+    writeStringProperty("scenery_paths", sceneryPaths);
+    
     d->runSQL("COMMIT");
   } catch (sg_exception& e) {
     SG_LOG(SG_NAVCACHE, SG_ALERT, "caught exception rebuilding navCache:" << e.what());
@@ -1040,6 +1250,10 @@ void NavDataCache::writeIntProperty(const string& key, int value)
 
 void NavDataCache::writeStringProperty(const string& key, const string& value)
 {
+  d->reset(d->clearProperty);
+  sqlite_bind_stdstring(d->clearProperty, 1, key);
+  d->execUpdate(d->clearProperty);
+
   d->reset(d->writePropertyQuery);
   sqlite_bind_stdstring(d->writePropertyQuery, 1, key);
   sqlite_bind_stdstring(d->writePropertyQuery, 2, value);
@@ -1048,13 +1262,42 @@ void NavDataCache::writeStringProperty(const string& key, const string& value)
 
 void NavDataCache::writeDoubleProperty(const string& key, const double& value)
 {
+  d->reset(d->clearProperty);
+  sqlite_bind_stdstring(d->clearProperty, 1, key);
+  d->execUpdate(d->clearProperty);
+  
   d->reset(d->writePropertyQuery);
   sqlite_bind_stdstring(d->writePropertyQuery, 1, key);
   sqlite3_bind_double(d->writePropertyQuery, 2, value);
   d->execSelect(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));
+  }
+  
+  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);
+  }
+}
+  
 bool NavDataCache::isCachedFileModified(const SGPath& path) const
 {
   if (!path.exists()) {
@@ -1065,8 +1308,25 @@ bool NavDataCache::isCachedFileModified(const SGPath& path) const
   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());
+    time_t delta = std::labs(modtime - path.modTime());
+    if (delta != 0)
+    {
+      SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: rebuild required for " << path << ". Timestamps: " << modtime << " != " << path.modTime());
+      if (delta < 30)
+      {
+          // File system time stamp has slightly changed. It's unlikely that the user has managed to change a file, start fgfs,
+          // and then changed file again within x seconds - so it's suspicious...
+          SG_LOG(SG_NAVCACHE, SG_ALERT, "NavCache: suspicious file timestamp change. Please report this to FlightGear. "
+                  << "Delta: " << delta << ", Timestamps: " << modtime << ", " << path.modTime() << ", path: " << path);
+      }
+    }
+    else
+    {
+      SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: no rebuild required for " << path);
+    }
+    return (delta != 0);
   } else {
+    SG_LOG(SG_NAVCACHE, SG_DEBUG, "NavCache: initial build required for " << path);
     return true;
   }
 }
@@ -1079,6 +1339,20 @@ void NavDataCache::stampCacheFile(const SGPath& path)
   d->execInsert(d->stampFileCache);
 }
 
+void NavDataCache::beginTransaction()
+{
+  d->runSQL("BEGIN");
+}
+  
+void NavDataCache::commitTransaction()
+{
+  d->runSQL("COMMIT");
+}
+  
+void NavDataCache::abortTransaction()
+{
+  d->runSQL("ROLLBACK");
+}
 
 FGPositioned* NavDataCache::loadById(PositionedID rowid)
 {
@@ -1121,6 +1395,11 @@ 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);
@@ -1129,6 +1408,14 @@ void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos)
   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());
   
@@ -1439,11 +1726,16 @@ NavDataCache::findCommByFreq(int freqKhz, const SGGeod& aPos, FGPositioned::Filt
   sqlite3_bind_double(d->findCommByFreq, 5, cartPos.y());
   sqlite3_bind_double(d->findCommByFreq, 6, cartPos.z());
   
-  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;
+    }
+    
+    return p;
   }
   
-  return loadById(sqlite3_column_int64(d->findCommByFreq, 0));
+  return NULL;
 }
   
 PositionedIDVec
@@ -1611,6 +1903,164 @@ AirwayEdgeVec NavDataCache::airwayEdgesFrom(int network, PositionedID pos)
   }
   return result;
 }
+
+PositionedID NavDataCache::findNavaidForRunway(PositionedID runway, FGPositioned::Type ty)
+{
+  d->reset(d->findNavaidForRunway);
+  sqlite3_bind_int64(d->findNavaidForRunway, 1, runway);
+  sqlite3_bind_int(d->findNavaidForRunway, 2, ty);
+  if (!d->execSelect(d->findNavaidForRunway)) {
+    return 0;
+  }
+  
+  return sqlite3_column_int64(d->findNavaidForRunway, 0);
+}
+  
+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.
+  d->reset(d->insertTaxiNode);
+  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);
+  
+  d->reset(d->insertParkingPos);
+  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)
+{
+  d->reset(d->setParkingPushBack);
+  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);
+  d->reset(d->insertTaxiNode);
+  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)
+{
+  d->reset(d->insertTaxiEdge);
+  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;
+  d->reset(q);
+  sqlite3_bind_int64(q, 1, aAirport);
+  return d->selectIds(q);
+}
+  
+void NavDataCache::markGroundnetAsPushback(PositionedID nodeId)
+{
+  d->reset(d->markTaxiNodeAsPushback);
+  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;
+  d->reset(q);
+  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());
+  
+  while (d->execSelect(q)) {
+    PositionedID id = sqlite3_column_int64(q, 0);
+    if (!aRunway) {
+      return id;
+    }
+    
+  // 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 ) {
+      return id;
+    }
+  }
+  
+  return 0;
+}
+  
+PositionedIDVec NavDataCache::groundNetEdgesFrom(PositionedID pos, bool onlyPushback)
+{
+  sqlite3_stmt_ptr q = onlyPushback ? d->pushbackEdgesFrom : d->taxiEdgesFrom;
+  d->reset(q);
+  sqlite3_bind_int64(q, 1, pos);
+  return d->selectIds(q);
+}
+
+PositionedIDVec NavDataCache::findAirportParking(PositionedID airport, const std::string& flightType,
+                                   int radius)
+{
+  d->reset(d->findAirportParking);
+  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 AND airport=?2)");
+  sqlite3_bind_int(q, 1, FGPositioned::TAXI_NODE);
+  sqlite3_bind_int64(q, 2, 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);
+}
   
 } // of namespace flightgear