1
0
Fork 0

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
This commit is contained in:
James Turner 2024-01-10 08:11:42 +00:00
parent c456d18643
commit 6d70f48f73
21 changed files with 666 additions and 286 deletions

View file

@ -16,7 +16,7 @@ using namespace flightgear;
FGTaxiNode::FGTaxiNode(FGPositioned::Type ty, int index, const SGGeod& pos, FGTaxiNode::FGTaxiNode(FGPositioned::Type ty, int index, const SGGeod& pos,
bool aOnRunway, int aHoldType, 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), m_index(index),
isOnRunway(aOnRunway), isOnRunway(aOnRunway),
holdType(aHoldType), holdType(aHoldType),

View file

@ -1333,9 +1333,12 @@ bool FGRouteMgr::commandDefineUserWaypoint(const SGPropertyNode * arg, SGPropert
return false; return false;
} }
const bool temporary = arg->getBoolValue("temporary");
SGGeod pos(SGGeod::fromDeg(arg->getDoubleValue("longitude-deg"), SGGeod pos(SGGeod::fromDeg(arg->getDoubleValue("longitude-deg"),
arg->getDoubleValue("latitude-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; return true;
} }
@ -1347,7 +1350,14 @@ bool FGRouteMgr::commandDeleteUserWaypoint(const SGPropertyNode * arg, SGPropert
return false; 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);
} }

View file

@ -1519,7 +1519,7 @@ void GPS::defineWaypoint()
} }
SG_LOG(SG_INSTR, SG_INFO, "GPS:defineWaypoint: creating waypoint:" << ident); 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.clear();
_searchResults.push_back(wpt); _searchResults.push_back(wpt);
setScratchFromPositioned(wpt.get(), -1); setScratchFromPositioned(wpt.get(), -1);

View file

@ -1,6 +1,6 @@
#pragma once #pragma once
const int SCHEMA_VERSION = 22; const int SCHEMA_VERSION = 25;
#define SCHEMA_SQL \ #define SCHEMA_SQL \
"CREATE TABLE properties (key VARCHAR, value VARCHAR);" \ "CREATE TABLE properties (key VARCHAR, value VARCHAR);" \
@ -32,3 +32,18 @@ const int SCHEMA_VERSION = 22;
"CREATE TABLE airway_edge (network INT,airway INT64,a INT64,b INT64);" \ "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_from ON airway_edge(a);" \
"CREATE INDEX airway_edge_to ON airway_edge(b);" "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;"

View file

