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,
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),

View file

@ -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;
}
@ -1347,7 +1350,14 @@ bool FGRouteMgr::commandDeleteUserWaypoint(const SGPropertyNode * arg, SGPropert
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);
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);

View file

@ -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);" \
@ -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;"

View file

@ -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)");
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<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::TOWN:
case FGPositioned::VILLAGE:
{
FGPositioned* wpt = new FGPositioned(rowid, ty, ident, pos);
case FGPositioned::VISUAL_REPORTING_POINT: {
FGPositioned* wpt = new POI(rowid, ty, ident, pos, name);
return wpt;
}
@ -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");
const bool isTemporary = (item < 0);
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);
}
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);
sqlite3_bind_double(d->setAirportPos, 2, pos.getLongitudeDeg());
sqlite3_bind_double(d->setAirportPos, 3, pos.getLatitudeDeg());
sqlite3_bind_double(d->setAirportPos, 4, pos.getElevationM());
// bug 905; the octree leaf may change here, but the leaf may already be
// loaded, and caching its children. (Either the old or new leaf!). Worse,
// we may be called here as a result of loading one of those leaf's children.
// instead of dealing with all those possibilites, such as modifying
// the in-memory leaf's STL child container, we simply leave the runtime
// structures alone. This is fine providing items do no move very far, since
// all the spatial searches ultimately use the items' real cartesian position,
// which was updated above.
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(d->setAirportPos, 5, octreeLeaf->guid());
sqlite3_bind_int64(stmt, 5, octreeLeaf->guid());
}
sqlite3_bind_double(d->setAirportPos, 6, cartPos.x());
sqlite3_bind_double(d->setAirportPos, 7, cartPos.y());
sqlite3_bind_double(d->setAirportPos, 8, cartPos.z());
sqlite3_bind_double(stmt, 6, cartPos.x());
sqlite3_bind_double(stmt, 7, cartPos.y());
sqlite3_bind_double(stmt, 8, cartPos.z());
d->execUpdate(d->setAirportPos);
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);
}
PositionedID NavDataCache::createPOI(FGPositioned::Type ty, const std::string& ident, const SGGeod& aPos)
{
return d->insertPositioned(ty, ident, string(), aPos, 0,
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 */);
}
}
bool NavDataCache::removePOI(FGPositioned::Type ty, const std::string& aIdent)
bool NavDataCache::removePOI(FGPositionedRef ref)
{
d->removePositionedWithIdent(ty, aIdent);
// should remove from the live cache too?
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());
}
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

View file

@ -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();

View file

@ -1,26 +1,9 @@
/**
* PositionedOctree - define a spatial octree containing Positioned items
* arranged by their global cartesian position.
/*
* SPDX-FileCopyrightText: (C) 2012 James Turner <james@flightgear.org>
* 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<Node*>(globalPersistentOctree(), 0));
pq.push(Ordered<Node*>(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<Node*>(globalPersistentOctree(), 0));
pq.push(Ordered<Node*>(globalTransientOctree(), 0));
double rng = aRangeM;
SGTimeStamp tm;

View file

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

View file

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

View file

@ -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 <istream> // 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

View file

@ -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 <simgear/compiler.h>
@ -37,4 +21,3 @@ bool poiDBInit(const SGPath& path);
} // of namespace flightgear
#endif // _FG_NAVDB_HXX

View file

@ -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 <james@flightgear.org>
* 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);
}
@ -192,7 +206,8 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
{"mobile-tacan", MOBILE_TACAN},
{"obstacle", OBSTACLE},
{"parking", PARKING},
{"taxi-node",TAXI_NODE},
{"taxi-node", TAXI_NODE},
{"visual-reporting-point", VISUAL_REPORTING_POINT},
// aliases
{"localizer", LOC},
@ -208,9 +223,9 @@ FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
{"middle-marker", MM},
{"inner-marker", IM},
{"parking-stand", PARKING},
{"vrp", VISUAL_REPORTING_POINT},
{NULL, INVALID}
};
{NULL, INVALID}};
std::string lowerName = simgear::strutils::lowercase(aName);
@ -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));
}

View file

@ -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 <james@flightgear.org>
* 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 <cassert>
#include <string>
@ -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 <class T>
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;
};

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 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<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:
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,6 +468,8 @@ 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;
}
@ -440,6 +477,33 @@ static const char* commGhostGetMember(naContext c, void* g, naRef field, naRef*
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<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()
{
@ -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)));

View file

@ -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 <simgear/nasal/nasal.h>
@ -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

View file

@ -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()

View file

@ -71,6 +71,8 @@ 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();

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"}, std::string{m_gpsNode->getStringValue("wp/wp[1]/ID")});
SG_UNUSED(elapsed);
}
void RNAVProcedureTests::testLFKC_AJO1R()

View file

@ -3,6 +3,8 @@
#include "test_suite/FGTestApi/testGlobals.hxx"
#include "test_suite/FGTestApi/NavDataCache.hxx"
#include <Airports/airport.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navlist.hxx>
@ -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<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.
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