From 6d70f48f73d1adb56beaf39a84f73dc4607a3935 Mon Sep 17 00:00:00 2001 From: James Turner Date: Wed, 10 Jan 2024 08:11:42 +0000 Subject: [PATCH] Extend the waypoint / POI nav-data types - add new VRP type - add the concept of temporary waypoints / POIs which are not stored in the persisten cache - add Nasal APIs to create/delete/move waypoints, in addition to the existing create-waypoint and delete-waypoint commands - allow POIs/waypoints to have names as well as idents - Ensure ident/name and spatial queries work correctly for temporary and dynamic waypoints - add test coverage for all this --- src/Airports/gnnode.cxx | 2 +- src/Autopilot/route_mgr.cxx | 16 +- src/Instrumentation/gps.cxx | 2 +- src/Navaids/CacheSchema.h | 19 +- src/Navaids/NavDataCache.cxx | 216 +++++++++++++----- src/Navaids/NavDataCache.hxx | 7 +- src/Navaids/PositionedOctree.cxx | 41 ++-- src/Navaids/PositionedOctree.hxx | 40 +--- src/Navaids/airways.cxx | 4 +- src/Navaids/fixlist.cxx | 4 +- src/Navaids/poidb.cxx | 34 +-- src/Navaids/poidb.hxx | 29 +-- src/Navaids/positioned.cxx | 196 +++++++++------- src/Navaids/positioned.hxx | 57 ++--- src/Scripting/NasalPositioned.cxx | 152 +++++++++++- src/Scripting/NasalPositioned.hxx | 5 +- test_suite/FGTestApi/testGlobals.cxx | 6 + test_suite/FGTestApi/testGlobals.hxx | 4 +- .../Instrumentation/test_rnav_procedures.cxx | 2 + .../unit_tests/Navaids/test_navaids2.cxx | 112 +++++++++ .../unit_tests/Navaids/test_navaids2.hxx | 4 + 21 files changed, 666 insertions(+), 286 deletions(-) diff --git a/src/Airports/gnnode.cxx b/src/Airports/gnnode.cxx index 923d403ca..be8652627 100644 --- a/src/Airports/gnnode.cxx +++ b/src/Airports/gnnode.cxx @@ -16,7 +16,7 @@ using namespace flightgear; FGTaxiNode::FGTaxiNode(FGPositioned::Type ty, int index, const SGGeod& pos, bool aOnRunway, int aHoldType, - const std::string& ident) : FGPositioned(TRANSIENT_ID, ty, ident, pos), + const std::string& ident) : FGPositioned(NavDataCache::instance()->createTransientID(), ty, ident, pos), m_index(index), isOnRunway(aOnRunway), holdType(aHoldType), diff --git a/src/Autopilot/route_mgr.cxx b/src/Autopilot/route_mgr.cxx index 76fdba57b..7274e2ec9 100644 --- a/src/Autopilot/route_mgr.cxx +++ b/src/Autopilot/route_mgr.cxx @@ -1333,9 +1333,12 @@ bool FGRouteMgr::commandDefineUserWaypoint(const SGPropertyNode * arg, SGPropert return false; } + const bool temporary = arg->getBoolValue("temporary"); SGGeod pos(SGGeod::fromDeg(arg->getDoubleValue("longitude-deg"), arg->getDoubleValue("latitude-deg"))); - FGPositioned::createUserWaypoint(ident, pos); + const auto name = arg->getStringValue("name"); + FGPositioned::createWaypoint(FGPositioned::WAYPOINT, ident, pos, + temporary, name); return true; } @@ -1346,8 +1349,15 @@ bool FGRouteMgr::commandDeleteUserWaypoint(const SGPropertyNode * arg, SGPropert SG_LOG(SG_AUTOPILOT, SG_WARN, "missing ident deleting user waypoint"); return false; } - - return FGPositioned::deleteUserWaypoint(ident); + + FGPositioned::TypeFilter f(FGPositioned::WAYPOINT); + auto existing = FGPositioned::findFirstWithIdent(ident, &f); + if (!existing) { + SG_LOG(SG_AUTOPILOT, SG_WARN, "no user waypoint with ident:" << ident); + return false; + } + + return FGPositioned::deleteWaypoint(existing); } diff --git a/src/Instrumentation/gps.cxx b/src/Instrumentation/gps.cxx index 86f0b3abc..3b4a131df 100644 --- a/src/Instrumentation/gps.cxx +++ b/src/Instrumentation/gps.cxx @@ -1519,7 +1519,7 @@ void GPS::defineWaypoint() } SG_LOG(SG_INSTR, SG_INFO, "GPS:defineWaypoint: creating waypoint:" << ident); - FGPositionedRef wpt = FGPositioned::createUserWaypoint(ident, _scratchPos); + FGPositionedRef wpt = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, ident, _scratchPos, false); _searchResults.clear(); _searchResults.push_back(wpt); setScratchFromPositioned(wpt.get(), -1); diff --git a/src/Navaids/CacheSchema.h b/src/Navaids/CacheSchema.h index 63b22b483..38fcf32db 100644 --- a/src/Navaids/CacheSchema.h +++ b/src/Navaids/CacheSchema.h @@ -1,6 +1,6 @@ #pragma once -const int SCHEMA_VERSION = 22; +const int SCHEMA_VERSION = 25; #define SCHEMA_SQL \ "CREATE TABLE properties (key VARCHAR, value VARCHAR);" \ @@ -15,7 +15,7 @@ const int SCHEMA_VERSION = 22; "CREATE INDEX pos_name ON positioned(name collate nocase);" \ "CREATE INDEX pos_apt_type ON positioned(airport, type);" \ \ - "CREATE TABLE airport (scenery_path VARCHAR, has_metar BOOL);" \ + "CREATE TABLE airport (scenery_path VARCHAR, has_metar BOOL);" \ "CREATE TABLE comm (freq_khz INT,range_nm INT);" \ "CREATE INDEX comm_freq ON comm(freq_khz);" \ \ @@ -32,3 +32,18 @@ const int SCHEMA_VERSION = 22; "CREATE TABLE airway_edge (network INT,airway INT64,a INT64,b INT64);" \ "CREATE INDEX airway_edge_from ON airway_edge(a);" \ "CREATE INDEX airway_edge_to ON airway_edge(b);" + +// this part is run for every new connection, including read-only connections + +#define TEMPORARY_SCHEMA_SQL \ + "CREATE TEMPORARY TABLE temp_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);" \ + \ + \ + "CREATE TEMPORARY VIEW all_positioned AS " \ + "SELECT rowid AS guid, type, ident, name, airport, lon, lat," \ + "elev_m, octree_node, cart_x, cart_y, cart_z FROM positioned " \ + "UNION ALL " \ + "SELECT rowid AS guid, type, ident, name, airport, lon, lat," \ + "elev_m, octree_node, cart_x, cart_y, cart_z FROM temp_positioned;" diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index 0062387bb..af39a9895 100755 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -329,6 +329,8 @@ public: reset(checkTables); + initTemporaryTables(); + readPropertyQuery = prepare("SELECT value FROM properties WHERE key=?"); writePropertyQuery = prepare("INSERT INTO properties (key, value) VALUES (?,?)"); clearProperty = prepare("DELETE FROM properties WHERE key=?1"); @@ -549,6 +551,18 @@ public: } // of commands in scheme loop } + void initTemporaryTables() + { + const auto commands = simgear::strutils::split(TEMPORARY_SCHEMA_SQL, ";"); + for (auto sql : commands) { + if (sql.empty()) { + continue; + } + + runSQL(sql); + } // of commands in scheme loop + } + void prepareQueries() { writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)"); @@ -558,20 +572,20 @@ public: rollbackTransactionStmt = prepare("ROLLBACK"); -#define POSITIONED_COLS "rowid, type, ident, name, airport, lon, lat, elev_m, octree_node" +#define POSITIONED_COLS "guid, type, ident, name, airport, lon, lat, elev_m, octree_node" #define AND_TYPED "AND type>=?2 AND type <=?3" statCacheCheck = prepare("SELECT stamp, sha FROM stat_cache WHERE path=?"); stampFileCache = prepare("INSERT OR REPLACE INTO stat_cache " "(path, stamp, sha) VALUES (?,?, ?)"); - loadPositioned = prepare("SELECT " POSITIONED_COLS " FROM positioned WHERE rowid=?"); + loadPositioned = prepare("SELECT " POSITIONED_COLS " FROM all_positioned WHERE guid=?"); loadAirportStmt = prepare("SELECT scenery_path, has_metar FROM airport WHERE rowid=?"); loadNavaid = prepare("SELECT range_nm, freq, multiuse, runway, colocated FROM navaid WHERE rowid=?"); loadCommStation = prepare("SELECT freq_khz, range_nm FROM comm WHERE rowid=?"); loadRunwayStmt = prepare("SELECT heading, length_ft, width_m, surface, displaced_threshold," "stopway, reciprocal, ils FROM runway WHERE rowid=?1"); - getAirportItems = prepare("SELECT rowid FROM positioned WHERE airport=?1 " AND_TYPED); + getAirportItems = prepare("SELECT guid FROM all_positioned WHERE airport=?1 " AND_TYPED); setAirportMetar = prepare("UPDATE airport SET has_metar=?2 WHERE rowid=" @@ -588,8 +602,16 @@ public: "cart_x, cart_y, cart_z)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)"); - setAirportPos = prepare("UPDATE positioned SET lon=?2, lat=?3, elev_m=?4, octree_node=?5, " + insertTempPosQuery = prepare("INSERT INTO temp_positioned " + "(rowid, type, ident, name, lon, lat, elev_m, " + "cart_x, cart_y, cart_z)" + " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)"); + + updatePosition = prepare("UPDATE positioned SET lon=?2, lat=?3, elev_m=?4, octree_node=?5, " + "cart_x=?6, cart_y=?7, cart_z=?8 WHERE rowid=?1"); + updateTempPos = prepare("UPDATE temp_positioned SET lon=?2, lat=?3, elev_m=?4, octree_node=?5, " "cart_x=?6, cart_y=?7, cart_z=?8 WHERE rowid=?1"); + insertAirport = prepare("INSERT INTO airport (rowid, scenery_path, has_metar) VALUES (?, ?, ?)"); insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)" " VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); @@ -601,11 +623,11 @@ 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"); + removePositionedQuery = prepare("DELETE FROM positioned WHERE rowid=?1"); + removeTempPosQuery = prepare("DELETE FROM temp_positioned WHERE rowid=?1"); - // 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)"); + // query statement + findClosestWithIdent = prepare("SELECT guid FROM all_positioned WHERE ident=?1 " AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)"); findCommByFreq = prepare("SELECT positioned.rowid FROM positioned, comm WHERE " "positioned.rowid=comm.rowid AND freq_khz=?1 " @@ -649,7 +671,7 @@ public: sqlite3_bind_int(getAllAirports, 2, FGPositioned::SEAPORT); - getAirportItemByIdent = prepare("SELECT rowid FROM positioned WHERE airport=?1 AND ident=?2 AND type=?3"); + getAirportItemByIdent = prepare("SELECT guid FROM all_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=" "(SELECT rowid FROM positioned WHERE type=?4 AND ident=?1)"); @@ -839,6 +861,36 @@ public: return r; } + + PositionedID insertTemporaryPositioned(PositionedID guid, FGPositioned::Type ty, const string& ident, + const string& name, const SGGeod& pos, + bool spatialIndex) + { + if (abandonCache) + throw AbandonCacheException{}; + + SGVec3d cartPos(SGVec3d::fromGeod(pos)); + + sqlite3_bind_int64(insertTempPosQuery, 1, guid); + sqlite3_bind_int(insertTempPosQuery, 2, ty); + sqlite_bind_stdstring(insertTempPosQuery, 3, ident); + sqlite_bind_stdstring(insertTempPosQuery, 4, name); + sqlite3_bind_double(insertTempPosQuery, 5, pos.getLongitudeDeg()); + sqlite3_bind_double(insertTempPosQuery, 6, pos.getLatitudeDeg()); + sqlite3_bind_double(insertTempPosQuery, 7, pos.getElevationM()); + sqlite3_bind_double(insertTempPosQuery, 8, cartPos.x()); + sqlite3_bind_double(insertTempPosQuery, 9, cartPos.y()); + sqlite3_bind_double(insertTempPosQuery, 10, cartPos.z()); + + execInsert(insertTempPosQuery); + + // insert into the transient octree + Octree::Leaf* octreeLeaf = Octree::globalTransientOctree()->findLeafForPos(cartPos); + octreeLeaf->insertChild(ty, guid); + + return guid; + } + FGPositionedList findAllByString(const string& s, const string& column, FGPositioned::Filter* filter, bool exact) { @@ -847,7 +899,7 @@ public: // build up SQL query text string matchTerm = exact ? "=?1" : " LIKE ?1"; - string sql = "SELECT rowid FROM positioned WHERE " + column + matchTerm; + string sql = "SELECT guid FROM all_positioned WHERE " + column + matchTerm; if (filter) { sql += " " AND_TYPED; } @@ -916,12 +968,12 @@ public: deferredOctreeUpdates.clear(); } - void removePositionedWithIdent(FGPositioned::Type ty, const std::string& aIdent) + void removePositioned(PositionedID rowid) { - sqlite3_bind_int(removePOIQuery, 1, ty); - sqlite_bind_stdstring(removePOIQuery, 2, aIdent); - execUpdate(removePOIQuery); - reset(removePOIQuery); + auto stmt = rowid < 0 ? removeTempPosQuery : removePositionedQuery; + sqlite3_bind_int64(stmt, 1, rowid); + execUpdate(stmt); + reset(stmt); } NavDataCache* outer; @@ -959,9 +1011,10 @@ public: sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway, insertCommStation, insertNavaid; + sqlite3_stmt_ptr insertTempPosQuery; sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, setNavaidColocated, - setAirportPos; - sqlite3_stmt_ptr removePOIQuery; + updatePosition, updateTempPos; + sqlite3_stmt_ptr removePositionedQuery, removeTempPosQuery; sqlite3_stmt_ptr findClosestWithIdent; // octree (spatial index) related queries @@ -996,6 +1049,10 @@ public: // if we're performing a rebuild, the thread that is doing the work. // otherwise, NULL std::unique_ptr rebuilder; + + // transient rowIDs (not actually present in the on-disk DB, only in our + // in-memory cache / temporary table) start at this value and count down + PositionedID nextTransientId = -1000; }; ////////////////////////////////////////////////////////////////////// @@ -1058,9 +1115,9 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadById(sqlite3_int64 rowid, case FGPositioned::CITY: case FGPositioned::TOWN: case FGPositioned::VILLAGE: - { - FGPositioned* wpt = new FGPositioned(rowid, ty, ident, pos); - return wpt; + case FGPositioned::VISUAL_REPORTING_POINT: { + FGPositioned* wpt = new POI(rowid, ty, ident, pos, name); + return wpt; } case FGPositioned::FREQ_GROUND: @@ -2009,14 +2066,14 @@ void NavDataCache::clearDynamicPositioneds() FGPositionedRef NavDataCache::loadById(PositionedID rowid) { - if (rowid < 1) { - return NULL; - } - if (!d) return NULL; + if (!d || (rowid == 0)) { + return {}; + }; + PositionedCache::iterator it = d->cache.find(rowid); if (it != d->cache.end()) { d->cacheHits++; - return it->second; // cache it + return it->second; // cache hit } sqlite3_int64 aptId; @@ -2060,35 +2117,62 @@ 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); - } + const bool isTemporary = (item < 0); + SGVec3d cartPos(SGVec3d::fromGeod(pos)); + auto it = d->cache.find(item); - SGVec3d cartPos(SGVec3d::fromGeod(pos)); + // transient item, update the transient octree : this is much easier than the + // persistent octree case (see logic below) becuase we know the tree is fully + // loaded, and there's no DB table to keep in sync; we just update the in-memory + // leaves once we found them. + if (isTemporary) { + assert(it != d->cache.end()); // transient item must existing in the in-memory cache - 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()); + SGVec3d oldCartPos(SGVec3d::fromGeod(it->second->geod())); + const auto ty = it->second->type(); -// 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::globalPersistentOctree()->findLeafForPos(cartPos); - sqlite3_bind_int64(d->setAirportPos, 5, octreeLeaf->guid()); + Octree::Leaf* oldOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(oldCartPos); + Octree::Leaf* newOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(cartPos); - sqlite3_bind_double(d->setAirportPos, 6, cartPos.x()); - sqlite3_bind_double(d->setAirportPos, 7, cartPos.y()); - sqlite3_bind_double(d->setAirportPos, 8, cartPos.z()); + // commonly the octree leaf won't change, so check for that + if (oldOctreeLeaf != newOctreeLeaf) { + oldOctreeLeaf->removeChild(item); + newOctreeLeaf->insertChild(ty, item); + } + + it->second->modifyPosition(pos); + } - d->execUpdate(d->setAirportPos); + if (it != d->cache.end()) { + d->cache[item]->modifyPosition(pos); + } + + auto stmt = isTemporary ? d->updateTempPos : d->updatePosition; + sqlite3_bind_int(stmt, 1, item); + sqlite3_bind_double(stmt, 2, pos.getLongitudeDeg()); + sqlite3_bind_double(stmt, 3, pos.getLatitudeDeg()); + sqlite3_bind_double(stmt, 4, pos.getElevationM()); + + if (!isTemporary) { + // 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::globalPersistentOctree()->findLeafForPos(cartPos); + sqlite3_bind_int64(stmt, 5, octreeLeaf->guid()); + } + + sqlite3_bind_double(stmt, 6, cartPos.x()); + sqlite3_bind_double(stmt, 7, cartPos.y()); + sqlite3_bind_double(stmt, 8, cartPos.z()); + + + d->execUpdate(stmt); } void NavDataCache::insertTower(PositionedID airportId, const SGGeod& pos) @@ -2215,21 +2299,32 @@ PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty, return d->execInsert(d->insertCommStation); } -PositionedID NavDataCache::insertFix(const std::string& ident, const SGGeod& aPos) +PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos, + const std::string& name, bool isTransient) { - return d->insertPositioned(FGPositioned::FIX, ident, string(), aPos, 0, true); + if (isTransient) { + return d->insertTemporaryPositioned(createTransientID(), ty, ident, name, aPos, + true /* spatial index */); + } else { + return d->insertPositioned(ty, ident, name, aPos, 0, + true /* spatial index */); + } + } -PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos) +bool NavDataCache::removePOI(FGPositionedRef ref) { - return d->insertPositioned(ty, ident, string(), aPos, 0, - true /* spatial index */); -} + const bool isTemporary = ref->guid() < 0; + // remove from the octree if temporary. + if (isTemporary) { + auto octreeLeaf = Octree::globalTransientOctree()->findLeafForPos(ref->cart()); + octreeLeaf->removeChild(ref->guid()); + } -bool NavDataCache::removePOI(FGPositioned::Type ty, const std::string& aIdent) -{ - d->removePositionedWithIdent(ty, aIdent); - // should remove from the live cache too? + d->removePositioned(ref->guid()); + + auto it = d->cache.find(ref->guid()); + d->cache.erase(it); return true; } @@ -2811,6 +2906,11 @@ SGPath NavDataCache::path() const return d->path; } +PositionedID NavDataCache::createTransientID() +{ + return d->nextTransientId--; +} + ///////////////////////////////////////////////////////////////////////////////////////// // Transaction RAII object diff --git a/src/Navaids/NavDataCache.hxx b/src/Navaids/NavDataCache.hxx index e455b1447..fd99e116b 100644 --- a/src/Navaids/NavDataCache.hxx +++ b/src/Navaids/NavDataCache.hxx @@ -182,11 +182,10 @@ public: PositionedID insertCommStation(FGPositioned::Type ty, const std::string& name, const SGGeod& pos, int freq, int range, PositionedID apt); - PositionedID insertFix(const std::string& ident, const SGGeod& aPos); - PositionedID createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos); + PositionedID createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos, const std::string& aName, bool transient); - bool removePOI(FGPositioned::Type ty, const std::string& aIdent); + bool removePOI(FGPositionedRef wpt); /// update the metar flag associated with an airport void setAirportMetar(const std::string& icao, bool hasMetar); @@ -336,6 +335,8 @@ public: void clearDynamicPositioneds(); + PositionedID createTransientID(); + private: NavDataCache(); diff --git a/src/Navaids/PositionedOctree.cxx b/src/Navaids/PositionedOctree.cxx index 84c7a9b94..c2b8be817 100644 --- a/src/Navaids/PositionedOctree.cxx +++ b/src/Navaids/PositionedOctree.cxx @@ -1,26 +1,9 @@ -/** - * PositionedOctree - define a spatial octree containing Positioned items - * arranged by their global cartesian position. +/* + * SPDX-FileCopyrightText: (C) 2012 James Turner + * SPDX_FileComment: define a spatial octree containing Positioned items + * SPDX-License-Identifier: GPL-2.0-or-later */ -// Written by James Turner, started 2012. -// -// Copyright (C) 2012 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - #include "config.h" #include "PositionedOctree.hxx" @@ -152,6 +135,20 @@ void Leaf::insertChild(FGPositioned::Type ty, PositionedID id) children.insert(children.end(), TypedPositioned(ty, id)); } +void Leaf::removeChild(PositionedID id) +{ + // only for use with transient items, i.e negative IDs + assert(id < 0); + + auto it = std::find_if(children.begin(), children.end(), [id](const TypedPositioned& p) { + return p.second == id; + }); + + if (it != children.end()) { + children.erase(it); + } +} + void Leaf::loadChildren() { if (_childrenLoaded) { @@ -337,6 +334,7 @@ bool findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPosit FindNearestPQueue pq; FindNearestResults results; pq.push(Ordered(globalPersistentOctree(), 0)); + pq.push(Ordered(globalTransientOctree(), 0)); double cut = aCutoffM; SGTimeStamp tm; @@ -378,6 +376,7 @@ bool findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filte FindNearestPQueue pq; FindNearestResults results; pq.push(Ordered(globalPersistentOctree(), 0)); + pq.push(Ordered(globalTransientOctree(), 0)); double rng = aRangeM; SGTimeStamp tm; diff --git a/src/Navaids/PositionedOctree.hxx b/src/Navaids/PositionedOctree.hxx index 2ae4341f5..1100e0677 100644 --- a/src/Navaids/PositionedOctree.hxx +++ b/src/Navaids/PositionedOctree.hxx @@ -1,28 +1,10 @@ -/** - * PositionedOctree - define a spatial octree containing Positioned items - * arranged by their global cartesian position. +/* + * SPDX-FileCopyrightText: (C) 2012 James Turner + * SPDX_FileComment: define a spatial octree containing Positioned items + * SPDX-License-Identifier: GPL-2.0-or-later */ -// Written by James Turner, started 2012. -// -// Copyright (C) 2012 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -#ifndef FG_POSITIONED_OCTREE_HXX -#define FG_POSITIONED_OCTREE_HXX +#pragma once // std #include @@ -193,14 +175,15 @@ namespace Octree } void insertChild(FGPositioned::Type ty, PositionedID id); + void removeChild(PositionedID id); - private: - bool _childrenLoaded = false; +private: + bool _childrenLoaded = false; - typedef std::multimap ChildMap; - ChildMap children; + typedef std::multimap ChildMap; + ChildMap children; - void loadChildren(); + void loadChildren(); }; class Branch : public Node @@ -253,4 +236,3 @@ namespace Octree } // of namespace flightgear -#endif // of FG_POSITIONED_OCTREE_HXX diff --git a/src/Navaids/airways.cxx b/src/Navaids/airways.cxx index 18e120de9..6346c8b38 100644 --- a/src/Navaids/airways.cxx +++ b/src/Navaids/airways.cxx @@ -422,12 +422,12 @@ void Airway::Network::addEdge(int aWay, const SGGeod& aStartPos, if (!start) { SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways start pt: '" << aStartIdent << "'"); - start = FGPositioned::createUserWaypoint(aStartIdent, aStartPos); + start = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, aStartIdent, aStartPos); } if (!end) { SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways end pt: '" << aEndIdent << "'"); - end = FGPositioned::createUserWaypoint(aEndIdent, aEndPos); + end = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, aEndIdent, aEndPos); } NavDataCache::instance()->insertEdge(_networkID, aWay, start->guid(), end->guid()); diff --git a/src/Navaids/fixlist.cxx b/src/Navaids/fixlist.cxx index 1c893e8bf..eadc68f0b 100644 --- a/src/Navaids/fixlist.cxx +++ b/src/Navaids/fixlist.cxx @@ -145,8 +145,8 @@ void FixesLoader::loadFixes(const NavDataCache::SceneryLocation& sceneryLocation } if (!duplicate) { - _cache->insertFix(ident, pos); - _loadedFixes.insert({ident, pos}); + _cache->createPOI(FGPositioned::FIX, ident, pos, {}, false); + _loadedFixes.insert({ident, pos}); } if ((lineNumber % 100) == 0) { diff --git a/src/Navaids/poidb.cxx b/src/Navaids/poidb.cxx index f3a1821c5..11d7b98f2 100644 --- a/src/Navaids/poidb.cxx +++ b/src/Navaids/poidb.cxx @@ -1,26 +1,10 @@ -// poidb.cxx -- points of interest management routines -// -// Written by Christian Schmitt, March 2013 -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// $Id$ +/* + * SPDX-FileCopyrightText: (C) Christian Schmitt, March 2013 + * SPDX_FileComment: points of interest management routines + * SPDX-License-Identifier: GPL-2.0-or-later + */ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif +#include "config.h" #include // std::ws #include "poidb.hxx" @@ -45,6 +29,10 @@ mapPOITypeToFGPType(int aTy) case 12: return FGPositioned::CITY; case 13: return FGPositioned::TOWN; case 14: return FGPositioned::VILLAGE; + + case 1000: return FGPositioned::VISUAL_REPORTING_POINT; + case 1001: return FGPositioned::WAYPOINT; + default: throw sg_range_exception("Unknown POI type", "FGNavDataCache::readPOIFromStream"); } @@ -87,7 +75,7 @@ static PositionedID readPOIFromStream(std::istream& aStream, NavDataCache* cache return 0; } - return cache->createPOI(type, name, pos); + return cache->createPOI(type, name, pos, name, false); } // load and initialize the POI database diff --git a/src/Navaids/poidb.hxx b/src/Navaids/poidb.hxx index cfbf3b92d..37b7d16a0 100644 --- a/src/Navaids/poidb.hxx +++ b/src/Navaids/poidb.hxx @@ -1,26 +1,10 @@ -// poidb.cxx -- points of interest management routines -// -// Written by Christian Schmitt, March 2013 -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// $Id$ +/* + * SPDX-FileCopyrightText: (C) Christian Schmitt, March 2013 + * SPDX_FileComment: points of interest management routines + * SPDX-License-Identifier: GPL-2.0-or-later + */ - -#ifndef _FG_POIDB_HXX -#define _FG_POIDB_HXX +#pragma once #include @@ -37,4 +21,3 @@ bool poiDBInit(const SGPath& path); } // of namespace flightgear -#endif // _FG_NAVDB_HXX diff --git a/src/Navaids/positioned.cxx b/src/Navaids/positioned.cxx index 00c5c8904..b315b1962 100644 --- a/src/Navaids/positioned.cxx +++ b/src/Navaids/positioned.cxx @@ -1,22 +1,8 @@ -// positioned.cxx - base class for objects which are positioned -// -// Copyright (C) 2008 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// $Id$ +/* + * SPDX-FileCopyrightText: (C) 2008 James Turner + * SPDX_FileComment: base class for objects which are spatially located in the simulated world + * SPDX-License-Identifier: GPL-2.0-or-later + */ #include "config.h" @@ -60,8 +46,6 @@ static bool validateFilter(FGPositioned::Filter* filter) return true; } -const PositionedID FGPositioned::TRANSIENT_ID = -2; - /////////////////////////////////////////////////////////////////////////////// FGPositioned::FGPositioned( PositionedID aGuid, @@ -118,25 +102,55 @@ bool FGPositioned::isNavaidType(FGPositioned* pos) } } +static bool isValidCustomWaypointType(FGPositioned::Type ty) +{ + switch (ty) { + case FGPositioned::WAYPOINT: + case FGPositioned::FIX: + case FGPositioned::VISUAL_REPORTING_POINT: + case FGPositioned::OBSTACLE: + return true; + default: + return false; + } +} + FGPositionedRef -FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos) +FGPositioned::createWaypoint(FGPositioned::Type aType, const std::string& aIdent, const SGGeod& aPos, + bool isTemporary, + const std::string& aName) { NavDataCache* cache = NavDataCache::instance(); - TypeFilter filter(WAYPOINT); - FGPositionedList existing = cache->findAllWithIdent(aIdent, &filter, true); - if (!existing.empty()) { - SG_LOG(SG_NAVAID, SG_WARN, "attempt to insert duplicate WAYPOINT:" << aIdent); - return existing.front(); + + if (!isValidCustomWaypointType(aType)) { + throw std::logic_error(std::string{"Create waypoint: not allowed for type:"} + nameForType(aType)); } - - PositionedID id = cache->createPOI(WAYPOINT, aIdent, aPos); + + TypeFilter filter(aType); + + FGPositionedRef existing = cache->findClosestWithIdent(aIdent, aPos, &filter); + if (existing) { + auto distanceNm = SGGeodesy::distanceNm(existing->geod(), aPos); + if (distanceNm < 100) { + SG_LOG(SG_NAVAID, SG_WARN, "attempt to insert duplicate waypoint:" << aIdent << " within 100nm of existing waypoint with same ident"); + return existing; + } + } + + const PositionedID id = cache->createPOI(aType, aIdent, aPos, aName, isTemporary); return cache->loadById(id); } -bool FGPositioned::deleteUserWaypoint(const std::string& aIdent) +bool FGPositioned::deleteWaypoint(FGPositionedRef ref) { NavDataCache* cache = NavDataCache::instance(); - return cache->removePOI(WAYPOINT, aIdent); + const auto ty = ref->type(); + if (!POI::isType(ty) && (ty != FGPositioned::FIX)) { + SG_LOG(SG_NAVAID, SG_DEV_WARN, "attempt to remove non-POI waypoint:" << ref->ident()); + return false; + } + + return cache->removePOI(ref); } @@ -156,62 +170,63 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName) const char* _name; Type _ty; } NameTypeEntry; - + const NameTypeEntry names[] = { - {"airport", AIRPORT}, - {"heliport", HELIPORT}, - {"seaport", SEAPORT}, - {"vor", VOR}, - {"loc", LOC}, - {"ils", ILS}, - {"gs", GS}, - {"ndb", NDB}, - {"wpt", WAYPOINT}, - {"fix", FIX}, - {"tacan", TACAN}, - {"dme", DME}, - {"atis", FREQ_ATIS}, - {"awos", FREQ_AWOS}, - {"tower", FREQ_TOWER}, - {"ground", FREQ_GROUND}, - {"approach", FREQ_APP_DEP}, - {"departure", FREQ_APP_DEP}, - {"clearance", FREQ_CLEARANCE}, - {"unicom", FREQ_UNICOM}, - {"runway", RUNWAY}, - {"helipad", HELIPAD}, - {"country", COUNTRY}, - {"city", CITY}, - {"town", TOWN}, - {"village", VILLAGE}, - {"taxiway", TAXIWAY}, - {"pavement", PAVEMENT}, - {"om", OM}, - {"mm", MM}, - {"im", IM}, - {"mobile-tacan", MOBILE_TACAN}, - {"obstacle", OBSTACLE}, - {"parking", PARKING}, - {"taxi-node",TAXI_NODE}, + {"airport", AIRPORT}, + {"heliport", HELIPORT}, + {"seaport", SEAPORT}, + {"vor", VOR}, + {"loc", LOC}, + {"ils", ILS}, + {"gs", GS}, + {"ndb", NDB}, + {"wpt", WAYPOINT}, + {"fix", FIX}, + {"tacan", TACAN}, + {"dme", DME}, + {"atis", FREQ_ATIS}, + {"awos", FREQ_AWOS}, + {"tower", FREQ_TOWER}, + {"ground", FREQ_GROUND}, + {"approach", FREQ_APP_DEP}, + {"departure", FREQ_APP_DEP}, + {"clearance", FREQ_CLEARANCE}, + {"unicom", FREQ_UNICOM}, + {"runway", RUNWAY}, + {"helipad", HELIPAD}, + {"country", COUNTRY}, + {"city", CITY}, + {"town", TOWN}, + {"village", VILLAGE}, + {"taxiway", TAXIWAY}, + {"pavement", PAVEMENT}, + {"om", OM}, + {"mm", MM}, + {"im", IM}, + {"mobile-tacan", MOBILE_TACAN}, + {"obstacle", OBSTACLE}, + {"parking", PARKING}, + {"taxi-node", TAXI_NODE}, + {"visual-reporting-point", VISUAL_REPORTING_POINT}, - // aliases - {"localizer", LOC}, - {"gnd", FREQ_GROUND}, - {"twr", FREQ_TOWER}, - {"waypoint", WAYPOINT}, - {"apt", AIRPORT}, - {"arpt", AIRPORT}, - {"rwy", RUNWAY}, - {"any", INVALID}, - {"all", INVALID}, - {"outer-marker", OM}, - {"middle-marker", MM}, - {"inner-marker", IM}, - {"parking-stand", PARKING}, + // aliases + {"localizer", LOC}, + {"gnd", FREQ_GROUND}, + {"twr", FREQ_TOWER}, + {"waypoint", WAYPOINT}, + {"apt", AIRPORT}, + {"arpt", AIRPORT}, + {"rwy", RUNWAY}, + {"any", INVALID}, + {"all", INVALID}, + {"outer-marker", OM}, + {"middle-marker", MM}, + {"inner-marker", IM}, + {"parking-stand", PARKING}, + {"vrp", VISUAL_REPORTING_POINT}, + + {NULL, INVALID}}; - {NULL, INVALID} - }; - std::string lowerName = simgear::strutils::lowercase(aName); for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) { @@ -259,6 +274,9 @@ const char* FGPositioned::nameForType(Type aTy) case CITY: return "city"; case TOWN: return "town"; case VILLAGE: return "village"; + case VISUAL_REPORTING_POINT: return "visual-reporting-point"; + case MOBILE_TACAN: return "mobile-tacan"; + case OBSTACLE: return "obstacle"; default: return "unknown"; } @@ -493,3 +511,13 @@ FGPositioned::TypeFilter::pass(FGPositioned* aPos) const return false; } +POI::POI(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos, const std::string& aName) : FGPositioned(aGuid, ty, aIdent, aPos), + mName(aName) +{ +} + +bool POI::isType(FGPositioned::Type ty) +{ + return (ty == FGPositioned::WAYPOINT) || (ty == FGPositioned::OBSTACLE) || + ((ty >= FGPositioned::COUNTRY) && (ty < FGPositioned::LAST_POI_TYPE)); +} diff --git a/src/Navaids/positioned.hxx b/src/Navaids/positioned.hxx index 656da33d9..42f5aec2e 100644 --- a/src/Navaids/positioned.hxx +++ b/src/Navaids/positioned.hxx @@ -1,25 +1,10 @@ -// positioned.hxx - base class for objects which are positioned -// -// Copyright (C) 2008 James Turner -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation; either version 2 of the -// License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, but -// WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// $Id$ +/* + * SPDX-FileCopyrightText: (C) 2008 James Turner + * SPDX_FileComment: base class for objects which are spatially located in the simulated world + * SPDX-License-Identifier: GPL-2.0-or-later + */ -#ifndef FG_POSITIONED_HXX -#define FG_POSITIONED_HXX +#pragma once #include #include @@ -42,8 +27,6 @@ namespace flightgear { class NavDataCache; } class FGPositioned : public SGReferenced { public: - static const PositionedID TRANSIENT_ID; - typedef enum { INVALID = 0, AIRPORT, @@ -91,7 +74,8 @@ public: CITY, TOWN, VILLAGE, - + VISUAL_REPORTING_POINT, + LAST_POI_TYPE, LAST_TYPE } Type; @@ -295,13 +279,16 @@ public: */ static const char* nameForType(Type aTy); - static FGPositionedRef createUserWaypoint(const std::string& aIdent, const SGGeod& aPos); - static bool deleteUserWaypoint(const std::string& aIdent); + static FGPositionedRef createWaypoint(FGPositioned::Type aType, const std::string& aIdent, const SGGeod& aPos, + bool isTemporary = false, + const std::string& aName = {}); + static bool deleteWaypoint(FGPositionedRef aWpt); protected: friend class flightgear::NavDataCache; FGPositioned(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos); + // called by NavDataCache::updatePosition void modifyPosition(const SGGeod& newPos); void invalidatePosition(); @@ -318,6 +305,8 @@ private: const SGVec3d mCart; }; + + template T* fgpositioned_cast(FGPositioned* p) { @@ -340,4 +329,18 @@ T* fgpositioned_cast(FGPositionedRef p) return nullptr; } -#endif // of FG_POSITIONED_HXX +class POI : public FGPositioned +{ +public: + POI(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos, const std::string& aName); + + const std::string& name() const override + { + return mName; + } + + static bool isType(FGPositioned::Type ty); + + private: + std::string mName; +}; \ No newline at end of file diff --git a/src/Scripting/NasalPositioned.cxx b/src/Scripting/NasalPositioned.cxx index 1d1247545..3ca6f5655 100644 --- a/src/Scripting/NasalPositioned.cxx +++ b/src/Scripting/NasalPositioned.cxx @@ -72,6 +72,9 @@ static naGhostType FixGhostType = { positionedGhostDestroy, "fix", fixGhostGetMe static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* out); static naGhostType CommGhostType = {positionedGhostDestroy, "comm", commGhostGetMember, nullptr}; +static const char* poiGhostGetMember(naContext c, void* g, naRef field, naRef* out); +static naGhostType POIGhostType = {positionedGhostDestroy, "poi", poiGhostGetMember, nullptr}; + static void hashset(naContext c, naRef hash, const char* key, naRef val) { naRef s = naNewString(c); @@ -92,7 +95,8 @@ FGPositioned* positionedGhost(naRef r) (naGhost_type(r) == &NavaidGhostType) || (naGhost_type(r) == &RunwayGhostType) || (naGhost_type(r) == &FixGhostType) || - (naGhost_type(r) == &CommGhostType)) { + (naGhost_type(r) == &FixGhostType) || + (naGhost_type(r) == &POIGhostType)) { return (FGPositioned*) naGhost_ptr(r); } @@ -134,12 +138,22 @@ static FGFix* fixGhost(naRef r) return 0; } +#if 0 static flightgear::CommStation* commGhost(naRef r) { if (naGhost_type(r) == &CommGhostType) return (flightgear::CommStation*)naGhost_ptr(r); return nullptr; } +#endif + +static POI* poiGhost(naRef r) +{ + if (naGhost_type(r) == &POIGhostType) + return (POI*)naGhost_ptr(r); + return nullptr; +} + static void positionedGhostDestroy(void* g) { @@ -151,6 +165,7 @@ static void positionedGhostDestroy(void* g) static naRef airportPrototype; static naRef geoCoordClass; +static naRef waypointPrototype; naRef ghostForAirport(naContext c, const FGAirport* apt) { @@ -222,6 +237,16 @@ naRef ghostForComm(naContext c, const flightgear::CommStation* comm) return naNewGhost2(c, &CommGhostType, (void*)comm); } +naRef ghostForPOI(naContext c, const POI* r) +{ + if (!r) { + return naNil(); + } + + FGPositioned::get(r); // take a ref + return naNewGhost2(c, &POIGhostType, (void*)r); +} + naRef ghostForPositioned(naContext c, FGPositionedRef pos) { if (!pos) { @@ -251,6 +276,14 @@ naRef ghostForPositioned(naContext c, FGPositionedRef pos) case FGPositioned::FREQ_ENROUTE: return ghostForComm(c, fgpositioned_cast(pos)); + case FGPositioned::WAYPOINT: + case FGPositioned::CITY: + case FGPositioned::COUNTRY: + case FGPositioned::TOWN: + case FGPositioned::VISUAL_REPORTING_POINT: + case FGPositioned::VILLAGE: + return ghostForPOI(c, fgpositioned_cast(pos)); + default: SG_LOG(SG_NASAL, SG_DEV_ALERT, "Type lacks Nasal ghost mapping:" << pos->typeString()); return naNil(); @@ -409,6 +442,8 @@ static const char* fixGhostGetMember(naContext c, void* g, naRef field, naRef* o // for homogenity with other values returned by navinfo() else if (!strcmp(fieldName, "type")) *out = stringToNasal(c, "fix"); else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, fix->ident()); + else if (!strcmp(fieldName, "guid")) + *out = naNum(fix->guid()); else { return 0; } @@ -433,13 +468,42 @@ static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* *out = stringToNasal(c, comm->name()); else if (!strcmp(fieldName, "frequency")) { *out = naNum(comm->freqMHz()); + } else if (!strcmp(fieldName, "guid")) { + *out = naNum(comm->guid()); } else { - return 0; + return 0; } return ""; } +static const char* poiGhostGetMember(naContext c, void* g, naRef field, naRef* out) +{ + const char* fieldName = naStr_data(field); + POI* wpt = (POI*)g; + + if (!strcmp(fieldName, "parents")) { + *out = naNewVector(c); + naVec_append(*out, waypointPrototype); + } else if (!strcmp(fieldName, "id")) + *out = stringToNasal(c, wpt->ident()); + else if (!strcmp(fieldName, "lat")) + *out = naNum(wpt->latitude()); + else if (!strcmp(fieldName, "lon")) + *out = naNum(wpt->longitude()); + else if (!strcmp(fieldName, "type")) + *out = stringToNasal(c, wpt->nameForType(wpt->type())); + else if (!strcmp(fieldName, "name")) + *out = stringToNasal(c, wpt->name()); + else if (!strcmp(fieldName, "guid")) + *out = naNum(wpt->guid()); + else { + return 0; + } + + return ""; +} + static bool hashIsCoord(naRef h) { @@ -540,6 +604,11 @@ int geodFromArgs(naRef* args, int offset, int argc, SGGeod& result) return 1; } + if (gt == &POIGhostType) { + result = poiGhost(args[offset])->geod(); + return 1; + } + auto wp = wayptGhost(args[offset]); if (wp) { result = wp->position(); @@ -1648,6 +1717,79 @@ static naRef f_tileIndex(naContext c, naRef me, int argc, naRef* args) return naNum(b.gen_index()); } +static naRef f_createWaypoint(naContext c, naRef me, int argc, naRef* args) +{ + if ((argc < 3) || !naIsString(args[0]) || !naIsString(args[1])) { + naRuntimeError(c, "createWaypoint: expects type and ident as first two args"); + } + + std::string typeSpec(naStr_data(args[0])); + std::string ident(naStr_data(args[1])); + std::string name; + + FGPositioned::Type wptType = FGPositioned::typeFromName(typeSpec); + + SGGeod pos; + int argOffset = geodFromArgs(args, 2, argc, pos) + 2; + if ((argc > argOffset) && naIsString(args[argOffset])) { + name = naStr_data(args[argOffset]); + ++argOffset; + } + + bool isTemporary = false; + if ((argc > argOffset) && naIsNum(args[argOffset])) { + isTemporary = (args[argOffset].num > 0); + } + + try { + auto wpt = FGPositioned::createWaypoint(wptType, ident, pos, isTemporary, name); + if (wptType == FGPositioned::FIX) { + return ghostForFix(c, fgpositioned_cast(wpt)); + } + + auto poi = fgpositioned_cast(wpt); + return ghostForPOI(c, poi); + } catch (std::exception& e) { + naRuntimeError(c, "Failed to create waypoint: %s", e.what()); + } + + return naNil(); +} + + +static naRef f_deleteWaypoint(naContext c, naRef me, int argc, naRef* args) +{ + if (argc < 1) { + naRuntimeError(c, "deleteWaypoint: missing argument"); + } + + FGPositioned* wpt = poiGhost(args[0]); + if (!wpt && naIsNum(args[0])) { + PositionedID guid = static_cast(args[0].num); + wpt = NavDataCache::instance()->loadById(guid); + if (!POI::isType(wpt->type())) { + naRuntimeError(c, "deleteWaypoint: only waypoints can be deleted for now"); + } + } + + FGPositioned::deleteWaypoint(wpt); + // note wpt is dead here, don't access + return naNil(); +} + +static naRef f_waypoint_move(naContext c, naRef me, int argc, naRef* args) +{ + POI* wpt = poiGhost(me); + if (!wpt) { + naRuntimeError(c, "waypoint.move called on non-POI object"); + } + + SGGeod pos; + geodFromArgs(args, 0, argc, pos); + + NavDataCache::instance()->updatePosition(wpt->guid(), pos); + return naNil(); +} void shutdownNasalPositioned() { @@ -1711,6 +1853,8 @@ static struct { {"greatCircleMove", f_greatCircleMove}, {"tileIndex", f_tileIndex}, {"tilePath", f_tilePath}, + {"createWaypoint", f_createWaypoint}, + {"deleteWaypoint", f_deleteWaypoint}, {0, 0}}; @@ -1740,6 +1884,10 @@ naRef initNasalPositioned(naRef globals, naContext c) hashset(c, airportPrototype, "findBestRunwayForPos", naNewFunc(c, naNewCCode(c, f_airport_findBestRunway))); hashset(c, airportPrototype, "tostring", naNewFunc(c, naNewCCode(c, f_airport_toString))); + waypointPrototype = naNewHash(c); + naSave(c, waypointPrototype); + hashset(c, waypointPrototype, "move", naNewFunc(c, naNewCCode(c, f_waypoint_move))); + for(int i=0; funcs[i].name; i++) { hashset(c, globals, funcs[i].name, naNewFunc(c, naNewCCode(c, funcs[i].func))); diff --git a/src/Scripting/NasalPositioned.hxx b/src/Scripting/NasalPositioned.hxx index fbc217a87..8659a58a9 100644 --- a/src/Scripting/NasalPositioned.hxx +++ b/src/Scripting/NasalPositioned.hxx @@ -18,8 +18,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -#ifndef SCRIPTING_NASAL_POSITIONED_HXX -#define SCRIPTING_NASAL_POSITIONED_HXX +#pragma once #include @@ -48,5 +47,3 @@ naRef initNasalPositioned(naRef globals, naContext c); naRef initNasalPositioned_cppbind(naRef globals, naContext c); void postinitNasalPositioned(naRef globals, naContext c); void shutdownNasalPositioned(); - -#endif // of SCRIPTING_NASAL_POSITIONED_HXX diff --git a/test_suite/FGTestApi/testGlobals.cxx b/test_suite/FGTestApi/testGlobals.cxx index 8c9587fd0..b9f1d69fd 100644 --- a/test_suite/FGTestApi/testGlobals.cxx +++ b/test_suite/FGTestApi/testGlobals.cxx @@ -459,6 +459,12 @@ SGPropertyNode_ptr propsFromString(const std::string& s) return m; } +bool geodsApproximatelyEqual(const SGGeod& a, const SGGeod& b) +{ + return SGGeodesy::distanceM(a, b) < 50.0; +} + + namespace tearDown { void shutdownTestGlobals() diff --git a/test_suite/FGTestApi/testGlobals.hxx b/test_suite/FGTestApi/testGlobals.hxx index f8bd4e371..f4e1a2ff7 100644 --- a/test_suite/FGTestApi/testGlobals.hxx +++ b/test_suite/FGTestApi/testGlobals.hxx @@ -70,7 +70,9 @@ void writeGeodsToKML(const std::string &label, const flightgear::SGGeodVec& geod void writePointToKML(const std::string& ident, const SGGeod& pos); bool executeNasal(const std::string& code); - + +bool geodsApproximatelyEqual(const SGGeod& a, const SGGeod& b); + namespace tearDown { void shutdownTestGlobals(); diff --git a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx index 5c5469e78..e9426b9fb 100644 --- a/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx +++ b/test_suite/unit_tests/Instrumentation/test_rnav_procedures.cxx @@ -501,6 +501,8 @@ void RNAVProcedureTests::testEGPH_TLA6C() CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, fp->legAtIndex(5)->waypoint()->ident()); CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")}); + + SG_UNUSED(elapsed); } void RNAVProcedureTests::testLFKC_AJO1R() diff --git a/test_suite/unit_tests/Navaids/test_navaids2.cxx b/test_suite/unit_tests/Navaids/test_navaids2.cxx index 5743be1b6..8eec74153 100644 --- a/test_suite/unit_tests/Navaids/test_navaids2.cxx +++ b/test_suite/unit_tests/Navaids/test_navaids2.cxx @@ -3,6 +3,8 @@ #include "test_suite/FGTestApi/testGlobals.hxx" #include "test_suite/FGTestApi/NavDataCache.hxx" +#include + #include #include #include @@ -34,3 +36,113 @@ void NavaidsTests::testBasic() CPPUNIT_ASSERT_EQUAL(tla->get_freq(), 11570); CPPUNIT_ASSERT_EQUAL(tla->get_range(), 130); } + +void NavaidsTests::testCustomWaypoint() +{ + // create a transaction, which we don't commit, to avoid making permanent DB changes + flightgear::NavDataCache::Transaction txn(flightgear::NavDataCache::instance()); + + SGGeod egccPos = SGGeod::fromDeg(-2.27, 53.35); + SGGeod offsetPos = SGGeodesy::direct(egccPos, 45.0, 20.0 * SG_NM_TO_METER); + auto poi = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, + "TEST_WP0", offsetPos, false, "Lovely Waypoint"); + CPPUNIT_ASSERT(poi); + CPPUNIT_ASSERT_EQUAL(poi->ident(), std::string{"TEST_WP0"}); + CPPUNIT_ASSERT_EQUAL(poi->name(), std::string{"Lovely Waypoint"}); + CPPUNIT_ASSERT(poi->guid() > 0); + CPPUNIT_ASSERT_EQUAL(poi->type(), FGPositioned::WAYPOINT); + CPPUNIT_ASSERT(FGTestApi::geodsApproximatelyEqual(offsetPos, poi->geod())); + + // same but a FIX + SGGeod offsetPos2 = SGGeodesy::direct(egccPos, 180.0, 35.0 * SG_NM_TO_METER); + auto fix = FGPositioned::createWaypoint(FGPositioned::FIX, + "TEST_WP1", offsetPos2); + CPPUNIT_ASSERT_EQUAL(fix->ident(), std::string{"TEST_WP1"}); + CPPUNIT_ASSERT_EQUAL(fix->name(), fix->ident()); + CPPUNIT_ASSERT(fix->guid() > 0); + CPPUNIT_ASSERT_EQUAL(fix->type(), FGPositioned::FIX); + CPPUNIT_ASSERT(FGTestApi::geodsApproximatelyEqual(offsetPos2, fix->geod())); + + // create same ident but far away + FGAirportRef vhhh = FGAirport::getByIdent("VHHH"); + + SGGeod pos3 = SGGeodesy::direct(vhhh->geod(), 10.0, 10.0 * SG_NM_TO_METER); + auto poi3 = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, + "TEST_WP0", pos3, false, "Lovely Hong Kong Waypoint"); + CPPUNIT_ASSERT_EQUAL(poi3->ident(), std::string{"TEST_WP0"}); + CPPUNIT_ASSERT_EQUAL(poi3->name(), std::string{"Lovely Hong Kong Waypoint"}); + CPPUNIT_ASSERT(FGTestApi::geodsApproximatelyEqual(pos3, poi3->geod())); + + // create same ident but nearby + SGGeod pos4 = SGGeodesy::direct(egccPos, 270.0, 10.0 * SG_NM_TO_METER); + auto duplicatePoi = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, + "TEST_WP0", pos4, false, "Lovely Waypoint"); + CPPUNIT_ASSERT_EQUAL(duplicatePoi, poi); + + // create with invalid type + CPPUNIT_ASSERT_THROW(FGPositioned::createWaypoint(FGPositioned::VOR, + "TEST_WP99", offsetPos), + std::logic_error); + + + FGPositioned::TypeFilter filt(FGPositioned::WAYPOINT); + FGPositionedList wps = FGPositioned::findAllWithIdent("TEST_WP0", &filt); + CPPUNIT_ASSERT_EQUAL(wps.size(), static_cast(2)); + + CPPUNIT_ASSERT(FGPositioned::deleteWaypoint(poi)); + wps = FGPositioned::findAllWithIdent("TEST_WP0", &filt); + CPPUNIT_ASSERT_EQUAL(wps.size(), static_cast(1)); +} + +void NavaidsTests::testTemporaryWaypoint() +{ + SGGeod egccPos = SGGeod::fromDeg(-2.27, 53.35); + SGGeod offsetPos = SGGeodesy::direct(egccPos, 45.0, 5.0 * SG_NM_TO_METER); + auto poi = FGPositioned::createWaypoint(FGPositioned::WAYPOINT, + "TEST_WP_TEMP0", offsetPos, true, "Lovely Waypoint"); + CPPUNIT_ASSERT(poi); + CPPUNIT_ASSERT_EQUAL(poi->ident(), std::string{"TEST_WP_TEMP0"}); + CPPUNIT_ASSERT_EQUAL(poi->name(), std::string{"Lovely Waypoint"}); + CPPUNIT_ASSERT(poi->guid() < 0); + CPPUNIT_ASSERT_EQUAL(poi->type(), FGPositioned::WAYPOINT); + CPPUNIT_ASSERT(FGTestApi::geodsApproximatelyEqual(offsetPos, poi->geod())); + + FGPositioned::TypeFilter filt(FGPositioned::WAYPOINT); + FGPositionedList wps = FGPositioned::findAllWithIdent("TEST_WP_TEMP0", &filt); + CPPUNIT_ASSERT_EQUAL(wps.size(), static_cast(1)); + CPPUNIT_ASSERT_EQUAL(wps.front(), poi); + + // check temporary points are found spatially + auto closest = FGPositioned::findClosestN(egccPos, 1, 50.0, &filt); + CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast(1)); + CPPUNIT_ASSERT_EQUAL(closest.front(), poi); + + auto byName = FGPositioned::findAllWithName("lovely", &filt, false); + CPPUNIT_ASSERT_EQUAL(byName.size(), static_cast(1)); + CPPUNIT_ASSERT_EQUAL(byName.front(), poi); + + + // check for gross position update working + FGAirportRef vhhh = FGAirport::getByIdent("VHHH"); + // auto asPoi = fgpositioned_cast(poi); + SGGeod newOffsetPos = SGGeodesy::direct(vhhh->geod(), 10.0, 10.0 * SG_NM_TO_METER); + // asPoi->modifyPosition(newOffsetPos); + flightgear::NavDataCache::instance()->updatePosition(poi->guid(), newOffsetPos); + + CPPUNIT_ASSERT(FGTestApi::geodsApproximatelyEqual(newOffsetPos, poi->geod())); + + closest = FGPositioned::findClosestN(egccPos, 1, 50.0, &filt); + CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast(0)); + + closest = FGPositioned::findClosestN(vhhh->geod(), 1, 50.0, &filt); + CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast(1)); + CPPUNIT_ASSERT_EQUAL(closest.front(), poi); + + // delete it + CPPUNIT_ASSERT(FGPositioned::deleteWaypoint(poi)); + wps = FGPositioned::findAllWithIdent("TEST_WP_TEMP0", &filt); + CPPUNIT_ASSERT_EQUAL(wps.size(), static_cast(0)); + + closest = FGPositioned::findClosestN(vhhh->geod(), 1, 50.0, &filt); + CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast(0)); +} diff --git a/test_suite/unit_tests/Navaids/test_navaids2.hxx b/test_suite/unit_tests/Navaids/test_navaids2.hxx index fc982a8dd..f3885ecb8 100644 --- a/test_suite/unit_tests/Navaids/test_navaids2.hxx +++ b/test_suite/unit_tests/Navaids/test_navaids2.hxx @@ -32,6 +32,8 @@ class NavaidsTests : public CppUnit::TestFixture // Set up the test suite. CPPUNIT_TEST_SUITE(NavaidsTests); CPPUNIT_TEST(testBasic); + CPPUNIT_TEST(testCustomWaypoint); + CPPUNIT_TEST(testTemporaryWaypoint); CPPUNIT_TEST_SUITE_END(); public: @@ -43,6 +45,8 @@ public: // The tests. void testBasic(); + void testCustomWaypoint(); + void testTemporaryWaypoint(); }; #endif // _FG_NAVAIDS_UNIT_TESTS_HXX