@ -329,6 +329,8 @@ public:
reset(checkTables); reset(checkTables);
initTemporaryTables();
readPropertyQuery = prepare("SELECT value FROM properties WHERE key=?"); readPropertyQuery = prepare("SELECT value FROM properties WHERE key=?");
writePropertyQuery = prepare("INSERT INTO properties (key, value) VALUES (?,?)"); writePropertyQuery = prepare("INSERT INTO properties (key, value) VALUES (?,?)");
clearProperty = prepare("DELETE FROM properties WHERE key=?1"); clearProperty = prepare("DELETE FROM properties WHERE key=?1");
@ -549,6 +551,18 @@ public:
} // of commands in scheme loop } // 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() void prepareQueries()
{ {
writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)"); writePropertyMulti = prepare("INSERT INTO properties (key, value) VALUES(?1,?2)");
@ -558,20 +572,20 @@ public:
rollbackTransactionStmt = prepare("ROLLBACK"); 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" #define AND_TYPED "AND type>=?2 AND type <=?3"
statCacheCheck = prepare("SELECT stamp, sha FROM stat_cache WHERE path=?"); statCacheCheck = prepare("SELECT stamp, sha FROM stat_cache WHERE path=?");
stampFileCache = prepare("INSERT OR REPLACE INTO stat_cache " stampFileCache = prepare("INSERT OR REPLACE INTO stat_cache "
"(path, stamp, sha) VALUES (?,?, ?)"); "(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=?"); loadAirportStmt = prepare("SELECT scenery_path, has_metar FROM airport WHERE rowid=?");
loadNavaid = prepare("SELECT range_nm, freq, multiuse, runway, colocated FROM navaid 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=?"); loadCommStation = prepare("SELECT freq_khz, range_nm FROM comm WHERE rowid=?");
loadRunwayStmt = prepare("SELECT heading, length_ft, width_m, surface, displaced_threshold," loadRunwayStmt = prepare("SELECT heading, length_ft, width_m, surface, displaced_threshold,"
"stopway, reciprocal, ils FROM runway WHERE rowid=?1"); "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=" setAirportMetar = prepare("UPDATE airport SET has_metar=?2 WHERE rowid="
@ -588,8 +602,16 @@ public:
"cart_x, cart_y, cart_z)" "cart_x, cart_y, cart_z)"
" VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)"); " 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"); "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 (?, ?, ?)"); insertAirport = prepare("INSERT INTO airport (rowid, scenery_path, has_metar) VALUES (?, ?, ?)");
insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)" insertNavaid = prepare("INSERT INTO navaid (rowid, freq, range_nm, multiuse, runway, colocated)"
" VALUES (?1, ?2, ?3, ?4, ?5, ?6)"); " VALUES (?1, ?2, ?3, ?4, ?5, ?6)");
@ -601,11 +623,11 @@ public:
" VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)"); " VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)");
runwayLengthFtQuery = prepare("SELECT length_ft FROM runway WHERE rowid=?1"); 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 // query statement
findClosestWithIdent = prepare("SELECT rowid FROM positioned WHERE ident=?1 " findClosestWithIdent = prepare("SELECT guid FROM all_positioned WHERE ident=?1 " AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)");
AND_TYPED " ORDER BY distanceCartSqr(cart_x, cart_y, cart_z, ?4, ?5, ?6)");
findCommByFreq = prepare("SELECT positioned.rowid FROM positioned, comm WHERE " findCommByFreq = prepare("SELECT positioned.rowid FROM positioned, comm WHERE "
"positioned.rowid=comm.rowid AND freq_khz=?1 " "positioned.rowid=comm.rowid AND freq_khz=?1 "
@ -649,7 +671,7 @@ public:
sqlite3_bind_int(getAllAirports, 2, FGPositioned::SEAPORT); 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=" 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)"); "(SELECT rowid FROM positioned WHERE type=?4 AND ident=?1)");
@ -839,6 +861,36 @@ public:
return r; 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, FGPositionedList findAllByString(const string& s, const string& column,
FGPositioned::Filter* filter, bool exact) FGPositioned::Filter* filter, bool exact)
{ {
@ -847,7 +899,7 @@ public:
// build up SQL query text // build up SQL query text
string matchTerm = exact ? "=?1" : " LIKE ?1"; 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) { if (filter) {
sql += " " AND_TYPED; sql += " " AND_TYPED;
} }
@ -916,12 +968,12 @@ public:
deferredOctreeUpdates.clear(); deferredOctreeUpdates.clear();
} }
void removePositionedWithIdent(FGPositioned::Type ty, const std::string& aIdent) void removePositioned(PositionedID rowid)
{ {
sqlite3_bind_int(removePOIQuery, 1, ty); auto stmt = rowid < 0 ? removeTempPosQuery : removePositionedQuery;
sqlite_bind_stdstring(removePOIQuery, 2, aIdent); sqlite3_bind_int64(stmt, 1, rowid);
execUpdate(removePOIQuery); execUpdate(stmt);
reset(removePOIQuery); reset(stmt);
} }
NavDataCache* outer; NavDataCache* outer;
@ -959,9 +1011,10 @@ public:
sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway, sqlite3_stmt_ptr insertPositionedQuery, insertAirport, insertTower, insertRunway,
insertCommStation, insertNavaid; insertCommStation, insertNavaid;
sqlite3_stmt_ptr insertTempPosQuery;
sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, setNavaidColocated, sqlite3_stmt_ptr setAirportMetar, setRunwayReciprocal, setRunwayILS, setNavaidColocated,
setAirportPos; updatePosition, updateTempPos;
sqlite3_stmt_ptr removePOIQuery; sqlite3_stmt_ptr removePositionedQuery, removeTempPosQuery;
sqlite3_stmt_ptr findClosestWithIdent; sqlite3_stmt_ptr findClosestWithIdent;
// octree (spatial index) related queries // octree (spatial index) related queries
@ -996,6 +1049,10 @@ public:
// if we're performing a rebuild, the thread that is doing the work. // if we're performing a rebuild, the thread that is doing the work.
// otherwise, NULL // otherwise, NULL
std::unique_ptr<RebuildThread> rebuilder; std::unique_ptr<RebuildThread> 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,8 +1115,8 @@ FGPositioned* NavDataCache::NavDataCachePrivate::loadById(sqlite3_int64 rowid,
case FGPositioned::CITY: case FGPositioned::CITY:
case FGPositioned::TOWN: case FGPositioned::TOWN:
case FGPositioned::VILLAGE: case FGPositioned::VILLAGE:
{ case FGPositioned::VISUAL_REPORTING_POINT: {
FGPositioned* wpt = new FGPositioned(rowid, ty, ident, pos); FGPositioned* wpt = new POI(rowid, ty, ident, pos, name);
return wpt; return wpt;
} }
@ -2009,14 +2066,14 @@ void NavDataCache::clearDynamicPositioneds()
FGPositionedRef NavDataCache::loadById(PositionedID rowid) FGPositionedRef NavDataCache::loadById(PositionedID rowid)
{ {
if (rowid < 1) { if (!d || (rowid == 0)) {
return NULL; return {};
} };
if (!d) return NULL;
PositionedCache::iterator it = d->cache.find(rowid); PositionedCache::iterator it = d->cache.find(rowid);
if (it != d->cache.end()) { if (it != d->cache.end()) {
d->cacheHits++; d->cacheHits++;
return it->second; // cache it return it->second; // cache hit
} }
sqlite3_int64 aptId; sqlite3_int64 aptId;
@ -2060,35 +2117,62 @@ PositionedID NavDataCache::insertAirport(FGPositioned::Type ty, const string& id
void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos) void NavDataCache::updatePosition(PositionedID item, const SGGeod &pos)
{ {
if (d->cache.find(item) != d->cache.end()) { const bool isTemporary = (item < 0);
SG_LOG(SG_NAVCACHE, SG_DEBUG, "updating position of an item in the cache"); SGVec3d cartPos(SGVec3d::fromGeod(pos));
auto it = d->cache.find(item);
// 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
SGVec3d oldCartPos(SGVec3d::fromGeod(it->second->geod()));
const auto ty = it->second->type();
Octree::Leaf* oldOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(oldCartPos);
Octree::Leaf* newOctreeLeaf = Octree::globalTransientOctree()->findLeafForPos(cartPos);
// 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);
}
if (it != d->cache.end()) {
d->cache[item]->modifyPosition(pos); d->cache[item]->modifyPosition(pos);
} }
SGVec3d cartPos(SGVec3d::fromGeod(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());
sqlite3_bind_int(d->setAirportPos, 1, item); if (!isTemporary) {
sqlite3_bind_double(d->setAirportPos, 2, pos.getLongitudeDeg()); // bug 905; the octree leaf may change here, but the leaf may already be
sqlite3_bind_double(d->setAirportPos, 3, pos.getLatitudeDeg()); // loaded, and caching its children. (Either the old or new leaf!). Worse,
sqlite3_bind_double(d->setAirportPos, 4, pos.getElevationM()); // 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
// bug 905; the octree leaf may change here, but the leaf may already be // the in-memory leaf's STL child container, we simply leave the runtime
// loaded, and caching its children. (Either the old or new leaf!). Worse, // structures alone. This is fine providing items do no move very far, since
// we may be called here as a result of loading one of those leaf's children. // all the spatial searches ultimately use the items' real cartesian position,
// instead of dealing with all those possibilites, such as modifying // which was updated above.
// 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); Octree::Leaf* octreeLeaf = Octree::globalPersistentOctree()->findLeafForPos(cartPos);
sqlite3_bind_int64(d->setAirportPos, 5, octreeLeaf->guid()); sqlite3_bind_int64(stmt, 5, octreeLeaf->guid());
}
sqlite3_bind_double(d->setAirportPos, 6, cartPos.x()); sqlite3_bind_double(stmt, 6, cartPos.x());
sqlite3_bind_double(d->setAirportPos, 7, cartPos.y()); sqlite3_bind_double(stmt, 7, cartPos.y());
sqlite3_bind_double(d->setAirportPos, 8, cartPos.z()); sqlite3_bind_double(stmt, 8, cartPos.z());
d->execUpdate(d->setAirportPos); d->execUpdate(stmt);
} }
void NavDataCache::insertTower(PositionedID airportId, const SGGeod& pos) void NavDataCache::insertTower(PositionedID airportId, const SGGeod& pos)
@ -2215,21 +2299,32 @@ PositionedID NavDataCache::insertCommStation(FGPositioned::Type ty,
return d->execInsert(d->insertCommStation); 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,
PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos)
{
return d->insertPositioned(ty, ident, string(), aPos, 0,
true /* spatial index */); true /* spatial index */);
} else {
return d->insertPositioned(ty, ident, name, aPos, 0,
true /* spatial index */);
}
} }
bool NavDataCache::removePOI(FGPositioned::Type ty, const std::string& aIdent) bool NavDataCache::removePOI(FGPositionedRef ref)
{ {
d->removePositionedWithIdent(ty, aIdent); const bool isTemporary = ref->guid() < 0;
// should remove from the live cache too? // remove from the octree if temporary.
if (isTemporary) {
auto octreeLeaf = Octree::globalTransientOctree()->findLeafForPos(ref->cart());
octreeLeaf->removeChild(ref->guid());
}
d->removePositioned(ref->guid());
auto it = d->cache.find(ref->guid());
d->cache.erase(it);
return true; return true;
} }
@ -2811,6 +2906,11 @@ SGPath NavDataCache::path() const
return d->path; return d->path;
} }
PositionedID NavDataCache::createTransientID()
{
return d->nextTransientId--;
}
///////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////
// Transaction RAII object // Transaction RAII object

