]> git.mxchange.org Git - flightgear.git/blobdiff - src/Navaids/NavDataCache.cxx
Bug #927 - flightplan XML loading.
[flightgear.git] / src / Navaids / NavDataCache.cxx
index d39f438a5d67b57e6d75174f06460138152f8f5a..05e8c011eec4cd204a65474f44d50f1beef72727 100644 (file)
 #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 = 4;
+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
@@ -161,7 +163,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;
@@ -230,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);
@@ -332,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;
@@ -461,11 +480,33 @@ 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()
   {
-    clearProperty = prepare("DELETE FROM properties WHERE key=?1");
     writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)");
     
 #define POSITIONED_COLS "rowid, type, ident, name, airport, lon, lat, elev_m, octree_node"
@@ -528,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");
@@ -567,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)");
@@ -577,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);
@@ -675,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)
@@ -710,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
@@ -808,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;
@@ -819,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.
@@ -907,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;
   }
@@ -985,11 +1116,17 @@ 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;
 }
   
@@ -1020,6 +1157,10 @@ void NavDataCache::doRebuild()
     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)");
@@ -1054,6 +1195,9 @@ void NavDataCache::doRebuild()
     
     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());
@@ -1106,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);
@@ -1114,6 +1262,10 @@ 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);
@@ -1156,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;
   }
 }
@@ -1170,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)
 {
@@ -1212,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);
@@ -1220,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());
   
@@ -1707,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