View file

@ -182,11 +182,10 @@ public:
PositionedID insertCommStation(FGPositioned::Type ty, PositionedID insertCommStation(FGPositioned::Type ty,
const std::string& name, const SGGeod& pos, int freq, int range, const std::string& name, const SGGeod& pos, int freq, int range,
PositionedID apt); 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 /// update the metar flag associated with an airport
void setAirportMetar(const std::string& icao, bool hasMetar); void setAirportMetar(const std::string& icao, bool hasMetar);
@ -336,6 +335,8 @@ public:
void clearDynamicPositioneds(); void clearDynamicPositioneds();
PositionedID createTransientID();
private: private:
NavDataCache(); NavDataCache();

View file

@ -1,26 +1,9 @@
/** /*
* PositionedOctree - define a spatial octree containing Positioned items * SPDX-FileCopyrightText: (C) 2012 James Turner <james@flightgear.org>
* arranged by their global cartesian position. * 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 "config.h"
#include "PositionedOctree.hxx" #include "PositionedOctree.hxx"
@ -152,6 +135,20 @@ void Leaf::insertChild(FGPositioned::Type ty, PositionedID id)
children.insert(children.end(), TypedPositioned(ty, 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() void Leaf::loadChildren()
{ {
if (_childrenLoaded) { if (_childrenLoaded) {
@ -337,6 +334,7 @@ bool findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPosit
FindNearestPQueue pq; FindNearestPQueue pq;
FindNearestResults results; FindNearestResults results;
pq.push(Ordered<Node*>(globalPersistentOctree(), 0)); pq.push(Ordered<Node*>(globalPersistentOctree(), 0));
pq.push(Ordered<Node*>(globalTransientOctree(), 0));
double cut = aCutoffM; double cut = aCutoffM;
SGTimeStamp tm; SGTimeStamp tm;
@ -378,6 +376,7 @@ bool findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filte
FindNearestPQueue pq; FindNearestPQueue pq;
FindNearestResults results; FindNearestResults results;
pq.push(Ordered<Node*>(globalPersistentOctree(), 0)); pq.push(Ordered<Node*>(globalPersistentOctree(), 0));
pq.push(Ordered<Node*>(globalTransientOctree(), 0));
double rng = aRangeM; double rng = aRangeM;
SGTimeStamp tm; SGTimeStamp tm;

View file

@ -1,28 +1,10 @@
/** /*
* PositionedOctree - define a spatial octree containing Positioned items * SPDX-FileCopyrightText: (C) 2012 James Turner <james@flightgear.org>
* arranged by their global cartesian position. * SPDX_FileComment: define a spatial octree containing Positioned items
* SPDX-License-Identifier: GPL-2.0-or-later
*/ */
// Written by James Turner, started 2012. #pragma once
//
// 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
// std // std
#include <array> #include <array>
@ -193,8 +175,9 @@ namespace Octree
} }
void insertChild(FGPositioned::Type ty, PositionedID id); void insertChild(FGPositioned::Type ty, PositionedID id);
void removeChild(PositionedID id);
private: private:
bool _childrenLoaded = false; bool _childrenLoaded = false;
typedef std::multimap<FGPositioned::Type, PositionedID> ChildMap; typedef std::multimap<FGPositioned::Type, PositionedID> ChildMap;
@ -253,4 +236,3 @@ namespace Octree
} // of namespace flightgear } // of namespace flightgear
#endif // of FG_POSITIONED_OCTREE_HXX

View file

@ -422,12 +422,12 @@ void Airway::Network::addEdge(int aWay, const SGGeod& aStartPos,
if (!start) { if (!start) {
SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways start pt: '" << aStartIdent << "'"); 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) { if (!end) {
SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways end pt: '" << aEndIdent << "'"); 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()); NavDataCache::instance()->insertEdge(_networkID, aWay, start->guid(), end->guid());

View file

@ -145,7 +145,7 @@ void FixesLoader::loadFixes(const NavDataCache::SceneryLocation& sceneryLocation
} }
if (!duplicate) { if (!duplicate) {
_cache->insertFix(ident, pos); _cache->createPOI(FGPositioned::FIX, ident, pos, {}, false);
_loadedFixes.insert({ident, pos}); _loadedFixes.insert({ident, pos});
} }

View file

@ -1,26 +1,10 @@
// poidb.cxx -- points of interest management routines /*
// * SPDX-FileCopyrightText: (C) Christian Schmitt, March 2013
// Written by Christian Schmitt, March 2013 * SPDX_FileComment: points of interest management routines
// * SPDX-License-Identifier: GPL-2.0-or-later
// 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$
#ifdef HAVE_CONFIG_H #include "config.h"
# include "config.h"
#endif
#include <istream> // std::ws #include <istream> // std::ws
#include "poidb.hxx" #include "poidb.hxx"
@ -45,6 +29,10 @@ mapPOITypeToFGPType(int aTy)
case 12: return FGPositioned::CITY; case 12: return FGPositioned::CITY;
case 13: return FGPositioned::TOWN; case 13: return FGPositioned::TOWN;
case 14: return FGPositioned::VILLAGE; case 14: return FGPositioned::VILLAGE;
case 1000: return FGPositioned::VISUAL_REPORTING_POINT;
case 1001: return FGPositioned::WAYPOINT;
default: default:
throw sg_range_exception("Unknown POI type", "FGNavDataCache::readPOIFromStream"); throw sg_range_exception("Unknown POI type", "FGNavDataCache::readPOIFromStream");
} }
@ -87,7 +75,7 @@ static PositionedID readPOIFromStream(std::istream& aStream, NavDataCache* cache
return 0; return 0;
} }
return cache->createPOI(type, name, pos); return cache->createPOI(type, name, pos, name, false);
} }
// load and initialize the POI database // load and initialize the POI database

View file

@ -1,26 +1,10 @@
// poidb.cxx -- points of interest management routines /*
// * SPDX-FileCopyrightText: (C) Christian Schmitt, March 2013
// Written by Christian Schmitt, March 2013 * SPDX_FileComment: points of interest management routines
// * SPDX-License-Identifier: GPL-2.0-or-later
// 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$
#pragma once
#ifndef _FG_POIDB_HXX
#define _FG_POIDB_HXX
#include <simgear/compiler.h> #include <simgear/compiler.h>
@ -37,4 +21,3 @@ bool poiDBInit(const SGPath& path);
} // of namespace flightgear } // of namespace flightgear
#endif // _FG_NAVDB_HXX

View file

@ -1,22 +1,8 @@
// positioned.cxx - base class for objects which are positioned /*
// * SPDX-FileCopyrightText: (C) 2008 James Turner <james@flightgear.org>
// Copyright (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
// 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$
#include "config.h" #include "config.h"
@ -60,8 +46,6 @@ static bool validateFilter(FGPositioned::Filter* filter)
return true; return true;
} }
const PositionedID FGPositioned::TRANSIENT_ID = -2;
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
FGPositioned::FGPositioned( PositionedID aGuid, 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 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(); NavDataCache* cache = NavDataCache::instance();
TypeFilter filter(WAYPOINT);
FGPositionedList existing = cache->findAllWithIdent(aIdent, &filter, true); if (!isValidCustomWaypointType(aType)) {
if (!existing.empty()) { throw std::logic_error(std::string{"Create waypoint: not allowed for type:"} + nameForType(aType));
SG_LOG(SG_NAVAID, SG_WARN, "attempt to insert duplicate WAYPOINT:" << aIdent);
return existing.front();
} }
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); return cache->loadById(id);
} }
bool FGPositioned::deleteUserWaypoint(const std::string& aIdent) bool FGPositioned::deleteWaypoint(FGPositionedRef ref)
{ {
NavDataCache* cache = NavDataCache::instance(); 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);
} }
@ -192,7 +206,8 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
{"mobile-tacan", MOBILE_TACAN}, {"mobile-tacan", MOBILE_TACAN},
{"obstacle", OBSTACLE}, {"obstacle", OBSTACLE},
{"parking", PARKING}, {"parking", PARKING},
{"taxi-node",TAXI_NODE}, {"taxi-node", TAXI_NODE},
{"visual-reporting-point", VISUAL_REPORTING_POINT},
// aliases // aliases
{"localizer", LOC}, {"localizer", LOC},
@ -208,9 +223,9 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
{"middle-marker", MM}, {"middle-marker", MM},
{"inner-marker", IM}, {"inner-marker", IM},
{"parking-stand", PARKING}, {"parking-stand", PARKING},
{"vrp", VISUAL_REPORTING_POINT},
{NULL, INVALID} {NULL, INVALID}};
};
std::string lowerName = simgear::strutils::lowercase(aName); std::string lowerName = simgear::strutils::lowercase(aName);
@ -259,6 +274,9 @@ const char* FGPositioned::nameForType(Type aTy)
case CITY: return "city"; case CITY: return "city";
case TOWN: return "town"; case TOWN: return "town";
case VILLAGE: return "village"; case VILLAGE: return "village";
case VISUAL_REPORTING_POINT: return "visual-reporting-point";
case MOBILE_TACAN: return "mobile-tacan";
case OBSTACLE: return "obstacle";
default: default:
return "unknown"; return "unknown";
} }
@ -493,3 +511,13 @@ FGPositioned::TypeFilter::pass(FGPositioned* aPos) const
return false; 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));
}

View file

@ -1,25 +1,10 @@
// positioned.hxx - base class for objects which are positioned /*
// * SPDX-FileCopyrightText: (C) 2008 James Turner <james@flightgear.org>
// Copyright (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
// 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$
#ifndef FG_POSITIONED_HXX #pragma once
#define FG_POSITIONED_HXX
#include <cassert> #include <cassert>
#include <string> #include <string>
@ -42,8 +27,6 @@ namespace flightgear { class NavDataCache; }
class FGPositioned : public SGReferenced class FGPositioned : public SGReferenced
{ {
public: public:
static const PositionedID TRANSIENT_ID;
typedef enum { typedef enum {
INVALID = 0, INVALID = 0,
AIRPORT, AIRPORT,
@ -91,7 +74,8 @@ public:
CITY, CITY,
TOWN, TOWN,
VILLAGE, VILLAGE,
VISUAL_REPORTING_POINT,
LAST_POI_TYPE,
LAST_TYPE LAST_TYPE
} Type; } Type;
@ -295,13 +279,16 @@ public:
*/ */
static const char* nameForType(Type aTy); static const char* nameForType(Type aTy);
static FGPositionedRef createUserWaypoint(const std::string& aIdent, const SGGeod& aPos); static FGPositionedRef createWaypoint(FGPositioned::Type aType, const std::string& aIdent, const SGGeod& aPos,
static bool deleteUserWaypoint(const std::string& aIdent); bool isTemporary = false,
const std::string& aName = {});
static bool deleteWaypoint(FGPositionedRef aWpt);
protected: protected:
friend class flightgear::NavDataCache; friend class flightgear::NavDataCache;
FGPositioned(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos); FGPositioned(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos);
// called by NavDataCache::updatePosition
void modifyPosition(const SGGeod& newPos); void modifyPosition(const SGGeod& newPos);
void invalidatePosition(); void invalidatePosition();
@ -318,6 +305,8 @@ private:
const SGVec3d mCart; const SGVec3d mCart;
}; };
template <class T> template <class T>
T* fgpositioned_cast(FGPositioned* p) T* fgpositioned_cast(FGPositioned* p)
{ {
@ -340,4 +329,18 @@ T* fgpositioned_cast(FGPositionedRef p)
return nullptr; 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;
};

View file

@ -72,6 +72,9 @@ static naGhostType FixGhostType = { positionedGhostDestroy, "fix", fixGhostGetMe
static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* out); static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef* out);
static naGhostType CommGhostType = {positionedGhostDestroy, "comm", commGhostGetMember, nullptr}; 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) static void hashset(naContext c, naRef hash, const char* key, naRef val)
{ {
naRef s = naNewString(c); naRef s = naNewString(c);
@ -92,7 +95,8 @@ FGPositioned* positionedGhost(naRef r)
(naGhost_type(r) == &NavaidGhostType) || (naGhost_type(r) == &NavaidGhostType) ||
(naGhost_type(r) == &RunwayGhostType) || (naGhost_type(r) == &RunwayGhostType) ||
(naGhost_type(r) == &FixGhostType) || (naGhost_type(r) == &FixGhostType) ||
(naGhost_type(r) == &CommGhostType)) { (naGhost_type(r) == &FixGhostType) ||
(naGhost_type(r) == &POIGhostType)) {
return (FGPositioned*) naGhost_ptr(r); return (FGPositioned*) naGhost_ptr(r);
} }
@ -134,12 +138,22 @@ static FGFix* fixGhost(naRef r)
return 0; return 0;
} }
#if 0
static flightgear::CommStation* commGhost(naRef r) static flightgear::CommStation* commGhost(naRef r)
{ {
if (naGhost_type(r) == &CommGhostType) if (naGhost_type(r) == &CommGhostType)
return (flightgear::CommStation*)naGhost_ptr(r); return (flightgear::CommStation*)naGhost_ptr(r);
return nullptr; 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) static void positionedGhostDestroy(void* g)
{ {
@ -151,6 +165,7 @@ static void positionedGhostDestroy(void* g)
static naRef airportPrototype; static naRef airportPrototype;
static naRef geoCoordClass; static naRef geoCoordClass;
static naRef waypointPrototype;
naRef ghostForAirport(naContext c, const FGAirport* apt) 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); 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) naRef ghostForPositioned(naContext c, FGPositionedRef pos)
{ {
if (!pos) { if (!pos) {
@ -251,6 +276,14 @@ naRef ghostForPositioned(naContext c, FGPositionedRef pos)
case FGPositioned::FREQ_ENROUTE: case FGPositioned::FREQ_ENROUTE:
return ghostForComm(c, fgpositioned_cast<flightgear::CommStation>(pos)); return ghostForComm(c, fgpositioned_cast<flightgear::CommStation>(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<POI>(pos));
default: default:
SG_LOG(SG_NASAL, SG_DEV_ALERT, "Type lacks Nasal ghost mapping:" << pos->typeString()); SG_LOG(SG_NASAL, SG_DEV_ALERT, "Type lacks Nasal ghost mapping:" << pos->typeString());
return naNil(); 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() // for homogenity with other values returned by navinfo()
else if (!strcmp(fieldName, "type")) *out = stringToNasal(c, "fix"); else if (!strcmp(fieldName, "type")) *out = stringToNasal(c, "fix");
else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, fix->ident()); else if (!strcmp(fieldName, "name")) *out = stringToNasal(c, fix->ident());
else if (!strcmp(fieldName, "guid"))
*out = naNum(fix->guid());
else { else {
return 0; return 0;
} }
@ -433,6 +468,8 @@ static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef*
*out = stringToNasal(c, comm->name()); *out = stringToNasal(c, comm->name());
else if (!strcmp(fieldName, "frequency")) { else if (!strcmp(fieldName, "frequency")) {
*out = naNum(comm->freqMHz()); *out = naNum(comm->freqMHz());
} else if (!strcmp(fieldName, "guid")) {
*out = naNum(comm->guid());
} else { } else {
return 0; return 0;
} }
@ -440,6 +477,33 @@ static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef*
return ""; 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) static bool hashIsCoord(naRef h)
{ {
@ -540,6 +604,11 @@ int geodFromArgs(naRef* args, int offset, int argc, SGGeod& result)
return 1; return 1;
} }
if (gt == &POIGhostType) {
result = poiGhost(args[offset])->geod();
return 1;
}
auto wp = wayptGhost(args[offset]); auto wp = wayptGhost(args[offset]);
if (wp) { if (wp) {
result = wp->position(); result = wp->position();
@ -1648,6 +1717,79 @@ static naRef f_tileIndex(naContext c, naRef me, int argc, naRef* args)
return naNum(b.gen_index()); 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<FGFix>(wpt));
}
auto poi = fgpositioned_cast<POI>(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<PositionedID>(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() void shutdownNasalPositioned()
{ {
@ -1711,6 +1853,8 @@ static struct {
{"greatCircleMove", f_greatCircleMove}, {"greatCircleMove", f_greatCircleMove},
{"tileIndex", f_tileIndex}, {"tileIndex", f_tileIndex},
{"tilePath", f_tilePath}, {"tilePath", f_tilePath},
{"createWaypoint", f_createWaypoint},
{"deleteWaypoint", f_deleteWaypoint},
{0, 0}}; {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, "findBestRunwayForPos", naNewFunc(c, naNewCCode(c, f_airport_findBestRunway)));
hashset(c, airportPrototype, "tostring", naNewFunc(c, naNewCCode(c, f_airport_toString))); 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++) { for(int i=0; funcs[i].name; i++) {
hashset(c, globals, funcs[i].name, hashset(c, globals, funcs[i].name,
naNewFunc(c, naNewCCode(c, funcs[i].func))); naNewFunc(c, naNewCCode(c, funcs[i].func)));

View file

@ -18,8 +18,7 @@
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef SCRIPTING_NASAL_POSITIONED_HXX #pragma once
#define SCRIPTING_NASAL_POSITIONED_HXX
#include <simgear/nasal/nasal.h> #include <simgear/nasal/nasal.h>
@ -48,5 +47,3 @@ naRef initNasalPositioned(naRef globals, naContext c);
naRef initNasalPositioned_cppbind(naRef globals, naContext c); naRef initNasalPositioned_cppbind(naRef globals, naContext c);
void postinitNasalPositioned(naRef globals, naContext c); void postinitNasalPositioned(naRef globals, naContext c);
void shutdownNasalPositioned(); void shutdownNasalPositioned();
#endif // of SCRIPTING_NASAL_POSITIONED_HXX

View file

@ -459,6 +459,12 @@ SGPropertyNode_ptr propsFromString(const std::string& s)
return m; return m;
} }
bool geodsApproximatelyEqual(const SGGeod& a, const SGGeod& b)
{
return SGGeodesy::distanceM(a, b) < 50.0;
}
namespace tearDown { namespace tearDown {
void shutdownTestGlobals() void shutdownTestGlobals()

View file

@ -71,6 +71,8 @@ void writePointToKML(const std::string& ident, const SGGeod& pos);
bool executeNasal(const std::string& code); bool executeNasal(const std::string& code);
bool geodsApproximatelyEqual(const SGGeod& a, const SGGeod& b);
namespace tearDown { namespace tearDown {
void shutdownTestGlobals(); void shutdownTestGlobals();

View file

@ -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"}, fp->legAtIndex(5)->waypoint()->ident());
CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")}); CPPUNIT_ASSERT_EQUAL(std::string{"TLA"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
SG_UNUSED(elapsed);
} }
void RNAVProcedureTests::testLFKC_AJO1R() void RNAVProcedureTests::testLFKC_AJO1R()

View file

@ -3,6 +3,8 @@
#include "test_suite/FGTestApi/testGlobals.hxx" #include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx" #include "test_suite/FGTestApi/NavDataCache.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx> #include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx> #include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx> #include <Navaids/navlist.hxx>
@ -34,3 +36,113 @@ void NavaidsTests::testBasic()
CPPUNIT_ASSERT_EQUAL(tla->get_freq(), 11570); CPPUNIT_ASSERT_EQUAL(tla->get_freq(), 11570);
CPPUNIT_ASSERT_EQUAL(tla->get_range(), 130); 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<size_t>(2));
CPPUNIT_ASSERT(FGPositioned::deleteWaypoint(poi));
wps = FGPositioned::findAllWithIdent("TEST_WP0", &filt);
CPPUNIT_ASSERT_EQUAL(wps.size(), static_cast<size_t>(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<size_t>(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<size_t>(1));
CPPUNIT_ASSERT_EQUAL(closest.front(), poi);
auto byName = FGPositioned::findAllWithName("lovely", &filt, false);
CPPUNIT_ASSERT_EQUAL(byName.size(), static_cast<size_t>(1));
CPPUNIT_ASSERT_EQUAL(byName.front(), poi);
// check for gross position update working
FGAirportRef vhhh = FGAirport::getByIdent("VHHH");
// auto asPoi = fgpositioned_cast<POI>(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<size_t>(0));
closest = FGPositioned::findClosestN(vhhh->geod(), 1, 50.0, &filt);
CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast<size_t>(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<size_t>(0));
closest = FGPositioned::findClosestN(vhhh->geod(), 1, 50.0, &filt);
CPPUNIT_ASSERT_EQUAL(closest.size(), static_cast<size_t>(0));
}

View file

@ -32,6 +32,8 @@ class NavaidsTests : public CppUnit::TestFixture
// Set up the test suite. // Set up the test suite.
CPPUNIT_TEST_SUITE(NavaidsTests); CPPUNIT_TEST_SUITE(NavaidsTests);
CPPUNIT_TEST(testBasic); CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testCustomWaypoint);
CPPUNIT_TEST(testTemporaryWaypoint);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
public: public:
@ -43,6 +45,8 @@ public:
// The tests. // The tests.
void testBasic(); void testBasic();
void testCustomWaypoint();
void testTemporaryWaypoint();
}; };
#endif // _FG_NAVAIDS_UNIT_TESTS_HXX #endif // _FG_NAVAIDS_UNIT_TESTS_HXX