first commit

This commit is contained in:
Your Name
2022-10-20 20:29:11 +08:00
commit 4d531f8044
3238 changed files with 1387862 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
include(FlightGearComponent)
set(SOURCES
airways.cxx
fixlist.cxx
markerbeacon.cxx
navdb.cxx
navlist.cxx
navrecord.cxx
poidb.cxx
positioned.cxx
procedure.cxx
route.cxx
routePath.cxx
waypoint.cxx
LevelDXML.cxx
FlightPlan.cxx
NavDataCache.cxx
PositionedOctree.cxx
PolyLine.cxx
SHPParser.cxx
)
set(HEADERS
airways.hxx
fixlist.hxx
markerbeacon.hxx
navdb.hxx
navlist.hxx
navrecord.hxx
poidb.hxx
positioned.hxx
procedure.hxx
route.hxx
routePath.hxx
waypoint.hxx
LevelDXML.hxx
FlightPlan.hxx
NavDataCache.hxx
PositionedOctree.hxx
PolyLine.hxx
SHPParser.hxx
CacheSchema.h
)
flightgear_component(Navaids "${SOURCES}" "${HEADERS}")

38
src/Navaids/CacheSchema.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef FG_NAVCACHE_SCHEMA_HXX
#define FG_NAVCACHE_SCHEMA_HXX
const int SCHEMA_VERSION = 20;
#define SCHEMA_SQL \
"CREATE TABLE properties (key VARCHAR, value VARCHAR);" \
"CREATE TABLE stat_cache (path VARCHAR unique, stamp INT);"\
\
"CREATE TABLE 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 INDEX pos_octree ON positioned(octree_node);" \
"CREATE INDEX pos_ident ON positioned(ident collate nocase);" \
"CREATE INDEX pos_name ON positioned(name collate nocase);" \
"CREATE INDEX pos_apt_type ON positioned(airport, type);"\
\
"CREATE TABLE airport (has_metar BOOL);" \
"CREATE TABLE comm (freq_khz INT,range_nm INT);" \
"CREATE INDEX comm_freq ON comm(freq_khz);" \
\
"CREATE TABLE runway (heading FLOAT, length_ft FLOAT, width_m FLOAT," \
"surface INT, displaced_threshold FLOAT,stopway FLOAT,reciprocal INT64,ils INT64);" \
"CREATE TABLE navaid (freq INT,range_nm INT,multiuse FLOAT, runway INT64,colocated INT64);" \
"CREATE INDEX navaid_freq ON navaid(freq);" \
\
"CREATE TABLE octree (children INT);" \
\
"CREATE TABLE airway (ident VARCHAR collate nocase, network INT);" \
"CREATE INDEX airway_ident ON airway(ident);" \
\
"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);"
#endif

2352
src/Navaids/FlightPlan.cxx Normal file

File diff suppressed because it is too large Load Diff

517
src/Navaids/FlightPlan.hxx Normal file
View File

@@ -0,0 +1,517 @@
/**
* FlightPlan.hxx - defines a full flight-plan object, including
* departure, cruise, arrival information and waypoints
*/
// 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_FLIGHTPLAN_HXX
#define FG_FLIGHTPLAN_HXX
#include <functional>
#include <Navaids/route.hxx>
#include <Airports/airport.hxx>
namespace flightgear
{
class Transition;
class FlightPlan;
typedef SGSharedPtr<FlightPlan> FlightPlanRef;
enum class ICAOFlightRules
{
VFR = 0,
IFR,
IFR_VFR, // type Y
VFR_IFR // type Z
};
enum class ICAOFlightType
{
Scheduled = 0,
NonScheduled,
GeneralAviation,
Military,
Other // type X
};
class FlightPlan : public RouteBase
{
public:
virtual ~FlightPlan();
/**
create a FlightPlan with isRoute *not* set
*/
static FlightPlanRef create();
/**
@factory to create a FlightPlan with isRoute=true
*/
static FlightPlanRef createRoute();
virtual std::string ident() const;
void setIdent(const std::string& s);
// propogate the GPS/FMS setting for this through to the RoutePath
void setFollowLegTrackToFixes(bool tf);
bool followLegTrackToFixes() const;
void setMaxFlyByTurnAngle(double deg);
double maxFlyByTurnAngle() const;
// aircraft approach category as per CFR 97.3, etc
// http://www.flightsimaviation.com/data/FARS/part_97-3.html
std::string icaoAircraftCategory() const;
void setIcaoAircraftCategory(const std::string& cat);
std::string icaoAircraftType() const
{ return _aircraftType; }
void setIcaoAircraftType(const std::string& ty);
FlightPlanRef clone(const std::string& newIdent = {}, bool convertToFlightPlan = false) const;
/**
is this flight-pan a route (for planning) or an active flight-plan (which can be flow?)
Routes can contain Via, and cannot be active: FlightPlans contain Legs for procedures and
airways, i.e what the GPS/FMC actally flies.
*/
bool isRoute() const;
/**
* flight-plan leg encapsulation
*/
class Leg : public SGReferenced
{
public:
FlightPlan* owner() const
{ return const_cast<FlightPlan*>(_parent); }
Waypt* waypoint() const
{ return _waypt; }
// return the next leg after this one
Leg* nextLeg() const;
/**
* requesting holding at the waypoint upon reaching it. This will
* convert the waypt to a Hold if not already defined as one, but
* with default hold data.
*
* If the waypt is not of a type suitable for holding at, returns false
* (eg a runway or dynamic waypoint)
*/
bool setHoldCount(int count);
int holdCount() const;
bool convertWaypointToHold();
unsigned int index() const;
int altitudeFt() const;
int speed() const;
int speedKts() const;
double speedMach() const;
RouteRestriction altitudeRestriction() const;
RouteRestriction speedRestriction() const;
void setSpeed(RouteRestriction ty, double speed);
void setAltitude(RouteRestriction ty, int altFt);
double courseDeg() const;
double distanceNm() const;
double distanceAlongRoute() const;
/**
* helper function, if the waypoint is modified in some way, to
* notify the flightplan owning this leg, and hence any delegates
* obsering us
*/
void markWaypointDirty();
private:
friend class FlightPlan;
Leg(FlightPlan* owner, WayptRef wpt);
Leg* cloneFor(FlightPlan* owner) const;
void writeToProperties(SGPropertyNode* node) const;
const FlightPlan* _parent;
RouteRestriction _speedRestrict = RESTRICT_NONE,
_altRestrict = RESTRICT_NONE;
int _speed = 0;
int _altitudeFt = 0;
// if > 0, we will hold at the waypoint using
// the published hold side/course
// This only works if _waypt is a Hold, either defined by a procedure
// or modified to become one
int _holdCount = 0;
WayptRef _waypt;
/// length of this leg following the flown path
mutable double _pathDistance = -1.0;
mutable double _courseDeg = -1.0;
/// total distance of this leg from departure point
mutable double _distanceAlongPath = 11.0;
};
using LegRef = SGSharedPtr<Leg>;
class DelegateFactory;
using DelegateFactoryRef = std::shared_ptr<DelegateFactory>;
class Delegate
{
public:
virtual ~Delegate();
virtual void departureChanged() { }
virtual void arrivalChanged() { }
virtual void waypointsChanged() { }
virtual void cruiseChanged() { }
virtual void cleared() { }
virtual void activated() { }
/**
* Invoked when the C++ code determines the active leg is done / next
* leg should be sequenced. The default route-manager delegate will
* advance to the next waypoint when handling this.
*
* If multiple delegates are installed, take special care not to sequence
* the waypoint twice.
*/
virtual void sequence() { }
virtual void currentWaypointChanged() { }
virtual void endOfFlightPlan() { }
virtual void loaded() { }
protected:
Delegate();
private:
friend class FlightPlan;
// record the factory which created us, so we have the option to clean up
DelegateFactoryRef _factory;
};
LegRef insertWayptAtIndex(Waypt* aWpt, int aIndex);
void insertWayptsAtIndex(const WayptVec& wps, int aIndex);
void deleteIndex(int index);
void clearAll();
void clearLegs();
int clearWayptsWithFlag(WayptFlag flag);
int currentIndex() const
{ return _currentIndex; }
void sequence();
void setCurrentIndex(int index);
void activate();
void finish();
bool isActive() const;
LegRef currentLeg() const;
LegRef nextLeg() const;
LegRef previousLeg() const;
int numLegs() const
{ return static_cast<int>(_legs.size()); }
LegRef legAtIndex(int index) const;
int findWayptIndex(const SGGeod& aPos) const;
int findWayptIndex(const FGPositionedRef aPos) const;
int indexOfFirstNonDepartureWaypoint() const;
int indexOfFirstArrivalWaypoint() const;
int indexOfFirstApproachWaypoint() const;
int indexOfDestinationRunwayWaypoint() const;
bool load(const SGPath& p);
bool save(const SGPath& p) const;
bool save(std::ostream& stream) const;
bool load(std::istream& stream);
FGAirportRef departureAirport() const
{ return _departure; }
FGAirportRef destinationAirport() const
{ return _destination; }
FGRunway* departureRunway() const
{ return _departureRunway; }
FGRunway* destinationRunway() const
{ return _destinationRunway; }
Approach* approach() const
{ return _approach; }
void setDeparture(FGAirport* apt);
void setDeparture(FGRunway* rwy);
void clearDeparture();
SID* sid() const
{ return _sid; }
Transition* sidTransition() const;
void setSID(SID* sid, const std::string& transition = std::string());
void setSID(Transition* sidWithTrans);
void clearSID();
void setDestination(FGAirport* apt);
void setDestination(FGRunway* rwy);
void clearDestination();
FGAirportRef alternate() const;
void setAlternate(FGAirportRef alt);
/**
* note setting an approach will implicitly update the destination
* airport and runway to match
*/
void setApproach(Approach* app, const std::string& transition = {});
void setApproach(Transition* approachWithTrans);
Transition* approachTransition() const;
STAR* star() const
{ return _star; }
Transition* starTransition() const;
void setSTAR(STAR* star, const std::string& transition = std::string());
void setSTAR(Transition* starWithTrans);
void clearSTAR();
double totalDistanceNm() const
{ return _totalDistance; }
int estimatedDurationMinutes() const
{ return _estimatedDuration; }
void setEstimatedDurationMinutes(int minutes);
/**
* @brief computeDurationMinutes - use performance data and cruise data
* to estimate enroute time
*/
void computeDurationMinutes();
/**
* given a waypoint index, and an offset in NM, find the geodetic
* position on the route path. I.e the point 10nm before or after
* a particular waypoint.
*/
SGGeod pointAlongRoute(int aIndex, double aOffsetNm) const;
/**
given a waypoint index, find a point at a normalised offset, which must be [-1 .. 1]
eg an offset of -0.5 will be half-way between aIndex and the preceeding waypoint,
and an offset of 0.3 will be 30% of the distance from aIndex to the next waypoint.
*/
SGGeod pointAlongRouteNorm(int aIndex, double aOffsetNorm) const;
/**
@brief given an index to insert a waypoint into the plan, find the geographical vicinity.
This is used to aid disambiguration searches, etc: see the vicinity paramter to 'waypointFromString'
below, for example
When aIndex is negative, the vicinity used is the end of the current flight-plan, i.e appending to
the waypoints rather than appending.
*/
SGGeod vicinityForInsertIndex(int aIndex) const;
/**
* Create a WayPoint from a string in the following format:
* -
* 'vicinity' specifies the search area, to disambiguate navaids, etc with duplicate names
*/
WayptRef waypointFromString(const std::string& target, const SGGeod& vicinity = SGGeod::invalid());
/**
* attempt to replace the route waypoints (and potentially the SID and
* STAR) based on an ICAO standard route string, i.e item 15.
* Returns true if the rotue was parsed successfully (and this flight
* plan modified accordingly) or false if the string could not be
* parsed.
*/
bool parseICAORouteString(const std::string& routeData);
std::string asICAORouteString() const;
// ICAO flight-plan data
void setFlightRules(ICAOFlightRules rules);
ICAOFlightRules flightRules() const;
void setFlightType(ICAOFlightType type);
ICAOFlightType flightType() const;
void setCallsign(const std::string& callsign);
std::string callsign() const
{ return _callsign; }
void setRemarks(const std::string& remarks);
std::string remarks() const
{ return _remarks; }
// cruise data
void setCruiseSpeedKnots(int kts);
int cruiseSpeedKnots() const;
void setCruiseSpeedMach(double mach);
double cruiseSpeedMach() const;
void setCruiseSpeedKPH(int kmh);
int cruiseSpeedKPH() const;
void setCruiseFlightLevel(int flightLevel);
int cruiseFlightLevel() const;
void setCruiseAltitudeFt(int altFt);
int cruiseAltitudeFt() const;
void setCruiseAltitudeM(int altM);
int cruiseAltitudeM() const;
/**
* abstract interface for creating delegates automatically when a
* flight-plan is created or loaded
*/
class DelegateFactory
{
public:
virtual Delegate* createFlightPlanDelegate(FlightPlan* fp) = 0;
virtual void destroyFlightPlanDelegate(FlightPlan* fp, Delegate* d);
};
static void registerDelegateFactory(DelegateFactoryRef df);
static void unregisterDelegateFactory(DelegateFactoryRef df);
void addDelegate(Delegate* d);
void removeDelegate(Delegate* d);
using LegVisitor = std::function<void(Leg*)>;
void forEachLeg(const LegVisitor& lv);
private:
FlightPlan(bool isRoute);
friend class Leg;
int findLegIndex(const Leg* l) const;
void lockDelegates();
void unlockDelegates();
void notifyCleared();
unsigned int _delegateLock = 0;
bool _arrivalChanged = false,
_departureChanged = false,
_waypointsChanged = false,
_currentWaypointChanged = false,
_cruiseDataChanged = false;
bool _didLoadFP = false;
void saveToProperties(SGPropertyNode* d) const;
bool loadXmlFormat(const SGPath& path);
bool loadGpxFormat(const SGPath& path);
bool loadPlainTextFormat(const SGPath& path);
bool loadVersion1XMLRoute(SGPropertyNode_ptr routeData);
bool loadVersion2XMLRoute(SGPropertyNode_ptr routeData);
void loadXMLRouteHeader(SGPropertyNode_ptr routeData);
WayptRef parseVersion1XMLWaypt(SGPropertyNode* aWP);
double magvarDegAt(const SGGeod& pos) const;
bool parseICAOLatLon(const std::string &s, SGGeod &p);
/**
helper to convert VIA legs into a list of regular waypoints after load, etc
*/
bool expandVias();
std::string _ident;
std::string _callsign;
std::string _remarks;
std::string _aircraftType;
const bool _isRoute;
int _currentIndex;
bool _followLegTrackToFix;
char _aircraftCategory;
ICAOFlightType _flightType = ICAOFlightType::Other;
ICAOFlightRules _flightRules = ICAOFlightRules::VFR;
int _cruiseAirspeedKnots = 0;
double _cruiseAirspeedMach = 0.0;
int _cruiseAirspeedKph = 0;
int _cruiseFlightLevel = 0;
int _cruiseAltitudeFt = 0;
int _cruiseAltitudeM = 0;
int _estimatedDuration = 0;
double _maxFlyByTurnAngle = 90.0;
FGAirportRef _departure, _destination;
FGAirportRef _alternate;
FGRunway *_departureRunway, *_destinationRunway;
SGSharedPtr<SID> _sid;
SGSharedPtr<STAR> _star;
SGSharedPtr<Approach> _approach;
std::string _sidTransition, _starTransition, _approachTransition;
double _totalDistance;
void rebuildLegData();
using LegVec = std::vector<LegRef>;
LegVec _legs;
std::vector<Delegate*> _delegates;
};
} // of namespace flightgear
#endif // of FG_FLIGHTPLAN_HXX

402
src/Navaids/LevelDXML.cxx Normal file
View File

@@ -0,0 +1,402 @@
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <algorithm>
#include "LevelDXML.hxx"
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx>
#include <Navaids/waypoint.hxx>
#include <Airports/airport.hxx>
using std::string;
using std::vector;
namespace flightgear
{
NavdataVisitor::NavdataVisitor(FGAirport* aApt, const SGPath& aPath):
_airport(aApt),
_path(aPath),
_sid(NULL),
_star(NULL),
_approach(NULL),
_transition(NULL),
_procedure(NULL)
{
}
void NavdataVisitor::startXML()
{
}
void NavdataVisitor::endXML()
{
}
void NavdataVisitor::startElement(const char* name, const XMLAttributes &atts)
{
_text.clear();
string tag(name);
if (tag == "Airport") {
string icao(atts.getValue("ICAOcode"));
if (_airport->ident() != icao) {
throw sg_format_exception("Airport and ICAO mismatch", icao, _path.utf8Str());
}
} else if (tag == "Sid") {
string ident(atts.getValue("Name"));
_sid = new SID(ident, _airport);
_procedure = _sid;
_waypoints.clear();
processRunways(_sid, atts);
} else if (tag == "Star") {
string ident(atts.getValue("Name"));
_star = new STAR(ident, _airport);
_procedure = _star;
_waypoints.clear();
processRunways(_star, atts);
} else if ((tag == "Sid_Waypoint") ||
(tag == "App_Waypoint") ||
(tag == "Star_Waypoint") ||
(tag == "AppTr_Waypoint") ||
(tag == "SidTr_Waypoint") ||
(tag == "RwyTr_Waypoint"))
{
// reset waypoint data
_speed = 0.0;
_altRestrict = RESTRICT_NONE;
_altitude = 0.0;
_overflightWaypt = false; // default to Fly-by
_courseFlag = false; // default to heading
} else if (tag == "Approach") {
_ident = atts.getValue("Name");
_waypoints.clear();
ProcedureType ty = PROCEDURE_APPROACH_RNAV;
// if the name of the procedure starts with the correct name, set the procedure type
if (_ident.find("ILS") == 0) {
ty = PROCEDURE_APPROACH_ILS;
} else if (_ident.find("VOR") == 0 || _ident.find("VDM") == 0) {
ty = PROCEDURE_APPROACH_VOR;
} else if (_ident.find("NDB") == 0 || _ident.find("NDM") == 0) {
ty = PROCEDURE_APPROACH_NDB;
}
_approach = new Approach(_ident, ty);
_procedure = _approach;
} else if ((tag == "Sid_Transition") ||
(tag == "App_Transition") ||
(tag == "Star_Transition")) {
_transIdent = atts.getValue("Name");
_transition = new Transition(_transIdent, PROCEDURE_TRANSITION, _procedure);
_transWaypts.clear();
} else if (tag == "RunwayTransition") {
_transIdent = atts.getValue("Runway");
if (!_airport->hasRunwayWithIdent(_transIdent)) {
_transIdent.clear();
_transition = nullptr;
} else {
_transition = new Transition(_transIdent, PROCEDURE_RUNWAY_TRANSITION, _procedure);
_transWaypts.clear();
}
} else {
// nothing here, we warn on unrecognized in endElement
}
}
void NavdataVisitor::processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts)
{
string v("All");
if (atts.hasAttribute("Runways")) {
v = atts.getValue("Runways");
}
if (v == "All") {
for (unsigned int r=0; r<_airport->numRunways(); ++r) {
aProc->addRunway(_airport->getRunwayByIndex(r));
}
return;
}
auto rwys = simgear::strutils::split_on_any_of(v, " ,");
for (auto rwy : rwys) {
if (!_airport->hasRunwayWithIdent(rwy)) {
const auto renamed = _airport->findAPTRunwayForNewName(rwy);
if (!renamed.empty()) {
rwy = renamed;
} else {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "Procedure file " << _path << " references unknown airport runway:" << rwy);
continue;
}
}
aProc->addRunway(_airport->getRunwayByIdent(rwy));
}
}
void NavdataVisitor::endElement(const char* name)
{
string tag(name);
if ((tag == "Sid_Waypoint") ||
(tag == "App_Waypoint") ||
(tag == "Star_Waypoint"))
{
_waypoints.push_back(buildWaypoint(_procedure));
} else if ((tag == "AppTr_Waypoint") ||
(tag == "SidTr_Waypoint") ||
(tag == "RwyTr_Waypoint") ||
(tag == "StarTr_Waypoint"))
{
_transWaypts.push_back(buildWaypoint(_transition));
} else if (tag == "Sid_Transition") {
assert(_sid);
// SID waypoints are stored backwards, to share code with STARs
std::reverse(_transWaypts.begin(), _transWaypts.end());
_transition->setPrimary(_transWaypts);
_sid->addTransition(_transition);
} else if (tag == "Star_Transition") {
assert(_star);
_transition->setPrimary(_transWaypts);
_star->addTransition(_transition);
} else if (tag == "App_Transition") {
assert(_approach);
_transition->setPrimary(_transWaypts);
_approach->addTransition(_transition);
} else if (tag == "RunwayTransition") {
if (!_transition) {
// transition was skipped for some reason
return;
}
ArrivalDeparture* ad;
if (_sid) {
// SID waypoints are stored backwards, to share code with STARs
std::reverse(_transWaypts.begin(), _transWaypts.end());
ad = _sid;
} else {
ad = _star;
}
_transition->setPrimary(_transWaypts);
FGRunwayRef rwy = _airport->getRunwayByIdent(_transIdent);
ad->addRunwayTransition(rwy, _transition);
} else if (tag == "Approach") {
finishApproach();
} else if (tag == "Sid") {
finishSid();
} else if (tag == "Star") {
finishStar();
} else if (tag == "Longitude") {
_longitude = atof(_text.c_str());
} else if (tag == "Latitude") {
_latitude = atof(_text.c_str());
} else if (tag == "Name") {
_wayptName = _text;
} else if (tag == "Type") {
_wayptType = _text;
} else if (tag == "Speed") {
_speed = atoi(_text.c_str());
} else if (tag == "Altitude") {
_altitude = atof(_text.c_str());
} else if (tag == "AltitudeRestriction") {
if (_text == "at") {
_altRestrict = RESTRICT_AT;
} else if (_text == "above") {
_altRestrict = RESTRICT_ABOVE;
} else if (_text == "below") {
_altRestrict = RESTRICT_BELOW;
} else {
throw sg_format_exception("Unrecognized altitude restriction", _text);
}
} else if (tag == "Hld_Rad_or_Inbd") {
if (_text == "Inbd") {
_holdRadial = -1.0;
}
} else if (tag == "Hld_Time_or_Dist") {
_holdDistance = (_text == "Dist");
} else if (tag == "Hld_Rad_value") {
_holdRadial = atof(_text.c_str());
} else if (tag == "Hld_Turn") {
_holdRighthanded = (_text == "Right");
} else if (tag == "Hld_td_value") {
_holdTD = atof(_text.c_str());
} else if (tag == "Hdg_Crs") {
_courseFlag = atoi(_text.c_str());
} else if (tag == "Hdg_Crs_value") {
_courseOrHeading = atof(_text.c_str());
} else if (tag == "DMEtoIntercept") {
_dmeDistance = atof(_text.c_str());
} else if (tag == "RadialtoIntercept") {
_radial = atof(_text.c_str());
} else if (tag == "Flytype") {
// values are 'Fly-by' and 'Fly-over'
_overflightWaypt = (_text == "Fly-over");
} else if ((tag == "AltitudeCons") ||
(tag == "BankLimit") ||
(tag == "Sp_Turn") ||
(tag == "Airport") ||
(tag == "ProceduresDB"))
{
// ignored but don't warn
} else {
SG_LOG(SG_IO, SG_INFO, "unrecognized Level-D XML element:" << tag);
}
}
Waypt* NavdataVisitor::buildWaypoint(RouteBase* owner)
{
Waypt* wp = NULL;
if (_wayptType == "Normal") {
// new LatLonWaypoint
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
wp = new BasicWaypt(pos, _wayptName, owner);
} else if (_wayptType == "Runway") {
string ident = _wayptName.substr(2);
if (!_airport->hasRunwayWithIdent(ident)) {
const auto renamed = _airport->findAPTRunwayForNewName(ident);
if (renamed.empty()) {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "Missing runway " << ident << " reading " << _path);
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
// fall back to a basic WP
wp = new BasicWaypt(pos, _wayptName, owner);
ident.clear();
} else {
ident = renamed;
}
}
if (!ident.empty()) {
FGRunwayRef rwy = _airport->getRunwayByIdent(ident);
wp = new RunwayWaypt(rwy, owner);
}
} else if (_wayptType == "Hold") {
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
Hold* h = new Hold(pos, _wayptName, owner);
wp = h;
if (_holdRighthanded) {
h->setRightHanded();
} else {
h->setLeftHanded();
}
if (_holdDistance) {
h->setHoldDistance(_holdTD);
} else {
h->setHoldTime(_holdTD * 60.0);
}
if (_holdRadial >= 0.0) {
h->setHoldRadial(_holdRadial);
}
} else if (_wayptType == "Vectors") {
wp = new ATCVectors(owner, _airport);
} else if ((_wayptType == "Intc") || (_wayptType == "VorRadialIntc")) {
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
wp = new RadialIntercept(owner, _wayptName, pos, _courseOrHeading, _radial);
} else if (_wayptType == "DmeIntc") {
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude));
wp = new DMEIntercept(owner, _wayptName, pos, _courseOrHeading, _dmeDistance);
} else if (_wayptType == "ConstHdgtoAlt") {
wp = new HeadingToAltitude(owner, _wayptName, _courseOrHeading);
} else if (_wayptType == "PBD") {
SGGeod pos(SGGeod::fromDeg(_longitude, _latitude)), pos2;
double az2;
SGGeodesy::direct(pos, _courseOrHeading, _dmeDistance, pos2, az2);
wp = new BasicWaypt(pos2, _wayptName, owner);
} else {
SG_LOG(SG_NAVAID, SG_ALERT, "implement waypoint type:" << _wayptType);
throw sg_format_exception("Unrecognized waypt type", _wayptType);
}
assert(wp);
if ((_altitude > 0.0) && (_altRestrict != RESTRICT_NONE)) {
wp->setAltitude(_altitude,_altRestrict);
}
if (_speed > 0.0) {
wp->setSpeed(_speed, RESTRICT_AT); // or _BELOW?
}
if (_overflightWaypt) {
wp->setFlag(WPT_OVERFLIGHT);
}
return wp;
}
void NavdataVisitor::finishApproach()
{
WayptVec::iterator it;
FGRunwayRef rwy;
// find the runway node
for (it = _waypoints.begin(); it != _waypoints.end(); ++it) {
FGPositionedRef navid = (*it)->source();
if (!navid) {
continue;
}
if (navid->type() == FGPositioned::RUNWAY) {
rwy = (FGRunway*) navid.get();
break;
}
}
if (!rwy) {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "Parsing:" << _path << " found approach without a runway:" << _ident);
delete _approach;
_approach = nullptr;
return;
}
WayptVec primary(_waypoints.begin(), it);
// erase all points up to and including the runway, to leave only the
// missed segments
_waypoints.erase(_waypoints.begin(), ++it);
_approach->setRunway(rwy);
_approach->setPrimaryAndMissed(primary, _waypoints);
_airport->addApproach(_approach);
_approach = NULL;
}
void NavdataVisitor::finishSid()
{
// reverse order, because that's how we deal with commonality between
// STARs and SIDs. SID::route undoes this
std::reverse(_waypoints.begin(), _waypoints.end());
_sid->setCommon(_waypoints);
_airport->addSID(_sid);
_sid = NULL;
}
void NavdataVisitor::finishStar()
{
_star->setCommon(_waypoints);
_airport->addSTAR(_star);
_star = NULL;
}
void NavdataVisitor::data (const char * s, int len)
{
_text += string(s, len);
}
void NavdataVisitor::pi (const char * target, const char * data) {
//cout << "Processing instruction " << target << ' ' << data << endl;
}
void NavdataVisitor::warning (const char * message, int line, int column) {
SG_LOG(SG_NAVAID, SG_WARN, "Warning: " << message << " (" << line << ',' << column << ')');
}
void NavdataVisitor::error (const char * message, int line, int column) {
SG_LOG(SG_NAVAID, SG_ALERT, "Error: " << message << " (" << line << ',' << column << ')');
}
}

68
src/Navaids/LevelDXML.hxx Normal file
View File

@@ -0,0 +1,68 @@
#ifndef FG_NAV_LEVELDXML_HXX
#define FG_NAV_LEVELDXML_HXX
class FGAirport;
class SGPath;
#include <simgear/xml/easyxml.hxx>
#include <simgear/misc/sg_path.hxx>
#include <Navaids/procedure.hxx>
namespace flightgear
{
class NavdataVisitor : public XMLVisitor {
public:
NavdataVisitor(FGAirport* aApt, const SGPath& aPath);
protected:
virtual void startXML ();
virtual void endXML ();
virtual void startElement (const char * name, const XMLAttributes &atts);
virtual void endElement (const char * name);
virtual void data (const char * s, int len);
virtual void pi (const char * target, const char * data);
virtual void warning (const char * message, int line, int column);
virtual void error (const char * message, int line, int column);
private:
Waypt* buildWaypoint(RouteBase* owner);
void processRunways(ArrivalDeparture* aProc, const XMLAttributes &atts);
void finishApproach();
void finishSid();
void finishStar();
FGAirport* _airport;
SGPath _path;
std::string _text; ///< last element text value
SID* _sid;
STAR* _star;
Approach* _approach;
Transition* _transition;
Procedure* _procedure;
WayptVec _waypoints; ///< waypoint list for current approach/sid/star
WayptVec _transWaypts; ///< waypoint list for current transition
std::string _wayptName;
std::string _wayptType;
std::string _ident; // id of segment under construction
std::string _transIdent;
double _longitude, _latitude, _altitude, _speed;
RouteRestriction _altRestrict;
bool _overflightWaypt;
double _holdRadial; // inbound hold radial, or -1 if radial is 'inbound'
double _holdTD; ///< hold time (seconds) or distance (nm), based on flag below
bool _holdRighthanded;
bool _holdDistance; // true, TD is distance in nm; false, TD is time in seconds
double _courseOrHeading, _radial, _dmeDistance;
bool _courseFlag;
};
}
#endif

2915
src/Navaids/NavDataCache.cxx Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,372 @@
/**
* NavDataCache.hxx - defines a unified binary cache for navigation
* data, parsed from various text / XML sources.
*/
// 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_NAVDATACACHE_HXX
#define FG_NAVDATACACHE_HXX
#include <memory>
#include <cstddef> // for std::size_t
#include <functional>
#include <simgear/misc/strutils.hxx> // for string_list
#include <Navaids/positioned.hxx>
#include <Main/globals.hxx> // for PathList
class SGPath;
class FGRunway;
namespace flightgear
{
/// a pair of airport ID, runway ID
typedef std::pair<PositionedID, PositionedID> AirportRunwayPair;
typedef std::pair<FGPositioned::Type, PositionedID> TypedPositioned;
typedef std::vector<TypedPositioned> TypedPositionedVec;
// pair of airway ID, destination node ID
typedef std::pair<int, PositionedID> AirwayEdge;
typedef std::vector<AirwayEdge> AirwayEdgeVec;
namespace Octree {
class Node;
class Branch;
}
class Airway;
using AirwayRef = SGSharedPtr<Airway>;
class NavDataCache
{
public:
~NavDataCache();
// singleton accessor
static NavDataCache* instance();
// static creator
static NavDataCache* createInstance();
static void shutdown();
SGPath path() const;
enum DatFileType {
DATFILETYPE_APT = 0,
DATFILETYPE_METAR,
DATFILETYPE_AWY,
DATFILETYPE_NAV,
DATFILETYPE_FIX,
DATFILETYPE_POI,
DATFILETYPE_CARRIER,
DATFILETYPE_TACAN_FREQ,
DATFILETYPE_LAST
};
struct DatFilesGroupInfo {
DatFileType datFileType; // for instance, DATFILETYPE_APT
PathList paths; // SGPath instances
std::size_t totalSize; // total size of all these files, in bytes
};
// datTypeStr[DATFILETYPE_APT] = std::string("apt"), etc. This gives,
// among other things, the subdirectory of $scenery_path/NavData where
// each type of dat file is looked for.
static const std::string datTypeStr[];
// defaultDatFile[DATFILETYPE_APT] = std::string("Airports/apt.dat.gz"),
// etc. This tells where to find the historical dat files: those under
// $FG_ROOT.
static const std::string defaultDatFile[];
// Update d->datFilesInfo and legacy d->metarDatPath, d->poiDatPath,
// etc. by looking into $scenery_path/NavData for each scenery path.
void updateListsOfDatFiles();
// Returns datFilesInfo for the given type.
const DatFilesGroupInfo& getDatFilesInfo(DatFileType datFileType) const;
/**
* predicate - check if the cache needs to be rebuilt.
* This can happen is the cache file is missing or damaged, or one of the
** global input files is changed.
*/
bool isRebuildRequired();
static bool isAnotherProcessRebuilding();
enum RebuildPhase
{
REBUILD_UNKNOWN = 0,
REBUILD_READING_APT_DAT_FILES,
REBUILD_LOADING_AIRPORTS,
REBUILD_NAVAIDS,
REBUILD_FIXES,
REBUILD_POIS,
REBUILD_DONE
};
/**
* run the cache rebuild - returns the current phase or 'done'
*/
RebuildPhase rebuild();
unsigned int rebuildPhaseCompletionPercentage() const;
void setRebuildPhaseProgress(RebuildPhase ph, unsigned int percent = 0);
bool isCachedFileModified(const SGPath& path) const;
void stampCacheFile(const SGPath& path);
int readIntProperty(const std::string& key);
double readDoubleProperty(const std::string& key);
std::string readStringProperty(const std::string& key);
void writeIntProperty(const std::string& key, int value);
void writeStringProperty(const std::string& key, const std::string& value);
void writeDoubleProperty(const std::string& key, const double& value);
// Warning: order is not necessarily preserved upon write-read cycles!
string_list readStringListProperty(const std::string& key);
void writeStringListProperty(const std::string& key, const string_list& values);
// These ones guarantee the same order after each write-read cycle.
string_list readOrderedStringListProperty(const std::string& key,
const char* separator);
void writeOrderedStringListProperty(const std::string& key,
const string_list& values,
const char* separator);
/**
* retrieve an FGPositioned from the cache.
* This may be trivial if the object is previously loaded, or require actual
* disk IO.
*/
FGPositionedRef loadById(PositionedID guid);
PositionedID insertAirport(FGPositioned::Type ty, const std::string& ident,
const std::string& name);
void insertTower(PositionedID airportId, const SGGeod& pos);
PositionedID insertRunway(FGPositioned::Type ty, const string& ident,
const SGGeod& pos, PositionedID apt,
double heading, double length, double width, double displacedThreshold,
double stopway, int markings, int approach, int tdz, int reil,
int surfaceCode, int shoulder_code, float smoothness, int center_lights,
int edge_lights, int distance_remaining);
PositionedID insertRunway(FGPositioned::Type ty, const string& ident,
const SGGeod& pos, PositionedID apt,
double heading, double length, double width, double displacedThreshold,
double stopway, int surfaceCode);
void setRunwayReciprocal(PositionedID runway, PositionedID recip);
void setRunwayILS(PositionedID runway, PositionedID ils);
PositionedID insertNavaid(FGPositioned::Type ty, const std::string& ident,
const std::string& name, const SGGeod& pos, int freq, int range, double multiuse,
PositionedID apt, PositionedID runway);
// Assign colocated DME to a navaid
void setNavaidColocated(PositionedID navaid, PositionedID colocatedDME);
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);
bool removePOI(FGPositioned::Type ty, const std::string& aIdent);
/// update the metar flag associated with an airport
void setAirportMetar(const std::string& icao, bool hasMetar);
/**
* Modify the position of an existing item.
*/
void updatePosition(PositionedID item, const SGGeod &pos);
FGPositionedList findAllWithIdent( const std::string& ident,
FGPositioned::Filter* filter,
bool exact );
FGPositionedList findAllWithName( const std::string& ident,
FGPositioned::Filter* filter,
bool exact );
FGPositionedRef findClosestWithIdent( const std::string& aIdent,
const SGGeod& aPos,
FGPositioned::Filter* aFilter );
/**
* Helper to implement the AirportSearch widget. Optimised text search of
* airport names and idents, returning a list suitable for passing directly
* to PLIB.
*/
char** searchAirportNamesAndIdents(const std::string& aFilter);
/**
* Find the closest matching comm-station on a frequency, to a position.
* The filter with be used for both type ranging and to validate the result
* candidates.
*/
FGPositionedRef findCommByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt);
/**
* find all items of a specified type (or range of types) at an airport
*/
PositionedIDVec airportItemsOfType(PositionedID apt, FGPositioned::Type ty,
FGPositioned::Type maxTy = FGPositioned::INVALID);
/**
* find the first match item of the specified type and ident, at an airport
*/
PositionedID airportItemWithIdent(PositionedID apt, FGPositioned::Type ty, const std::string& ident);
/**
* Find all navaids matching a particular frequency, sorted by range from the
* supplied position. Type-range will be determined from the filter
*/
PositionedIDVec findNavaidsByFreq(int freqKhz, const SGGeod& pos, FGPositioned::Filter* filt);
/// overload version of the above that does not consider positioned when
/// returning results. Only used by TACAN carrier search
PositionedIDVec findNavaidsByFreq(int freqKhz, FGPositioned::Filter* filt);
/**
* Given a runway and type, find the corresponding navaid (ILS / GS / OM)
*/
PositionedID findNavaidForRunway(PositionedID runway, FGPositioned::Type ty);
/**
* given a navaid name (or similar) from apt.dat / nav.dat, find the
* corresponding airport and runway IDs.
* Such names look like: 'LHBP 31L DME-ILS' or 'UEEE 23L MM'
*/
AirportRunwayPair findAirportRunway(const std::string& name);
/**
* Given an aiport, and runway and ILS identifier, find the corresponding cache
* entry. This matches the data we get in the ils.xml files for airports.
*/
PositionedID findILS(PositionedID airport, const std::string& runway, const std::string& navIdent);
/**
* Given an Octree node ID, return a bit-mask defining which of the child
* nodes exist. In practice this means an 8-bit value be sufficent, but
* an int works fine too.
*/
int getOctreeBranchChildren(int64_t octreeNodeId);
void defineOctreeNode(Octree::Branch* pr, Octree::Node* nd);
/**
* given an octree leaf, return all its child positioned items and their types
*/
TypedPositionedVec getOctreeLeafChildren(int64_t octreeNodeId);
// airways
int findAirway(int network, const std::string& aName, bool create);
int findAirway(const std::string& aName);
/**
* insert an edge between two positioned nodes, into the network.
* The airway identifier will be set accordingly. No reverse edge is created
* by this method - edges are directional so a reverses must be explicitly
* created.
*/
void insertEdge(int network, int airwayID, PositionedID from, PositionedID to);
/// is the specified positioned a node on the network?
bool isInAirwayNetwork(int network, PositionedID pos);
/**
* retrive all the destination points reachable from a positioned
* in an airway
*/
AirwayEdgeVec airwayEdgesFrom(int network, PositionedID pos);
AirwayRef loadAirway(int airwayID);
/**
* Waypoints on the airway
*/
PositionedIDVec airwayWaypts(int id);
class Transaction
{
public:
Transaction(NavDataCache* cache);
~Transaction();
void commit();
private:
NavDataCache* _instance;
bool _committed;
};
bool isReadOnly() const;
class ThreadedGUISearch
{
public:
ThreadedGUISearch(const std::string& term, bool onlyAirports);
~ThreadedGUISearch();
PositionedIDVec results() const;
bool isComplete() const;
private:
class ThreadedGUISearchPrivate;
std::unique_ptr<ThreadedGUISearchPrivate> d;
};
void clearDynamicPositioneds();
private:
NavDataCache();
friend class RebuildThread;
// A generic function for loading all navigation data files of the
// specified type (apt/fix/nav etc.) using the passed type-specific loader.
void loadDatFiles(DatFileType type,
std::function<void(const SGPath&, std::size_t, std::size_t)> loader);
void doRebuild();
friend class Transaction;
void beginTransaction();
void commitTransaction();
void abortTransaction();
class NavDataCachePrivate;
std::unique_ptr<NavDataCachePrivate> d;
bool rebuildInProgress = false;
};
} // of namespace flightgear
#endif // of FG_NAVDATACACHE_HXX

176
src/Navaids/PolyLine.cxx Normal file
View File

@@ -0,0 +1,176 @@
/**
* Polyline - store geographic line-segments */
// Written by James Turner, started 2013.
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// 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.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "PolyLine.hxx"
#include <cassert>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/structure/exception.hxx>
#include <Navaids/PositionedOctree.hxx>
using namespace flightgear;
PolyLine::PolyLine(Type aTy, const SGGeodVec& aPoints) :
m_type(aTy),
m_data(aPoints)
{
assert(!aPoints.empty());
}
PolyLine::~PolyLine()
{
}
unsigned int PolyLine::numPoints() const
{
return m_data.size();
}
SGGeod PolyLine::point(unsigned int aIndex) const
{
assert(aIndex <= m_data.size());
return m_data[aIndex];
}
PolyLineList PolyLine::createChunked(Type aTy, const SGGeodVec& aRawPoints)
{
PolyLineList result;
if (aRawPoints.size() < 2) {
return result;
}
const double maxDistanceSquaredM = 40000 * 40000; // 40km to start with
SGVec3d chunkStartCart = SGVec3d::fromGeod(aRawPoints.front());
SGGeodVec chunk;
SGGeodVec::const_iterator it = aRawPoints.begin();
while (it != aRawPoints.end()) {
SGVec3d ptCart = SGVec3d::fromGeod(*it);
double d2 = distSqr(chunkStartCart, ptCart);
// distance check, but also ensure we generate actual valid line segments.
if ((chunk.size() >= 2) && (d2 > maxDistanceSquaredM)) {
chunk.push_back(*it); // close the segment
result.push_back(new PolyLine(aTy, chunk));
chunkStartCart = ptCart;
chunk.clear();
}
chunk.push_back(*it++); // add to open chunk
}
// if we have a single trailing point, we already added it as the last
// point of the previous chunk, so we're ok. Otherwise, create the
// final chunk's polyline
if (chunk.size() > 1) {
result.push_back(new PolyLine(aTy, chunk));
}
return result;
}
PolyLineRef PolyLine::create(PolyLine::Type aTy, const SGGeodVec &aRawPoints)
{
return new PolyLine(aTy, aRawPoints);
}
void PolyLine::bulkAddToSpatialIndex(PolyLineList::const_iterator begin,
PolyLineList::const_iterator end)
{
flightgear::PolyLineList::const_iterator it;
for (it=begin; it != end; ++it) {
(*it)->addToSpatialIndex();
}
}
void PolyLine::addToSpatialIndex() const
{
Octree::Node* node = Octree::globalTransientOctree()->findNodeForBox(cartesianBox());
node->addPolyLine(const_cast<PolyLine*>(this));
}
SGBoxd PolyLine::cartesianBox() const
{
SGBoxd result;
SGGeodVec::const_iterator it;
for (it = m_data.begin(); it != m_data.end(); ++it) {
SGVec3d cart = SGVec3d::fromGeod(*it);
result.expandBy(cart);
}
return result;
}
class SingleTypeFilter : public PolyLine::TypeFilter
{
public:
SingleTypeFilter(PolyLine::Type aTy) :
m_type(aTy)
{ }
virtual bool pass(PolyLine::Type aTy) const
{ return (aTy == m_type); }
private:
PolyLine::Type m_type;
};
PolyLineList PolyLine::linesNearPos(const SGGeod& aPos, double aRangeNm, Type aTy)
{
return linesNearPos(aPos, aRangeNm, SingleTypeFilter(aTy));
}
PolyLineList PolyLine::linesNearPos(const SGGeod& aPos, double aRangeNm, const TypeFilter& aFilter)
{
std::set<PolyLineRef> resultSet;
SGVec3d cart = SGVec3d::fromGeod(aPos);
double cutoffM = aRangeNm * SG_NM_TO_METER;
Octree::FindLinesDeque deque;
deque.push_back(Octree::globalTransientOctree());
while (!deque.empty()) {
Octree::Node* nd = deque.front();
deque.pop_front();
PolyLineList lines;
nd->visitForLines(cart, cutoffM, lines, deque);
// merge into result set, filtering as we go.
for (auto ref : lines) {
if (aFilter.pass(ref->type())) {
resultSet.insert(ref);
}
}
} // of deque iteration
PolyLineList result;
result.insert(result.end(), resultSet.begin(), resultSet.end());
return result;
}

127
src/Navaids/PolyLine.hxx Normal file
View File

@@ -0,0 +1,127 @@
/**
* Polyline - store geographic line-segments */
// Written by James Turner, started 2013.
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// 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_POLY_LINE_HXX
#define FG_POLY_LINE_HXX
#include <vector>
#include <simgear/sg_inlines.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/SGMath.hxx>
#include <simgear/math/SGBox.hxx>
#include <simgear/math/SGGeometryFwd.hxx>
namespace flightgear
{
typedef std::vector<SGGeod> SGGeodVec;
class PolyLine;
typedef SGSharedPtr<PolyLine> PolyLineRef;
typedef std::vector<PolyLineRef> PolyLineList;
/**
* @class Store geographical linear data, with a type code.
*
* This is a basic in-memory model of GIS line data, without support for
* many features; especially there is no support for per-node attributes.
*
* PolyLines are added to the spatial index and can be queried by passing
* a search centre and cutoff distance.
*/
class PolyLine : public SGReferenced
{
public:
virtual ~PolyLine();
enum Type
{
INVALID = 0,
COASTLINE,
NATIONAL_BOUNDARY, /// aka a border
REGIONAL_BOUNDARY, /// state / province / country / department
RIVER,
LAKE,
URBAN,
// airspace types in the future
LAST_TYPE
};
Type type() const
{ return m_type; }
/**
* number of points in this line - at least two.
*/
unsigned int numPoints() const;
SGGeod point(unsigned int aIndex) const;
const SGGeodVec& points() const
{ return m_data; }
/**
* create poly line objects from raw input points and a type.
* input points will be subdivided so the bounding area of each
* polyline stays within some threshold.
*
*/
static PolyLineList createChunked(Type aTy, const SGGeodVec& aRawPoints);
static PolyLineRef create(Type aTy, const SGGeodVec& aRawPoints);
static void bulkAddToSpatialIndex(PolyLineList::const_iterator begin,
PolyLineList::const_iterator end);
/**
* retrieve all the lines within a range of a search point.
* lines are returned if any point is near the search location.
*/
static PolyLineList linesNearPos(const SGGeod& aPos, double aRangeNm, Type aTy);
class TypeFilter
{
public:
virtual bool pass(Type aTy) const = 0;
};
static PolyLineList linesNearPos(const SGGeod& aPos, double aRangeNm, const TypeFilter& aFilter);
SGBoxd cartesianBox() const;
void addToSpatialIndex() const;
private:
PolyLine(Type aTy, const SGGeodVec& aPoints);
Type m_type;
SGGeodVec m_data;
};
} // of namespace flightgear
#endif

View File

@@ -0,0 +1,405 @@
/**
* PositionedOctree - define a spatial octree containing Positioned items
* arranged by their global cartesian position.
*/
// 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"
#include "positioned.hxx"
#include <cassert>
#include <algorithm> // for sort
#include <cstring> // for memset
#include <iostream>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/timing/timestamp.hxx>
#include "PolyLine.hxx"
namespace flightgear
{
namespace Octree
{
static std::unique_ptr<Node> global_spatialOctree;
static std::unique_ptr<Node> global_transientOctree;
double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
Node* globalTransientOctree()
{
if (!global_transientOctree) {
SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
global_transientOctree.reset(new Octree::Branch(SGBox<double>(-earthExtent, earthExtent), 1, false));
}
return global_transientOctree.get();
}
Node* globalPersistentOctree()
{
if (!global_spatialOctree) {
SGVec3d earthExtent(RADIUS_EARTH_M, RADIUS_EARTH_M, RADIUS_EARTH_M);
global_spatialOctree.reset(new Octree::Branch(SGBox<double>(-earthExtent, earthExtent), 1, true));
}
return global_spatialOctree.get();
}
Node::Node(const SGBoxd& aBox, int64_t aIdent, bool persistent) : _ident(aIdent),
_persistent(persistent),
_box(aBox)
{
}
Node::~Node()
{
}
void Node::addPolyLine(const PolyLineRef& aLine)
{
lines.push_back(aLine);
}
void Node::visitForLines(const SGVec3d& aPos, double aCutoff,
PolyLineList& aLines,
FindLinesDeque& aQ) const
{
SG_UNUSED(aPos);
SG_UNUSED(aCutoff);
aLines.insert(aLines.end(), lines.begin(), lines.end());
}
Node *Node::findNodeForBox(const SGBoxd&) const
{
return const_cast<Node*>(this);
}
Leaf::Leaf(const SGBoxd& aBox, int64_t aIdent, bool persistent) : Node(aBox, aIdent, persistent)
{
if (!persistent) {
_childrenLoaded = true;
}
}
void Leaf::visit(const SGVec3d& aPos, double aCutoff,
FGPositioned::Filter* aFilter,
FindNearestResults& aResults, FindNearestPQueue&)
{
int previousResultsSize = aResults.size();
int addedCount = 0;
NavDataCache* cache = NavDataCache::instance();
loadChildren();
ChildMap::const_iterator it = children.lower_bound(aFilter->minType());
ChildMap::const_iterator end = children.upper_bound(aFilter->maxType());
for (; it != end; ++it) {
FGPositioned* p = cache->loadById(it->second);
double d = dist(aPos, p->cart());
if (d > aCutoff) {
continue;
}
if (aFilter && !aFilter->pass(p)) {
continue;
}
++addedCount;
aResults.push_back(OrderedPositioned(p, d));
}
if (addedCount == 0) {
return;
}
// keep aResults sorted
// sort the new items, usually just one or two items
std::sort(aResults.begin() + previousResultsSize, aResults.end());
// merge the two sorted ranges together - in linear time
std::inplace_merge(aResults.begin(),
aResults.begin() + previousResultsSize, aResults.end());
}
void Leaf::insertChild(FGPositioned::Type ty, PositionedID id)
{
assert(_childrenLoaded);
children.insert(children.end(), TypedPositioned(ty, id));
}
void Leaf::loadChildren()
{
if (_childrenLoaded) {
return;
}
NavDataCache* cache = NavDataCache::instance();
for (const auto& tp : cache->getOctreeLeafChildren(guid())) {
// REVIEW: Memory Leak - 1,728 bytes in 36 blocks are still reachable
children.insert(children.end(), tp);
} // of leaf members iteration
_childrenLoaded = true;
}
///////////////////////////////////////////////////////////////////////////////
Branch::Branch(const SGBoxd& aBox, int64_t aIdent, bool persistent) : Node(aBox, aIdent, persistent)
{
_children.fill(nullptr);
if (!_persistent) {
_childrenLoaded = true;
}
}
void Branch::visit(const SGVec3d& aPos, double aCutoff,
FGPositioned::Filter*,
FindNearestResults&, FindNearestPQueue& aQ)
{
loadChildren();
for (unsigned int i=0; i<8; ++i) {
if (!_children[i]) {
continue;
}
double d = _children[i]->distToNearest(aPos);
if (d > aCutoff) {
continue; // exceeded cutoff
}
aQ.push(Ordered<Node*>(_children[i], d));
} // of child iteration
}
void Branch::visitForLines(const SGVec3d& aPos, double aCutoff,
PolyLineList& aLines,
FindLinesDeque& aQ) const
{
// add our own lines, easy
Node::visitForLines(aPos, aCutoff, aLines, aQ);
for (unsigned int i=0; i<8; ++i) {
if (!_children[i]) {
continue;
}
double d = _children[i]->distToNearest(aPos);
if (d > aCutoff) {
continue; // exceeded cutoff
}
aQ.push_back(_children[i]);
} // of child iteration
}
static bool boxContainsBox(const SGBoxd& a, const SGBoxd& b)
{
const SGVec3d aMin(a.getMin()),
aMax(a.getMax()),
bMin(b.getMin()),
bMax(b.getMax());
for (int i=0; i<3; ++i) {
if ((bMin[i] < aMin[i]) || (bMax[i] > aMax[i])) return false;
}
return true;
}
Node *Branch::findNodeForBox(const SGBoxd &box) const
{
// do this so childAtIndex sees consistent state of
// children[] and loaded flag.
loadChildren();
for (unsigned int i=0; i<8; ++i) {
const SGBoxd childBox(boxForChild(i));
if (boxContainsBox(childBox, box)) {
return childAtIndex(i)->findNodeForBox(box);
}
}
return Node::findNodeForBox(box);
}
Node* Branch::childForPos(const SGVec3d& aCart) const
{
assert(contains(aCart));
int childIndex = 0;
SGVec3d center(_box.getCenter());
// tests must match indices in SGbox::getCorner
if (aCart.x() < center.x()) {
childIndex += 1;
}
if (aCart.y() < center.y()) {
childIndex += 2;
}
if (aCart.z() < center.z()) {
childIndex += 4;
}
return childAtIndex(childIndex);
}
Node* Branch::childAtIndex(int childIndex) const
{
Node* child = _children[childIndex];
if (!child) { // lazy building of children
SGBoxd cb(boxForChild(childIndex));
double d2 = dot(cb.getSize(), cb.getSize());
assert(((_ident << 3) >> 3) == _ident);
// child index is 0..7, so 3-bits is sufficient, and hence we can
// pack 20 levels of octree into a int64, which is plenty
int64_t childIdent = (_ident << 3) | childIndex;
if (d2 < LEAF_SIZE_SQR) {
// REVIEW: Memory Leak - 480 bytes in 3 blocks are still reachable
child = new Leaf(cb, childIdent, _persistent);
} else {
// REVIEW: Memory Leak - 9,152 bytes in 52 blocks are still reachable
child = new Branch(cb, childIdent, _persistent);
}
_children[childIndex] = child;
if (_persistent && _childrenLoaded) {
// childrenLoad is done, so we're defining a new node - add it to the
// cache too.
NavDataCache::instance()->defineOctreeNode(const_cast<Branch*>(this), child);
}
}
return _children[childIndex];
}
void Branch::loadChildren() const
{
if (_childrenLoaded) {
return;
}
int childrenMask = NavDataCache::instance()->getOctreeBranchChildren(guid());
for (int i=0; i<8; ++i) {
if ((1 << i) & childrenMask) {
childAtIndex(i); // accessing will create!
}
} // of child index iteration
// set this after creating the child nodes, so the cache update logic
// in childAtIndex knows any future created children need to be added.
_childrenLoaded = true;
}
int Branch::childMask() const
{
int result = 0;
for (int i=0; i<8; ++i) {
if (_children[i]) {
result |= 1 << i;
}
}
return result;
}
bool findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec)
{
aResults.clear();
FindNearestPQueue pq;
FindNearestResults results;
pq.push(Ordered<Node*>(globalPersistentOctree(), 0));
double cut = aCutoffM;
SGTimeStamp tm;
tm.stamp();
while (!pq.empty() && (tm.elapsedMSec() < aCutoffMsec)) {
if (!results.empty()) {
// terminate the search if we have sufficent results, and we are
// sure no node still on the queue contains a closer match
double furthestResultOrder = results.back().order();
if ((results.size() >= aN) && (furthestResultOrder < pq.top().order())) {
// clear the PQ to mark this has 'full results' instead of partial
pq = FindNearestPQueue();
break;
}
}
Node* nd = pq.top().get();
pq.pop();
nd->visit(aPos, cut, aFilter, results, pq);
} // of queue iteration
// depending on leaf population, we may have (slighty) more results
// than requested
unsigned int numResults = std::min((unsigned int) results.size(), aN);
// copy results out
aResults.resize(numResults);
for (unsigned int r=0; r<numResults; ++r) {
aResults[r] = results[r].get();
}
return !pq.empty();
}
bool findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec)
{
aResults.clear();
FindNearestPQueue pq;
FindNearestResults results;
pq.push(Ordered<Node*>(globalPersistentOctree(), 0));
double rng = aRangeM;
SGTimeStamp tm;
tm.stamp();
while (!pq.empty() && (tm.elapsedMSec() < aCutoffMsec)) {
Node* nd = pq.top().get();
pq.pop();
nd->visit(aPos, rng, aFilter, results, pq);
} // of queue iteration
unsigned int numResults = results.size();
// copy results out
aResults.resize(numResults);
for (unsigned int r=0; r<numResults; ++r) {
aResults[r] = results[r].get();
}
return !pq.empty();
}
} // of namespace Octree
} // of namespace flightgear

View File

@@ -0,0 +1,256 @@
/**
* PositionedOctree - define a spatial octree containing Positioned items
* arranged by their global cartesian position.
*/
// 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
// std
#include <array>
#include <cassert>
#include <functional>
#include <map>
#include <queue>
#include <set>
#include <vector>
// SimGear
#include <simgear/math/SGGeometry.hxx>
#include <Navaids/positioned.hxx>
#include <Navaids/NavDataCache.hxx>
namespace flightgear
{
// forward decls
class PolyLine;
typedef SGSharedPtr<PolyLine> PolyLineRef;
typedef std::vector<PolyLineRef> PolyLineList;
namespace Octree
{
const double LEAF_SIZE = SG_NM_TO_METER * 8.0;
const double LEAF_SIZE_SQR = LEAF_SIZE * LEAF_SIZE;
/**
* Decorate an object with a double value, and use that value to order
* items, for the purpoises of the STL algorithms
*/
template <class T>
class Ordered
{
public:
Ordered(const T& v, double x) :
_order(x),
_inner(v)
{
assert(!SGMisc<double>::isNaN(x));
}
Ordered(const Ordered<T>& a) :
_order(a._order),
_inner(a._inner)
{
}
Ordered<T>& operator=(const Ordered<T>& a)
{
_order = a._order;
_inner = a._inner;
return *this;
}
bool operator<(const Ordered<T>& other) const
{
return _order < other._order;
}
bool operator>(const Ordered<T>& other) const
{
return _order > other._order;
}
const T& get() const
{ return _inner; }
double order() const
{ return _order; }
private:
double _order;
T _inner;
};
class Node;
typedef Ordered<Node*> OrderedNode;
typedef std::greater<OrderedNode> FNPQCompare;
/**
* the priority queue is fundamental to our search algorithm. When searching,
* we know the front of the queue is the nearest unexpanded node (to the search
* location). The default STL pqueue returns the 'largest' item from top(), so
* to get the smallest, we need to replace the default Compare functor (less<>)
* with greater<>.
*/
typedef std::priority_queue<OrderedNode, std::vector<OrderedNode>, FNPQCompare> FindNearestPQueue;
typedef Ordered<FGPositioned*> OrderedPositioned;
typedef std::vector<OrderedPositioned> FindNearestResults;
// for extracting lines, we don't care about distance ordering, since
// we're always grabbing all the lines in an area
typedef std::deque<Node*> FindLinesDeque;
Node* globalPersistentOctree();
Node* globalTransientOctree();
class Leaf;
/**
* Octree node base class, tracks its bounding box and provides various
* queries relating to it
*/
class Node
{
public:
int64_t guid() const
{ return _ident; }
const SGBoxd& bbox() const
{ return _box; }
bool contains(const SGVec3d& aPos) const
{
return intersects(aPos, _box);
}
double distToNearest(const SGVec3d& aPos) const
{
return dist(aPos, _box.getClosestPoint(aPos));
}
virtual void visit(const SGVec3d& aPos, double aCutoff,
FGPositioned::Filter* aFilter,
FindNearestResults& aResults, FindNearestPQueue&) = 0;
virtual Leaf* findLeafForPos(const SGVec3d& aPos) const = 0;
virtual void visitForLines(const SGVec3d& aPos, double aCutoff,
PolyLineList& aLines,
FindLinesDeque& aQ) const;
virtual Node* findNodeForBox(const SGBoxd& box) const;
virtual ~Node();
void addPolyLine(const PolyLineRef&);
protected:
Node(const SGBoxd& aBox, int64_t aIdent, bool persistent);
const int64_t _ident;
const bool _persistent = false;
const SGBoxd _box;
PolyLineList lines;
};
class Leaf : public Node
{
public:
Leaf(const SGBoxd& aBox, int64_t aIdent, bool persistent);
virtual void visit(const SGVec3d& aPos, double aCutoff,
FGPositioned::Filter* aFilter,
FindNearestResults& aResults, FindNearestPQueue&);
virtual Leaf* findLeafForPos(const SGVec3d&) const
{
return const_cast<Leaf*>(this);
}
void insertChild(FGPositioned::Type ty, PositionedID id);
private:
bool _childrenLoaded = false;
typedef std::multimap<FGPositioned::Type, PositionedID> ChildMap;
ChildMap children;
void loadChildren();
};
class Branch : public Node
{
public:
Branch(const SGBoxd& aBox, int64_t aIdent, bool persistent);
virtual void visit(const SGVec3d& aPos, double aCutoff,
FGPositioned::Filter*,
FindNearestResults&, FindNearestPQueue& aQ);
virtual Leaf* findLeafForPos(const SGVec3d& aPos) const
{
loadChildren();
return childForPos(aPos)->findLeafForPos(aPos);
}
int childMask() const;
virtual void visitForLines(const SGVec3d& aPos, double aCutoff,
PolyLineList& aLines,
FindLinesDeque& aQ) const;
virtual Node* findNodeForBox(const SGBoxd& box) const;
private:
Node* childForPos(const SGVec3d& aCart) const;
Node* childAtIndex(int childIndex) const;
/**
* Return the box for a child touching the specified corner
*/
SGBoxd boxForChild(unsigned int aCorner) const
{
SGBoxd r(_box.getCenter());
r.expandBy(_box.getCorner(aCorner));
return r;
}
void loadChildren() const;
mutable std::array<Node*, 8> _children;
mutable bool _childrenLoaded = false;
};
bool findNearestN(const SGVec3d& aPos, unsigned int aN, double aCutoffM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec);
bool findAllWithinRange(const SGVec3d& aPos, double aRangeM, FGPositioned::Filter* aFilter, FGPositionedList& aResults, int aCutoffMsec);
} // of namespace Octree
} // of namespace flightgear
#endif // of FG_POSITIONED_OCTREE_HXX

196
src/Navaids/SHPParser.cxx Normal file
View File

@@ -0,0 +1,196 @@
/**
* SHPParser - parse ESRI ShapeFiles containing PolyLines */
// Written by James Turner, started 2013.
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// 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.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "SHPParser.hxx"
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/io/lowlevel.hxx>
// http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf table 1
const int SHP_FILE_MAGIC = 9994;
const int SHP_FILE_VERSION = 1000;
const int SHP_NULL_TYPE = 0;
const int SHP_POLYLINE_TYPE = 3;
const int SHP_POLYGON_TYPE = 5;
namespace
{
void sgReadIntBE ( gzFile fd, int& var )
{
if ( gzread ( fd, &var, sizeof(int) ) != sizeof(int) ) {
throw sg_io_exception("gzread failed");
}
if ( sgIsLittleEndian() ) {
sgEndianSwap( (uint32_t *) &var);
}
}
void sgReadIntLE ( gzFile fd, int& var )
{
if ( gzread ( fd, &var, sizeof(int) ) != sizeof(int) ) {
throw sg_io_exception("gzread failed");
}
if ( sgIsBigEndian() ) {
sgEndianSwap( (uint32_t *) &var);
}
}
void readSHPRecordHeader(gzFile fd, int &recordNumber, int& contentLength)
{
sgReadIntBE(fd, recordNumber);
sgReadIntBE(fd, contentLength);
}
void parseSHPPoints2D(gzFile fd, int numPoints, flightgear::SGGeodVec& aPoints)
{
aPoints.reserve(numPoints);
std::vector<double> ds;
ds.resize(numPoints * 2);
sgReadDouble(fd, numPoints * 2, ds.data());
unsigned int index = 0;
for (int i=0; i<numPoints; ++i, index += 2) {
aPoints.push_back(SGGeod::fromDeg(ds[index], ds[index+1]));
}
}
} // anonymous namespace
namespace flightgear
{
void SHPParser::parsePolyLines(const SGPath& aPath, PolyLine::Type aTy,
PolyLineList& aResult, bool aClosed)
{
#if defined(SG_WINDOWS)
const auto ws = aPath.wstr();
gzFile file = gzopen_w(ws.c_str(), "rb");
#else
const auto s = aPath.utf8Str();
gzFile file = gzopen(s.c_str(), "rb");
#endif
if (!file) {
throw sg_io_exception("couldn't open file:", aPath);
}
try {
int header, fileLength, fileVersion, shapeType;
sgReadIntBE(file, header);
if (header != SHP_FILE_MAGIC) {
throw sg_io_exception("bad SHP header value", aPath);
}
// skip 5 ints, then read the file length
for (int i=0; i<6; ++i) {
sgReadIntBE(file, fileLength);
}
sgReadIntLE(file, fileVersion);
sgReadIntLE(file, shapeType);
if (fileVersion != SHP_FILE_VERSION) {
throw sg_io_exception("bad SHP file version", aPath);
}
if (aClosed && (shapeType != SHP_POLYGON_TYPE)) {
throw sg_io_exception("SHP file does not contain Polygon data", aPath);
}
if (!aClosed && (shapeType != SHP_POLYLINE_TYPE)) {
throw sg_io_exception("SHP file does not contain PolyLine data", aPath);
}
// we don't care about range values
double range;
for (int i=0; i<8; ++i) {
sgReadDouble(file, &range);
}
int readLength = 100; // sizeof the header
while (readLength < fileLength) {
int recordNumber, contentLengthWords;
readSHPRecordHeader(file, recordNumber, contentLengthWords);
int recordShapeType;
sgReadIntLE(file, recordShapeType);
if (recordShapeType == SHP_NULL_TYPE) {
continue; // nothing else to do
}
if (recordShapeType != shapeType) {
// vesion 1000 requires files to have homogenous shape type
throw sg_io_exception("SHP file shape-type mismatch", aPath);
}
// read PolyLine record from now on
double box[4];
for (int i=0; i<4; ++i) {
sgReadDouble(file, &box[i]);
}
int numParts, numPoints;
sgReadInt(file, &numParts);
sgReadInt(file, &numPoints);
std::vector<int> parts;
parts.resize(numParts);
sgReadInt(file, numParts, parts.data());
SGGeodVec points;
parseSHPPoints2D(file, numPoints, points);
for (int part=0; part<numParts; ++part) {
SGGeodVec partPoints;
unsigned int startIndex = parts[part];
unsigned int endIndex = ((part + 1) == numParts) ? numPoints : parts[part + 1];
partPoints.insert(partPoints.begin(), points.begin() + startIndex, points.begin() + endIndex);
if (aClosed) {
aResult.push_back(PolyLine::create(aTy, partPoints));
} else {
PolyLineList lines = PolyLine::createChunked(aTy, partPoints);
aResult.insert(aResult.end(), lines.begin(), lines.end());
}
}
// total record size if contentLenght + 4 words for the two record fields
readLength += (contentLengthWords + 4);
// partition
} // of record parsing
} catch (sg_exception& e) {
aResult.clear();
gzclose(file);
throw e; // rethrow
}
}
} // of namespace flightgear

46
src/Navaids/SHPParser.hxx Normal file
View File

@@ -0,0 +1,46 @@
/**
* SHPParser - parse ESRI ShapeFiles containing PolyLines */
// Written by James Turner, started 2013.
//
// Copyright (C) 2013 James Turner <zakalawe@mac.com>
//
// 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_SHP_PARSER_HXX
#define FG_SHP_PARSER_HXX
#include <Navaids/PolyLine.hxx>
// forward decls
class SGPath;
namespace flightgear
{
class SHPParser
{
public:
/**
* Parse a shape file containing PolyLine data.
*
* Throws sg_exceptions if parsing problems occur.
*/
static void parsePolyLines(const SGPath&, PolyLine::Type aTy, PolyLineList& aResult, bool aClosed);
};
} // of namespace flightgear
#endif // of FG_SHP_PARSER_HXX

706
src/Navaids/airways.cxx Normal file
View File

@@ -0,0 +1,706 @@
// airways.cxx - storage of airways network, and routing between nodes
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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 "airways.hxx"
#include <tuple>
#include <algorithm>
#include <set>
#include <simgear/sg_inlines.h>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <Main/globals.hxx>
#include <Navaids/positioned.hxx>
#include <Navaids/waypoint.hxx>
#include <Navaids/NavDataCache.hxx>
using std::make_pair;
using std::string;
using std::set;
using std::vector;
//#define DEBUG_AWY_SEARCH 1
namespace flightgear
{
static std::vector<AirwayRef> static_airwaysCache;
typedef SGSharedPtr<FGPositioned> FGPositionedRef;
//////////////////////////////////////////////////////////////////////////////
class AStarOpenNode : public SGReferenced
{
public:
AStarOpenNode(FGPositionedRef aNode, double aLegDist,
int aAirway,
FGPositionedRef aDest, AStarOpenNode* aPrev) :
node(aNode),
previous(aPrev),
airway(aAirway)
{
distanceFromStart = aLegDist;
if (previous) {
distanceFromStart += previous->distanceFromStart;
}
directDistanceToDestination = SGGeodesy::distanceM(node->geod(), aDest->geod());
}
virtual ~AStarOpenNode()
{
}
FGPositionedRef node;
SGSharedPtr<AStarOpenNode> previous;
int airway;
double distanceFromStart; // aka 'g(x)'
double directDistanceToDestination; // aka 'h(x)'
/**
* aka 'f(x)'
*/
double totalCost() const {
return distanceFromStart + directDistanceToDestination;
}
};
using AStarOpenNodeRef = SGSharedPtr<AStarOpenNode>;
////////////////////////////////////////////////////////////////////////////
Airway::Network* Airway::lowLevel()
{
static Network* static_lowLevel = nullptr;
if (!static_lowLevel) {
static_lowLevel = new Network;
static_lowLevel->_networkID = Airway::LowLevel;
}
return static_lowLevel;
}
Airway::Network* Airway::highLevel()
{
static Network* static_highLevel = nullptr;
if (!static_highLevel) {
static_highLevel = new Network;
static_highLevel->_networkID = Airway::HighLevel;
}
return static_highLevel;
}
Airway::Airway(const std::string& aIdent,
const Level level,
int dbId,
int aTop, int aBottom) :
_ident(aIdent),
_level(level),
_cacheId(dbId),
_topAltitudeFt(aTop),
_bottomAltitudeFt(aBottom)
{
assert((level == HighLevel) || (level == LowLevel));
static_airwaysCache.push_back(this);
}
void Airway::loadAWYDat(const SGPath& path)
{
std::string identStart, identEnd, name;
double latStart, lonStart, latEnd, lonEnd;
int type, base, top;
sg_gzifstream in( path );
if ( !in.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
throw sg_io_exception("Could not open airways data", path);
}
// toss the first two lines of the file
in >> skipeol;
in >> skipeol;
// read in each remaining line of the file
while (!in.eof()) {
in >> identStart;
if (identStart == "99") {
break;
}
in >> latStart >> lonStart >> identEnd >> latEnd >> lonEnd >> type >> base >> top >> name;
in >> skipeol;
// type = 1; low-altitude (victor)
// type = 2; high-altitude (jet)
Network* net = (type == 1) ? lowLevel() : highLevel();
SGGeod startPos(SGGeod::fromDeg(lonStart, latStart)),
endPos(SGGeod::fromDeg(lonEnd, latEnd));
if (type == 1) {
} else if (type == 2) {
} else {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "unknown airway type:" << type << " for " << name);
continue;
}
auto pieces = simgear::strutils::split(name, "-");
for (auto p : pieces) {
int awy = net->findAirway(p);
net->addEdge(awy, startPos, identStart, endPos, identEnd);
}
} // of file line iteration
}
WayptVec::const_iterator Airway::find(WayptRef wpt) const
{
assert(!_elements.empty());
if (wpt->type() == "via") {
// map vias to their end navaid / fix, so chaining them
// together works. (Temporary waypoint is discarded after search)
wpt = new NavaidWaypoint(wpt->source(), wpt->owner());
}
return std::find_if(_elements.begin(), _elements.end(),
[wpt] (const WayptRef& w)
{
if (!w) return false;
return w->matches(wpt);
});
}
bool Airway::canVia(const WayptRef& from, const WayptRef& to) const
{
loadWaypoints();
auto fit = find(from);
auto tit = find(to);
if ((fit == _elements.end()) || (tit == _elements.end())) {
return false;
}
if (fit < tit) {
// forward progression
for (++fit; fit != tit; ++fit) {
if (*fit == nullptr) {
// traversed an airway discontinuity
return false;
}
}
} else {
// reverse progression
for (--fit; fit != tit; --fit) {
if (*fit == nullptr) {
// traversed an airway discontinuity
return false;
}
}
}
return true;
}
WayptVec Airway::via(const WayptRef& from, const WayptRef& to) const
{
loadWaypoints();
WayptVec v;
auto fit = find(from);
auto tit = find(to);
if ((fit == _elements.end()) || (tit == _elements.end())) {
throw sg_exception("bad VIA transition points");
}
if (fit == tit) {
// will cause duplicate point but that seems better than
// return an empty
v.push_back(*tit);
return v;
}
// establish the ordering of the transitions, i.e are we moving forward or
// backard along the airway.
if (fit < tit) {
// forward progression
for (++fit; fit != tit; ++fit) {
v.push_back(*fit);
}
} else {
// reverse progression
for (--fit; fit != tit; --fit) {
v.push_back(*fit);
}
}
v.push_back(*tit);
return v;
}
bool Airway::containsNavaid(const FGPositionedRef &navaid) const
{
if (!navaid)
return false;
loadWaypoints();
auto it = std::find_if(_elements.begin(), _elements.end(),
[navaid](WayptRef w)
{
if (!w) return false;
return w->matches(navaid);
});
return (it != _elements.end());
}
int Airway::Network::findAirway(const std::string& aName)
{
const Level level = _networkID;
auto it = std::find_if(static_airwaysCache.begin(), static_airwaysCache.end(),
[aName, level](const AirwayRef& awy)
{ return (awy->_level == level) && (awy->ident() == aName); });
if (it != static_airwaysCache.end()) {
return (*it)->_cacheId;
}
return NavDataCache::instance()->findAirway(_networkID, aName, true);
}
AirwayRef Airway::findByIdent(const std::string& aIdent, Level level)
{
auto it = std::find_if(static_airwaysCache.begin(), static_airwaysCache.end(),
[aIdent, level](const AirwayRef& awy)
{
if ((level != Both) && (awy->_level != level)) return false;
return (awy->ident() == aIdent);
});
if (it != static_airwaysCache.end()) {
return *it;
}
auto ndc = NavDataCache::instance();
int airwayId = 0;
if (level == Both) {
airwayId = ndc->findAirway(HighLevel, aIdent, false);
if (airwayId == 0) {
level = LowLevel; // not found in HighLevel, try LowLevel
} else {
level = HighLevel; // fix up, so Airway ctro see a valid value
}
}
if (airwayId == 0) {
airwayId = ndc->findAirway(level, aIdent, false);
if (airwayId == 0) {
return {};
}
}
return ndc->loadAirway(airwayId);
}
AirwayRef Airway::loadByCacheId(int cacheId)
{
auto it = std::find_if(static_airwaysCache.begin(), static_airwaysCache.end(),
[cacheId](const AirwayRef& awy)
{ return (awy->_cacheId == cacheId); });
if (it != static_airwaysCache.end()) {
return *it;
}
return NavDataCache::instance()->loadAirway(cacheId);
;
}
void Airway::loadWaypoints() const
{
NavDataCache* ndc = NavDataCache::instance();
for (auto id : ndc->airwayWaypts(_cacheId)) {
if (id == 0) {
_elements.push_back({});
} else {
FGPositionedRef pos = ndc->loadById(id);
auto wp = new NavaidWaypoint(pos, const_cast<Airway*>(this));
wp->setFlag(WPT_VIA);
wp->setFlag(WPT_GENERATED);
_elements.push_back(wp);
}
}
}
AirwayRef Airway::findByIdentAndVia(const std::string& aIdent, const WayptRef& from, const WayptRef& to)
{
AirwayRef hi = findByIdent(aIdent, HighLevel);
if (hi && hi->canVia(from, to)) {
return hi;
}
AirwayRef low = findByIdent(aIdent, LowLevel);
if (low && low->canVia(from, to)) {
return low;
}
return nullptr;
}
AirwayRef Airway::findByIdentAndNavaid(const std::string& aIdent, const FGPositionedRef nav)
{
AirwayRef hi = findByIdent(aIdent, HighLevel);
if (hi && hi->containsNavaid(nav)) {
return hi;
}
AirwayRef low = findByIdent(aIdent, LowLevel);
if (low && low->containsNavaid(nav)) {
return low;
}
return nullptr;
}
WayptRef Airway::findEnroute(const std::string &aIdent) const
{
loadWaypoints();
auto it = std::find_if(_elements.begin(), _elements.end(),
[&aIdent](WayptRef w)
{
if (!w) return false;
return w->ident() == aIdent;
});
if (it != _elements.end())
return *it;
return {};
}
WayptRef Airway::findEnroute(const FGPositionedRef& nav) const
{
loadWaypoints();
auto it = std::find_if(_elements.begin(), _elements.end(),
[&nav](WayptRef w)
{
if (!w) return false;
return w->source() == nav;
});
if (it != _elements.end())
return *it;
return {};
}
void Airway::Network::addEdge(int aWay, const SGGeod& aStartPos,
const std::string& aStartIdent,
const SGGeod& aEndPos, const std::string& aEndIdent)
{
FGPositionedRef start = FGPositioned::findClosestWithIdent(aStartIdent, aStartPos);
FGPositionedRef end = FGPositioned::findClosestWithIdent(aEndIdent, aEndPos);
if (!start) {
SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways start pt: '" << aStartIdent << "'");
start = FGPositioned::createUserWaypoint(aStartIdent, aStartPos);
}
if (!end) {
SG_LOG(SG_NAVAID, SG_DEBUG, "unknown airways end pt: '" << aEndIdent << "'");
end = FGPositioned::createUserWaypoint(aEndIdent, aEndPos);
}
NavDataCache::instance()->insertEdge(_networkID, aWay, start->guid(), end->guid());
}
//////////////////////////////////////////////////////////////////////////////
static double headingDiffDeg(double a, double b)
{
double rawDiff = b - a;
SG_NORMALIZE_RANGE(rawDiff, -180.0, 180.0);
return rawDiff;
}
bool Airway::Network::inNetwork(PositionedID posID) const
{
NetworkMembershipDict::iterator it = _inNetworkCache.find(posID);
if (it != _inNetworkCache.end()) {
return it->second; // cached, easy
}
bool r = NavDataCache::instance()->isInAirwayNetwork(_networkID, posID);
_inNetworkCache.insert(it, std::make_pair(posID, r));
return r;
}
bool Airway::Network::route(WayptRef aFrom, WayptRef aTo,
WayptVec& aPath)
{
if (!aFrom || !aTo) {
throw sg_exception("invalid waypoints to route between");
}
// find closest nodes on the graph to from/to
// if argument waypoints are directly on the graph (which is frequently the
// case), note this so we don't duplicate them in the output.
FGPositionedRef from, to;
bool exactTo, exactFrom;
std::tie(from, exactFrom) = findClosestNode(aFrom);
std::tie(to, exactTo) = findClosestNode(aTo);
#ifdef DEBUG_AWY_SEARCH
SG_LOG(SG_NAVAID, SG_INFO, "from:" << from->ident() << "/" << from->name());
SG_LOG(SG_NAVAID, SG_INFO, "to:" << to->ident() << "/" << to->name());
#endif
bool ok = search2(from, to, aPath);
if (!ok) {
return false;
}
return cleanGeneratedPath(aFrom, aTo, aPath, exactTo, exactFrom);
}
bool Airway::Network::cleanGeneratedPath(WayptRef aFrom, WayptRef aTo, WayptVec& aPath,
bool exactTo, bool exactFrom)
{
// path cleaning phase : various cases to handle here.
// if either the TO or FROM waypoints were 'exact', i.e part of the enroute
// structure, we don't want to duplicate them. This happens frequently with
// published SIDs and STARs.
// secondly, if the waypoints are NOT on the enroute structure, the course to
// them may be a significant dog-leg. Check how the leg course deviates
// from the direct course FROM->TO, and delete the first/last leg if it's more
// than 90 degrees out.
// note we delete a maximum of one leg, and no more. This is a heuristic - we
// could check the next (previous) legs, but at some point we'll end up
// deleting too much.
const double MAX_DOG_LEG = 90.0;
double enrouteCourse = SGGeodesy::courseDeg(aFrom->position(), aTo->position()),
finalLegCourse = SGGeodesy::courseDeg(aPath.back()->position(), aTo->position());
bool isDogLeg = fabs(headingDiffDeg(enrouteCourse, finalLegCourse)) > MAX_DOG_LEG;
if (exactTo || isDogLeg) {
aPath.pop_back();
}
// edge case - if from and to are equal, which can happen, don't
// crash here. This happens routing EGPH -> EGCC; 'DCS' is common
// to the EGPH departure and EGCC STAR.
if (aPath.empty()) {
return true;
}
double initialLegCourse = SGGeodesy::courseDeg(aFrom->position(), aPath.front()->position());
isDogLeg = fabs(headingDiffDeg(enrouteCourse, initialLegCourse)) > MAX_DOG_LEG;
if (exactFrom || isDogLeg) {
aPath.erase(aPath.begin());
}
return true;
}
std::pair<FGPositionedRef, bool>
Airway::Network::findClosestNode(WayptRef aRef)
{
if (aRef->source()) {
// we can check directly
if (inNetwork(aRef->source()->guid())) {
return std::make_pair(aRef->source(), true);
}
}
return findClosestNode(aRef->position());
}
class InAirwayFilter : public FGPositioned::Filter
{
public:
InAirwayFilter(const Airway::Network* aNet) :
_net(aNet)
{ ; }
virtual bool pass(FGPositioned* aPos) const
{
return _net->inNetwork(aPos->guid());
}
virtual FGPositioned::Type minType() const
{ return FGPositioned::WAYPOINT; }
virtual FGPositioned::Type maxType() const
{ return FGPositioned::VOR; }
private:
const Airway::Network* _net;
};
std::pair<FGPositionedRef, bool>
Airway::Network::findClosestNode(const SGGeod& aGeod)
{
InAirwayFilter f(this);
FGPositionedRef r = FGPositioned::findClosest(aGeod, 800.0, &f);
bool exact = false;
if (r && (SGGeodesy::distanceM(aGeod, r->geod()) < 100.0)) {
exact = true; // within 100 metres, let's call that exact
}
return make_pair(r, exact);
}
FGPositionedRef
Airway::Network::findNodeByIdent(const std::string& ident, const SGGeod& near) const
{
InAirwayFilter f(this);
return FGPositioned::findClosestWithIdent(ident, near, &f);
}
/////////////////////////////////////////////////////////////////////////////
typedef vector<AStarOpenNodeRef> OpenNodeHeap;
static void buildWaypoints(AStarOpenNodeRef aNode, WayptVec& aRoute)
{
// count the route length, and hence pre-size aRoute
size_t count = 0;
AStarOpenNodeRef n = aNode;
for (; n != nullptr; ++count, n = n->previous) {;}
aRoute.resize(count);
// run over the route, creating waypoints
for (n = aNode; n; n=n->previous) {
// get / create airway to be the owner for this waypoint
AirwayRef awy = Airway::loadByCacheId(n->airway);
auto wp = new NavaidWaypoint(n->node, awy);
if (awy) {
wp->setFlag(WPT_VIA);
}
wp->setFlag(WPT_GENERATED);
aRoute[--count] = wp;
}
}
/**
* Inefficent (linear) helper to find an open node in the heap
*/
static AStarOpenNodeRef
findInOpen(const OpenNodeHeap& aHeap, FGPositioned* aPos)
{
for (unsigned int i=0; i<aHeap.size(); ++i) {
if (aHeap[i]->node == aPos) {
return aHeap[i];
}
}
return nullptr;
}
class HeapOrder
{
public:
bool operator()(AStarOpenNode* a, AStarOpenNode* b)
{
return a->totalCost() > b->totalCost();
}
};
bool Airway::Network::search2(FGPositionedRef aStart, FGPositionedRef aDest,
WayptVec& aRoute)
{
typedef set<PositionedID> ClosedNodeSet;
OpenNodeHeap openNodes;
ClosedNodeSet closedNodes;
HeapOrder ordering;
openNodes.push_back(new AStarOpenNode(aStart, 0.0, 0, aDest, nullptr));
// A* open node iteration
while (!openNodes.empty()) {
std::pop_heap(openNodes.begin(), openNodes.end(), ordering);
AStarOpenNodeRef x = openNodes.back();
FGPositioned* xp = x->node;
openNodes.pop_back();
closedNodes.insert(xp->guid());
#ifdef DEBUG_AWY_SEARCH
SG_LOG(SG_NAVAID, SG_INFO, "x:" << xp->ident() << ", f(x)=" << x->totalCost());
#endif
// check if xp is the goal; if so we're done, since there cannot be an open
// node with lower f(x) value.
if (xp == aDest) {
buildWaypoints(x, aRoute);
return true;
}
// adjacent (neighbour) iteration
NavDataCache* cache = NavDataCache::instance();
for (auto other : cache->airwayEdgesFrom(_networkID, xp->guid())) {
if (closedNodes.count(other.second)) {
continue; // closed, ignore
}
FGPositioned* yp = cache->loadById(other.second);
double edgeDistanceM = SGGeodesy::distanceM(xp->geod(), yp->geod());
AStarOpenNodeRef y = findInOpen(openNodes, yp);
if (y) { // already open
double g = x->distanceFromStart + edgeDistanceM;
if (g > y->distanceFromStart) {
// worse path, ignore
#ifdef DEBUG_AWY_SEARCH
SG_LOG(SG_NAVAID, SG_INFO, "\tabandoning " << yp->ident() <<
" path is worse: g(y)" << y->distanceFromStart << ", g'=" << g);
#endif
continue;
}
// we need to update y. Unfortunately this means rebuilding the heap,
// since y's score can change arbitrarily
#ifdef DEBUG_AWY_SEARCH
SG_LOG(SG_NAVAID, SG_INFO, "\tfixing up previous for new path to " << yp->ident() << ", d =" << g);
#endif
y->previous = x;
y->distanceFromStart = g;
y->airway = other.first;
std::make_heap(openNodes.begin(), openNodes.end(), ordering);
} else { // not open, insert a new node for y into the heap
y = new AStarOpenNode(yp, edgeDistanceM, other.first, aDest, x);
#ifdef DEBUG_AWY_SEARCH
SG_LOG(SG_NAVAID, SG_INFO, "\ty=" << yp->ident() << ", f(y)=" << y->totalCost());
#endif
openNodes.push_back(y);
std::push_heap(openNodes.begin(), openNodes.end(), ordering);
}
} // of neighbour iteration
} // of open node iteration
SG_LOG(SG_NAVAID, SG_INFO, "A* failed to find route");
return false;
}
} // of namespace flightgear

191
src/Navaids/airways.hxx Normal file
View File

@@ -0,0 +1,191 @@
// airways.hxx - storage of airways network, and routing between nodes
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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_AIRWAYS_HXX
#define FG_AIRWAYS_HXX
#include <map>
#include <vector>
#include <Navaids/route.hxx>
#include <Navaids/positioned.hxx>
class SGPath;
typedef SGSharedPtr<FGPositioned> FGPositionedRef;
namespace flightgear {
// forward declare some helpers
struct SearchContext;
class AdjacentWaypoint;
class InAirwayFilter;
class Airway;
using AirwayRef = SGSharedPtr<Airway>;
class Airway : public RouteBase
{
public:
enum Level {
UnknownLevel = 0,
LowLevel = 1, /// Victor airways
HighLevel = 2, /// Jet airways
Both = 3
};
std::string ident() const override
{ return _ident; }
int cacheId() const
{ return _cacheId; }
Level level() const
{ return _level; }
static void loadAWYDat(const SGPath& path);
double topAltitudeFt() const
{ return _topAltitudeFt; }
double bottomAltitudeFt() const
{ return _bottomAltitudeFt; }
static AirwayRef loadByCacheId(int cacheId);
static AirwayRef findByIdent(const std::string& aIdent, Level level);
/**
* Find the airway based on its ident. IF both high- and low- level idents
* exist, select the one which can route between the from and to waypoints
* correctly, preferring high-level airways.
*/
static AirwayRef findByIdentAndVia(const std::string& aIdent, const WayptRef& from, const WayptRef& to);
/**
* Find an airway by ident, and containing a particula rnavaid/fix.
*/
static AirwayRef findByIdentAndNavaid(const std::string& aIdent, const FGPositionedRef nav);
WayptRef findEnroute(const std::string& aIdent) const;
bool canVia(const WayptRef& from, const WayptRef& to) const;
WayptVec via(const WayptRef& from, const WayptRef& to) const;
bool containsNavaid(const FGPositionedRef& navaid) const;
WayptRef findEnroute(const FGPositionedRef& navaid) const;
/**
* Track a network of airways
*
*/
class Network
{
public:
friend class Airway;
friend class InAirwayFilter;
/**
* Principal routing algorithm. Attempts to find the best route beween
* two points. If either point is part of the airway network (e.g, a SID
* or STAR transition), it will <em>not</em> be duplicated in the result
* path.
*
* Returns true if a route could be found, or false otherwise.
*/
bool route(WayptRef aFrom, WayptRef aTo, WayptVec& aPath);
/**
* Overloaded version working with a raw SGGeod
*/
std::pair<FGPositionedRef, bool> findClosestNode(const SGGeod& aGeod);
FGPositionedRef findNodeByIdent(const std::string& ident, const SGGeod& near) const;
private:
void addEdge(int aWay, const SGGeod& aStartPos,
const std::string& aStartIdent,
const SGGeod& aEndPos, const std::string& aEndIdent);
int findAirway(const std::string& aName);
bool cleanGeneratedPath(WayptRef aFrom, WayptRef aTo, WayptVec& aPath,
bool exactTo, bool exactFrom);
bool search2(FGPositionedRef aStart, FGPositionedRef aDest, WayptVec& aRoute);
/**
* Test if a positioned item is part of this airway network or not.
*/
bool inNetwork(PositionedID pos) const;
/**
* Find the closest node on the network, to the specified waypoint
*
* May return NULL,false if no match could be found; the search is
* internally limited to avoid very poor performance; for example,
* in the middle of an ocean.
*
* The second return value indicates if the returned value is
* equal (true) or distinct (false) to the input waypoint.
* Equality here means being physically within a close tolerance,
* on the order of a hundred metres.
*/
std::pair<FGPositionedRef, bool> findClosestNode(WayptRef aRef);
/**
* cache which positioned items are in this network
*/
typedef std::map<PositionedID, bool> NetworkMembershipDict;
mutable NetworkMembershipDict _inNetworkCache;
Level _networkID;
};
static Network* highLevel();
static Network* lowLevel();
private:
Airway(const std::string& aIdent, const Level level, int dbId, int aTop, int aBottom);
void loadWaypoints() const;
WayptVec::const_iterator find(WayptRef wpt) const;
friend class Network;
friend class NavDataCache;
const std::string _ident;
const Level _level;
const int _cacheId;
int _topAltitudeFt;
int _bottomAltitudeFt;
mutable WayptVec _elements;
};
} // of namespace flightgear
#endif //of FG_AIRWAYS_HXX

467
src/Navaids/awynet.cxx Normal file
View File

@@ -0,0 +1,467 @@
// awynet.cxx
// by Durk Talsma, started June 2005.
//
// Copyright (C) 2004 Durk Talsma.
//
// 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <math.h>
#include <algorithm>
#include <iostream>
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/route/waypoint.hxx>
#include <simgear/misc/sg_path.hxx>
#include "awynet.hxx"
using std::sort;
using std::string;
using std::cerr;
using std::endl;
/**************************************************************************
* FGNode
*************************************************************************/
FGNode::FGNode()
{
}
FGNode::FGNode(const SGGeod& aPos, int idx, std::string id) :
ident(id),
geod(aPos),
index(idx)
{
cart = SGVec3d::fromGeod(geod);
}
bool FGNode::matches(std::string id, const SGGeod& aPos)
{
if ((ident == id) &&
(fabs(aPos.getLatitudeDeg() - geod.getLatitudeDeg()) < 1.0) &&
(fabs(aPos.getLongitudeDeg() - geod.getLongitudeDeg()) < 1.0))
return true;
else
return false;
}
/***************************************************************************
* FGAirway
**************************************************************************/
FGAirway::FGAirway()
{
length = 0;
}
void FGAirway::setStart(node_map *nodes)
{
node_map_iterator itr = nodes->find(startNode);
if (itr == nodes->end()) {
cerr << "Couldn't find node: " << startNode << endl;
}
else {
start = itr->second->getAddress();
itr->second->addAirway(this);
}
}
void FGAirway::setEnd(node_map *nodes)
{
node_map_iterator itr = nodes->find(endNode);
if (itr == nodes->end()) {
cerr << "Couldn't find node: " << endNode << endl;
}
else {
end = itr->second->getAddress();
}
}
// There is probably a computationally cheaper way of
// doing this.
void FGAirway::setTrackDistance()
{
length = SGGeodesy::distanceM(start->getPosition(), end->getPosition());
}
/***************************************************************************
* FGAirRoute()
**************************************************************************/
bool FGAirRoute::next(int *val)
{
//for (intVecIterator i = nodes.begin(); i != nodes.end(); i++)
// cerr << "FGTaxiRoute contains : " << *(i) << endl;
//cerr << "Offset from end: " << nodes.end() - currNode << endl;
//if (currNode != nodes.end())
// cerr << "true" << endl;
//else
// cerr << "false" << endl;
if (currNode == nodes.end())
return false;
*val = *(currNode);
currNode++;
return true;
};
void FGAirRoute::add(const FGAirRoute &other) {
for (constIntVecIterator i = other.nodes.begin() ;
i != other.nodes.end(); i++)
{
nodes.push_back((*i));
}
distance += other.distance;
}
/***************************************************************************
* FGAirwayNetwork()
**************************************************************************/
FGAirwayNetwork::FGAirwayNetwork()
{
hasNetwork = false;
foundRoute = false;
totalDistance = 0;
maxDistance = 0;
}
FGAirwayNetwork::~FGAirwayNetwork()
{
for (unsigned int it = 0; it < nodes.size(); it++) {
delete nodes[ it];
}
}
void FGAirwayNetwork::addAirway(const FGAirway &seg)
{
segments.push_back(seg);
}
//void FGAirwayNetwork::addNode(const FGNode &node)
//{
// nodes.push_back(node);
//}
/*
void FGAirwayNetwork::addNodes(FGParkingList *parkings)
{
FGTaxiNode n;
FGParkingList::iterator i = parkings->begin();
while (i != parkings->end())
{
n.setIndex(i->getIndex());
n.setLatitude(i->getLatitude());
n.setLongitude(i->getLongitude());
nodes.push_back(n);
i++;
}
}
*/
void FGAirwayNetwork::init()
{
hasNetwork = true;
FGAirwayVectorIterator i = segments.begin();
while(i != segments.end()) {
//cerr << "initializing Airway " << i->getIndex() << endl;
i->setStart(&nodesMap);
i->setEnd (&nodesMap);
//i->setTrackDistance();
//cerr << "Track distance = " << i->getLength() << endl;
//cerr << "Track ends at" << i->getEnd()->getIndex() << endl;
i++;
}
//exit(1);
}
void FGAirwayNetwork::load(const SGPath& path)
{
std::string identStart, identEnd, token, name;
double latStart, lonStart, latEnd, lonEnd;
int type, base, top;
int airwayIndex = 0;
FGNode *n;
sg_gzifstream in( path );
if ( !in.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path.str() );
exit(-1);
}
// toss the first two lines of the file
in >> skipeol;
in >> skipeol;
// read in each remaining line of the file
while ( ! in.eof() ) {
string token;
in >> token;
if ( token == "99" ) {
return; //in >> skipeol;
}
// Read each line from the database
identStart = token;
in >> latStart >> lonStart >> identEnd >> latEnd >> lonEnd >> type >> base >> top >> name;
in >> skipeol;
/*out << identStart << " "
<< latStart << " "
<< lonStart << " "
<< identEnd << " "
<< latEnd << " "
<< lonEnd << " "
<< type << " "
<< base << " "
<< top << " "
<< name << " "
<< endl;*/
//first determine whether the start and end reference database already exist
//if not we should create them
int startIndex = 0, endIndex=0;
// FGNodeVectorIterator i = nodes.begin();
// while (i != nodes.end() && (!(i->matches(identStart,latStart, lonStart))))
// {
// i++;
// startIndex++;
// }
// if (i == nodes.end())
// {
// nodes.push_back(FGNode(latStart, lonStart, startIndex, identStart));
// //cout << "Adding node: " << identStart << endl;
// }
// i = nodes.begin();
// while (i != nodes.end() && (!(i->matches(identEnd,latEnd, lonEnd)))) {
// i++;
// endIndex++;
// }
// if (i == nodes.end()) {
// nodes.push_back(FGNode(latEnd, lonEnd, endIndex, identEnd));
// //cout << "Adding node: " << identEnd << endl;
// }
// generate unique IDs for the nodes, consisting of a combination
// of the Navaid identifier + the integer value of the lat/lon position.
// identifier alone will not suffice, because they might not be globally unique.
char buffer[32];
string startNode, endNode;
// Start
buffer[sizeof(buffer)-1] = 0;
snprintf(buffer, sizeof(buffer)-1, "%s%d%d", identStart.c_str(), (int) latStart, (int) lonStart);
startNode = buffer;
node_map_iterator itr = nodesMap.find(string(buffer));
if (itr == nodesMap.end()) {
startIndex = nodes.size();
SGGeod startPos(SGGeod::fromDeg(lonStart, latStart));
n = new FGNode(startPos, startIndex, identStart);
nodesMap[string(buffer)] = n;
nodes.push_back(n);
//cout << "Adding node: " << identStart << endl;
}
else {
startIndex = itr->second->getIndex();
}
// Start
snprintf(buffer, 32, "%s%d%d", identEnd.c_str(), (int) latEnd, (int) lonEnd);
endNode = buffer;
itr = nodesMap.find(string(buffer));
if (itr == nodesMap.end()) {
endIndex = nodes.size();
SGGeod endPos(SGGeod::fromDeg(lonEnd, latEnd));
n = new FGNode(endPos, endIndex, identEnd);
nodesMap[string(buffer)] = n;
nodes.push_back(n);
//cout << "Adding node: " << identEnd << endl;
}
else {
endIndex = itr->second->getIndex();
}
FGAirway airway;
airway.setIndex ( airwayIndex++ );
airway.setStartNodeRef ( startNode );
airway.setEndNodeRef ( endNode );
airway.setType ( type );
airway.setBase ( base );
airway.setTop ( top );
airway.setName ( name );
segments.push_back(airway);
//cout << " Adding Airway: " << name << " " << startIndex << " " << endIndex << endl;
}
}
int FGAirwayNetwork::findNearestNode(const SGGeod& aPos)
{
double minDist = HUGE_VAL;
int index = -1;
SGVec3d cart = SGVec3d::fromGeod(aPos);
//cerr << "Lat " << lat << " lon " << lon << endl;
for (FGNodeVectorIterator
itr = nodes.begin();
itr != nodes.end(); itr++)
{
double d2 = distSqr(cart, (*itr)->getCart());
if (d2 < minDist)
{
minDist = d2;
index = (*itr)->getIndex();
}
//cerr << (*itr)->getIndex() << endl;
}
//cerr << " returning " << index << endl;
return index;
}
FGNode *FGAirwayNetwork::findNode(int idx)
{
for (FGNodeVectorIterator
itr = nodes.begin();
itr != nodes.end(); itr++)
{
if ((*itr)->getIndex() == idx)
return (*itr)->getAddress();
}
return 0;
}
FGAirRoute FGAirwayNetwork::findShortestRoute(int start, int end)
{
foundRoute = false;
totalDistance = 0;
FGNode *firstNode = findNode(start);
//FGNode *lastNode = findNode(end);
//prevNode = prevPrevNode = -1;
//prevNode = start;
routes.clear();
traceStack.clear();
trace(firstNode, end, 0, 0);
FGAirRoute empty;
if (!foundRoute)
{
SG_LOG( SG_NAVAID, SG_INFO, "Failed to find route from waypoint " << start << " to " << end );
cerr << "Failed to find route from waypoint " << start << " to " << end << endl;
//exit(1);
}
sort(routes.begin(), routes.end());
//for (intVecIterator i = route.begin(); i != route.end(); i++)
// {
// rte->push_back(*i);
// }
if (routes.begin() != routes.end())
return *(routes.begin());
else
return empty;
}
void FGAirwayNetwork::trace(FGNode *currNode, int end, int depth, double distance)
{
traceStack.push_back(currNode->getIndex());
totalDistance += distance;
//cerr << depth << " ";
//cerr << "Starting trace " << depth << " total distance: " << totalDistance<< endl;
//<< currNode->getIndex() << endl;
// If the current route matches the required end point we found a valid route
// So we can add this to the routing table
if (currNode->getIndex() == end)
{
cerr << "Found route : " << totalDistance << "" << " " << *(traceStack.end()-1) << endl;
routes.push_back(FGAirRoute(traceStack,totalDistance));
traceStack.pop_back();
if (!(foundRoute))
maxDistance = totalDistance;
else
if (totalDistance < maxDistance)
maxDistance = totalDistance;
foundRoute = true;
totalDistance -= distance;
return;
}
// search if the currentNode has been encountered before
// if so, we should step back one level, because it is
// rather rediculous to proceed further from here.
// if the current node has not been encountered before,
// i should point to traceStack.end()-1; and we can continue
// if i is not traceStack.end, the previous node was found,
// and we should return.
// This only works at trace levels of 1 or higher though
if (depth > 0) {
intVecIterator i = traceStack.begin();
while ((*i) != currNode->getIndex()) {
//cerr << "Route so far : " << (*i) << endl;
i++;
}
if (i != traceStack.end()-1) {
traceStack.pop_back();
totalDistance -= distance;
return;
}
// If the total distance from start to the current waypoint
// is longer than that of a route we can also stop this trace
// and go back one level.
if ((totalDistance > maxDistance) && foundRoute)
{
cerr << "Stopping rediculously long trace: " << totalDistance << endl;
traceStack.pop_back();
totalDistance -= distance;
return;
}
}
//cerr << "2" << endl;
if (currNode->getBeginRoute() != currNode->getEndRoute())
{
//cerr << "l3l" << endl;
for (FGAirwayPointerVectorIterator
i = currNode->getBeginRoute();
i != currNode->getEndRoute();
i++)
{
//cerr << (*i)->getLenght() << endl;
trace((*i)->getEnd(), end, depth+1, (*i)->getLength());
// {
// // cerr << currNode -> getIndex() << " ";
// route.push_back(currNode->getIndex());
// return true;
// }
}
}
else
{
//SG_LOG( SG_NAVAID, SG_DEBUG, "4" );
//cerr << "4" << endl;
}
traceStack.pop_back();
totalDistance -= distance;
return;
}

201
src/Navaids/awynet.hxx Normal file
View File

@@ -0,0 +1,201 @@
// airwaynet.hxx - A number of classes to handle taxiway
// assignments by the AI code
//
// Written by Durk Talsma. Based upon the ground netword code, started June 2005.
//
// Copyright (C) 2004 Durk Talsma.
//
// 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 St, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
#ifndef _AIRWAYNETWORK_HXX_
#define _AIRWAYNETWORK_HXX_
#include <string>
#include <istream>
#include <set>
#include <map>
#include <vector>
#include <simgear/io/iostreams/sgstream.hxx>
//#include "parking.hxx"
class FGAirway; // forward reference
class SGPath;
class SGGeod;
typedef std::vector<FGAirway> FGAirwayVector;
typedef std::vector<FGAirway *> FGAirwayPointerVector;
typedef std::vector<FGAirway>::iterator FGAirwayVectorIterator;
typedef std::vector<FGAirway*>::iterator FGAirwayPointerVectorIterator;
/**************************************************************************************
* class FGNode
*************************************************************************************/
class FGNode
{
private:
std::string ident;
SGGeod geod;
SGVec3d cart; // cached cartesian position
int index;
FGAirwayPointerVector next; // a vector to all the segments leaving from this node
public:
FGNode();
FGNode(const SGGeod& aPos, int idx, std::string id);
void setIndex(int idx) { index = idx;};
void addAirway(FGAirway *segment) { next.push_back(segment); };
const SGGeod& getPosition() {return geod;}
const SGVec3d& getCart() { return cart; }
int getIndex() { return index; };
std::string getIdent() { return ident; };
FGNode *getAddress() { return this;};
FGAirwayPointerVectorIterator getBeginRoute() { return next.begin(); };
FGAirwayPointerVectorIterator getEndRoute() { return next.end(); };
bool matches(std::string ident, const SGGeod& aPos);
};
typedef std::vector<FGNode *> FGNodeVector;
typedef std::vector<FGNode *>::iterator FGNodeVectorIterator;
typedef std::map < std::string, FGNode *> node_map;
typedef node_map::iterator node_map_iterator;
typedef node_map::const_iterator const_node_map_iterator;
/***************************************************************************************
* class FGAirway
**************************************************************************************/
class FGAirway
{
private:
std::string startNode;
std::string endNode;
double length;
FGNode *start;
FGNode *end;
int index;
int type; // 1=low altitude; 2=high altitude airway
int base; // base altitude
int top; // top altitude
std::string name;
public:
FGAirway();
FGAirway(FGNode *, FGNode *, int);
void setIndex (int val) { index = val; };
void setStartNodeRef (std::string val) { startNode = val; };
void setEndNodeRef (std::string val) { endNode = val; };
void setStart(node_map *nodes);
void setEnd (node_map *nodes);
void setType (int tp) { type = tp;};
void setBase (int val) { base = val;};
void setTop (int val) { top = val;};
void setName (std::string val) { name = val;};
void setTrackDistance();
FGNode * getEnd() { return end;};
double getLength() { if (length == 0) setTrackDistance(); return length; };
int getIndex() { return index; };
std::string getName() { return name; };
};
typedef std::vector<int> intVec;
typedef std::vector<int>::iterator intVecIterator;
typedef std::vector<int>::const_iterator constIntVecIterator;
class FGAirRoute
{
private:
intVec nodes;
double distance;
intVecIterator currNode;
public:
FGAirRoute() { distance = 0; currNode = nodes.begin(); };
FGAirRoute(intVec nds, double dist) { nodes = nds; distance = dist; currNode = nodes.begin();};
bool operator< (const FGAirRoute &other) const {return distance < other.distance; };
bool empty () { return nodes.begin() == nodes.end(); };
bool next(int *val);
void first() { currNode = nodes.begin(); };
void add(const FGAirRoute &other);
void add(int node) {nodes.push_back(node);};
friend std::istream& operator >> (std::istream& in, FGAirRoute& r);
};
inline std::istream& operator >> ( std::istream& in, FGAirRoute& r )
{
int node;
in >> node;
r.nodes.push_back(node);
//getline( in, n.name );
return in;
}
typedef std::vector<FGAirRoute> AirRouteVector;
typedef std::vector<FGAirRoute>::iterator AirRouteVectorIterator;
/**************************************************************************************
* class FGAirwayNetwork
*************************************************************************************/
class FGAirwayNetwork
{
private:
bool hasNetwork;
node_map nodesMap;
FGNodeVector nodes;
FGAirwayVector segments;
//intVec route;
intVec traceStack;
AirRouteVector routes;
bool foundRoute;
double totalDistance, maxDistance;
public:
FGAirwayNetwork();
~FGAirwayNetwork();
//void addNode (const FGNode& node);
//void addNodes (FGParkingVec *parkings);
void addAirway(const FGAirway& seg);
void init();
bool exists() { return hasNetwork; };
int findNearestNode(const SGGeod& aPos);
FGNode *findNode(int idx);
FGAirRoute findShortestRoute(int start, int end);
void trace(FGNode *, int, int, double dist);
void load(const SGPath& path);
};
#endif

47
src/Navaids/fix.hxx Normal file
View File

@@ -0,0 +1,47 @@
// fix.hxx -- fix class
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_FIX_HXX
#define _FG_FIX_HXX
#include <simgear/compiler.h>
#include <string>
#include "positioned.hxx"
class FGFix : public FGPositioned
{
public:
FGFix(PositionedID aGuid, const std::string& aIdent, const SGGeod& aPos);
inline ~FGFix(void) {}
static bool isType(FGPositioned::Type ty)
{ return (ty == FGPositioned::FIX); }
inline const std::string& get_ident() const { return ident(); }
inline double get_lon() const { return longitude(); }
inline double get_lat() const { return latitude(); }
};
#endif // _FG_FIX_HXX

175
src/Navaids/fixlist.cxx Normal file
View File

@@ -0,0 +1,175 @@
// fixlist.cxx -- fix list management class
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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>
#endif
#include <stdlib.h> // atof()
#include <algorithm>
#include <string> // std::getline()
#include <errno.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/misc/strutils.hxx> // simgear::strutils::split()
#include <simgear/math/SGGeod.hxx>
#include <simgear/math/SGMathFwd.hxx>
#include <simgear/math/SGVec3.hxx>
#include <simgear/constants.h>
#include <simgear/structure/exception.hxx>
#include "fixlist.hxx"
#include <Navaids/fix.hxx>
#include <Navaids/NavDataCache.hxx>
// A navaid with the same ident as an existing navaid not more distant than
// this will be considered duplicate.
static const double DUPLICATE_DETECTION_RADIUS_NM = 15;
FGFix::FGFix(PositionedID aGuid, const std::string& aIdent, const SGGeod& aPos) :
FGPositioned(aGuid, FIX, aIdent, aPos)
{
}
namespace flightgear
{
FixesLoader::FixesLoader() : _cache(NavDataCache::instance())
{ }
FixesLoader::~FixesLoader()
{ }
// Load fixes from the specified fix.dat (or fix.dat.gz) file
void FixesLoader::loadFixes(const SGPath& path, std::size_t bytesReadSoFar,
std::size_t totalSizeOfAllDatFiles)
{
sg_gzifstream in( path );
const std::string utf8path = path.utf8Str();
if ( !in.is_open() ) {
throw sg_io_exception(
"Cannot open file (" + simgear::strutils::error_string(errno) + ")",
sg_location(path));
}
// toss the first two lines of the file
for (int i = 0; i < 2; i++) {
in >> skipeol;
throwExceptionIfStreamError(in, path);
}
unsigned int lineNumber = 3;
// read in each remaining line of the file
for (std::string line; std::getline(in, line); lineNumber++) {
std::vector<std::string> fields = simgear::strutils::split(line);
std::vector<std::string>::size_type nb_fields = fields.size();
const std::string endOfData = "99"; // special code in the fix.dat spec
if (nb_fields == 0) { // blank line
continue;
} else if (nb_fields == 1) {
if (fields[0] == endOfData)
break;
else {
SG_LOG(SG_NAVAID, SG_WARN, utf8path << ": malformed line #" <<
lineNumber << ": only one field, but it is not '99'");
continue;
}
} else if (nb_fields < 3) {
SG_LOG(SG_NAVAID, SG_WARN, utf8path << ": malformed line #" <<
lineNumber << ": expected at least 3 fields, but got " <<
fields.size());
continue;
} else if (nb_fields != 3 && nb_fields != 5 && nb_fields != 6) {
// XP FIX1101 format calls for 6 fields, the last being optional.
// XP FIX1100 has 5 fields.
// Earlier formats have 3 fields.
// In all these cases we need the first three only.
SG_LOG(SG_NAVAID, SG_INFO, utf8path << ": line #" <<
lineNumber << ": ignoring extra fields, past the first three " <<
"(expected 3 or 5 or 6 fields, but got " << fields.size() << ")");
}
std::string ident = fields[2];
double lat, lon;
try {
lat = std::stod(fields[0]);
lon = std::stod(fields[1]);
} catch (const std::exception&) {
SG_LOG(SG_NAVAID, SG_WARN, utf8path << ": malformed line #" <<
lineNumber << ": error parsing coordinates: " << fields[0] <<
" " << fields[1]);
continue;
}
SGGeod pos(SGGeod::fromDeg(lon, lat));
bool duplicate = false;
auto range = _loadedFixes.equal_range(ident);
for (auto it = range.first; it != range.second; ++it) {
double distNm = dist(SGVec3d::fromGeod(pos),
SGVec3d::fromGeod(it->second)) * SG_METER_TO_NM;
if (distNm < DUPLICATE_DETECTION_RADIUS_NM) {
SG_LOG(SG_NAVAID, SG_INFO,
utf8path << ":" << lineNumber << ": skipping fix " <<
ident << " (already defined nearby)");
duplicate = true;
break;
}
}
if (!duplicate) {
_cache->insertFix(ident, pos);
_loadedFixes.insert({ident, pos});
}
if ((lineNumber % 100) == 0) {
// every 100 lines
unsigned int percent = ((bytesReadSoFar + in.approxOffset()) * 100)
/ totalSizeOfAllDatFiles;
_cache->setRebuildPhaseProgress(NavDataCache::REBUILD_FIXES, percent);
}
}
throwExceptionIfStreamError(in, path);
}
void FixesLoader::throwExceptionIfStreamError(
const sg_gzifstream& input_stream, const SGPath& path)
{
if (input_stream.bad()) {
const std::string errMsg = simgear::strutils::error_string(errno);
SG_LOG(SG_NAVAID, SG_ALERT,
"Error while reading '" << path.utf8Str() << "': " << errMsg);
throw sg_io_exception("FixesLoader: error reading file (" + errMsg + ")",
sg_location(path));
}
}
} // of namespace flightgear;

59
src/Navaids/fixlist.hxx Normal file
View File

@@ -0,0 +1,59 @@
// fixlist.hxx -- fix list management class
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_FIXLIST_HXX
#define _FG_FIXLIST_HXX
#include <simgear/compiler.h>
#include <simgear/math/SGGeod.hxx>
#include <unordered_map>
#include <string>
class SGPath;
class sg_gzifstream;
namespace flightgear
{
class NavDataCache; // forward declaration
class FixesLoader
{
public:
FixesLoader();
~FixesLoader();
// Load fixes from the specified fix.dat (or fix.dat.gz) file
void loadFixes(const SGPath& path, std::size_t bytesReadSoFar,
std::size_t totalSizeOfAllDatFiles);
private:
void throwExceptionIfStreamError(const sg_gzifstream& input_stream,
const SGPath& path);
NavDataCache* _cache;
std::unordered_multimap<std::string, SGGeod> _loadedFixes;
};
}
#endif // _FG_FIXLIST_HXX

View File

@@ -0,0 +1,45 @@
// markerbeacon.cxx -- marker beacon class
//
// Written by James Turner, started December 2008.
//
// Copyright (C) 2008 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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"
#endif
#include <Navaids/markerbeacon.hxx>
#include <Airports/runways.hxx>
#include <Navaids/NavDataCache.hxx>
using std::string;
FGMarkerBeaconRecord::FGMarkerBeaconRecord(PositionedID aGuid, Type aTy,
PositionedID aRunway, const SGGeod& aPos) :
FGPositioned(aGuid, aTy, string(), aPos),
_runway(aRunway)
{
}
FGRunwayRef FGMarkerBeaconRecord::runway() const
{
FGPositioned* p = flightgear::NavDataCache::instance()->loadById(_runway);
assert(p->type() == FGPositioned::RUNWAY);
return static_cast<FGRunway*>(p);
}

View File

@@ -0,0 +1,44 @@
// markerbeacon.hxx -- marker beacon class
//
// Written by James Turner, started December 2008.
//
// Copyright (C) 2008 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_MARKERBEACON_HXX
#define _FG_MARKERBEACON_HXX
#include <simgear/compiler.h>
#include "positioned.hxx"
#include <Airports/airports_fwd.hxx>
class FGMarkerBeaconRecord : public FGPositioned
{
public:
FGMarkerBeaconRecord(PositionedID aGuid, Type aTy, PositionedID aRunway, const SGGeod& aPos);
FGRunwayRef runway() const;
private:
PositionedID _runway;
};
#endif // _FG_MARKERBEACON_HXX

View File

@@ -0,0 +1,30 @@
// Navaids forward declarations
//
// Copyright (C) 2013 Thomas Geymayer <tomgey@gmail.com>
//
// 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 NAVAIDS_FWD_HXX_
#define NAVAIDS_FWD_HXX_
#include <simgear/structure/SGSharedPtr.hxx>
class FGNavRecord;
class FGFix;
typedef SGSharedPtr<FGNavRecord> FGNavRecordRef;
typedef SGSharedPtr<FGFix> FGFixRef;
#endif /* NAVAIDS_FWD_HXX_ */

538
src/Navaids/navdb.cxx Normal file
View File

@@ -0,0 +1,538 @@
// navdb.cxx -- top level navaids management routines
//
// Written by Curtis Olson, started May 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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"
#endif
#include <string>
#include <vector>
#include <istream>
#include <cmath>
#include <cstddef> // std::size_t
#include <cerrno>
#include <limits>
#include <stdexcept>
#include "navdb.hxx"
#include <simgear/compiler.h>
#include <simgear/constants.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/SGGeod.hxx>
#include <simgear/math/SGMathFwd.hxx>
#include <simgear/math/SGVec3.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/props/props_io.hxx>
#include <simgear/sg_inlines.h>
#include "navlist.hxx"
#include <Main/globals.hxx>
#include <Navaids/markerbeacon.hxx>
#include <Airports/airport.hxx>
#include <Airports/runways.hxx>
#include <Airports/xmlloader.hxx>
#include <Main/fg_props.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
using std::string;
using std::vector;
namespace strutils = simgear::strutils;
// Duplicate navaids with the same ident will be removed if the disance
// between them is less than this.
static const double DUPLICATE_DETECTION_RADIUS_NM = 10;
static void throwExceptionIfStreamError(const std::istream& inputStream,
const SGPath& path)
{
if (inputStream.bad()) {
const std::string errMsg = simgear::strutils::error_string(errno);
SG_LOG(SG_NAVAID, SG_ALERT,
"Error while reading '" << path.utf8Str() << "': " << errMsg);
throw sg_io_exception("Error reading file (" + errMsg + ")",
sg_location(path));
}
}
static FGPositioned::Type
mapRobinTypeToFGPType(int aTy)
{
switch (aTy) {
case 2: return FGPositioned::NDB;
case 3: return FGPositioned::VOR;
case 4: return FGPositioned::ILS;
case 5: return FGPositioned::LOC;
case 6: return FGPositioned::GS;
case 7: return FGPositioned::OM;
case 8: return FGPositioned::MM;
case 9: return FGPositioned::IM;
case 12:
case 13: return FGPositioned::DME;
default: return FGPositioned::INVALID;
}
}
static double defaultNavRange(const string& ident, FGPositioned::Type type)
{
// Ranges are included with the latest data format, no need to
// assign our own defaults, unless the range is not set for some
// reason.
SG_LOG(SG_NAVAID, SG_DEBUG, "navaid " << ident << " has no range set, using defaults");
switch (type) {
case FGPositioned::NDB:
case FGPositioned::VOR:
return FG_NAV_DEFAULT_RANGE;
case FGPositioned::LOC:
case FGPositioned::ILS:
case FGPositioned::GS:
return FG_LOC_DEFAULT_RANGE;
case FGPositioned::DME:
return FG_DME_DEFAULT_RANGE;
case FGPositioned::TACAN:
case FGPositioned::MOBILE_TACAN:
return FG_TACAN_DEFAULT_RANGE;
default:
return FG_LOC_DEFAULT_RANGE;
}
}
namespace flightgear
{
static bool isNearby(const SGGeod& pos1, const SGGeod& pos2) {
double distNm = dist(SGVec3d::fromGeod(pos1),
SGVec3d::fromGeod(pos2)) * SG_METER_TO_NM;
return distNm <= DUPLICATE_DETECTION_RADIUS_NM;
}
static bool canBeDuplicate(FGPositionedRef ref, FGPositioned::Type type,
const std::string& name,
const SGGeod& pos, int freq)
{
if ((type >= FGPositioned::ILS) && (type <= FGPositioned::IM)) {
NavDataCache* cache = NavDataCache::instance();
AirportRunwayPair AR = cache->findAirportRunway(name);
PositionedID navaidId = cache->findNavaidForRunway(AR.second, type);
return navaidId != 0;
} else if (type == FGPositioned::DME) {
FGNavRecord* navRecord = dynamic_cast<FGNavRecord*>(ref.ptr());
return navRecord->get_freq() == freq;
} else {
return true;
}
}
// Parse a line from a file such as nav.dat or carrier_nav.dat. Load the
// corresponding data into the NavDataCache.
PositionedID NavLoader::processNavLine(
const string& line, const string& utf8Path, unsigned int lineNum,
FGPositioned::Type type, unsigned int version)
{
NavDataCache* cache = NavDataCache::instance();
int rowCode, elev_ft, freq, range;
// 'multiuse': different meanings depending on the record's row code
double lat, lon, multiuse;
// Short identifier and longer name for a navaid (e.g., 'OLN' and
// 'LFPO 02 OM')
string ident, name, arpt_code;
if (simgear::strutils::starts_with(line, "#")) {
// carrier_nav.dat has a comment line using this syntax...
return 0;
}
int num_splits;
if (version < 1100) {
// At most 9 fields (the ninth field may contain spaces)
num_splits = 8;
} else {
// At most 11 fields (the eleventh field may contain spaces)
num_splits = 10;
}
vector<string> fields(simgear::strutils::split(line, 0, num_splits));
vector<string>::size_type nbFields = fields.size();
static const string endOfData = "99"; // special code in the nav.dat spec
if (nbFields == 0) { // blank line
return 0;
} else if (nbFields == 1) {
if (fields[0] != endOfData) {
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": malformed line: only one "
"field, but it is not '99'" );
}
return 0;
} else if (nbFields < 9) {
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": invalid line "
"(at least 9 fields are required)" );
return 0;
}
// When their string argument can't be properly converted, std::stoi(),
// std::stof() and std::stod() all raise an exception which is always a
// subclass of std::logic_error.
try {
rowCode = std::stoi(fields[0]);
lat = std::stod(fields[1]);
lon = std::stod(fields[2]);
elev_ft = std::stoi(fields[3]);
freq = std::stoi(fields[4]);
range = std::stoi(fields[5]);
multiuse = std::stod(fields[6]);
ident = fields[7];
if (version >= 1100) {
// Convert names to the format present in 810 version.
// 1. fields[9] is ICAO region code, we skip over it.
// 2. For NDB, VOR and DMEs not associated with ILS,
// fields[8] is always ENRT, we skip over this too, to match
// the naming with version 810.
if ((rowCode == 2 || rowCode == 3 || rowCode == 12 || rowCode == 13)
&& fields[8] == "ENRT") {
name = fields[10];
} else {
name = fields[8] + " " + fields[10];
}
} else {
name = fields[8];
}
// Canonicalize name, removing whitespace from the beginning, the end
// and extraneous spaces between tokens.
name = simgear::strutils::simplify(name);
} catch (const std::logic_error& exc) {
// On my system using GNU libstdc++, exc.what() is limited to the function
// name (e.g., 'stod')!
SG_LOG( SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": unable to parse (" <<
exc.what() << "): '" <<
simgear::strutils::stripTrailingNewlines(line) << "'" );
return 0;
}
SGGeod pos(SGGeod::fromDegFt(lon, lat, static_cast<double>(elev_ft)));
// The type can be forced by our caller, but normally we use the value
// supplied in the .dat file.
if (type == FGPositioned::INVALID) {
type = mapRobinTypeToFGPType(rowCode);
if (type == FGPositioned::INVALID) {
static std::set<int> ignoredCodes;
if (ignoredCodes.insert(rowCode).second) {
SG_LOG(SG_NAVAID, SG_WARN,
utf8Path << ":" << lineNum << ": unrecognized row code "
<< rowCode << ", ignoring this line and all further lines "
<< "with the same code");
}
return 0;
}
}
// Silently multiply ADF frequencies by 100 so that ADF
// vs. NAV/LOC frequency lookups can use the same code.
if (type == FGPositioned::NDB) {
freq *= 100;
}
//
// Deduplication rules:
//
// 1. Navaid files are loaded according to the order in $FG_SCENERY,
// followed by the default file in $FG_ROOT/Navaids.
//
// 2. Navaids from each of these files are considered one by one and added
// to the cache, except a navaid is *not* added if another one lies within
// DUPLICATE_DETECTION_RADIUS_NM and:
//
// - it has the same name, type and ident, or
// - it has the same type and ident, and:
// * is either attached to the same runway (for navaid types LOC, ILS,
// GS, IM, MM, OM), or
// * has type FGPositioned::DME and the same frequency
// (this ensures that colocated DMEs and TACANs are *not* considered
// as duplicates, since they normally have different frequencies)
//
// For this logic to work reasonably, each set of nav.dat files in a given
// scenery path must be self-contained regarding the colocated navaids --
// if one of the colocated navaids is present, the other one must be too,
// and the files must be sorted by row codes as mandated in XP-NAV1100 spec.
//
// First, eliminate nearby with the same name, type and ident.
auto loadedNavsKey = std::make_tuple(type, ident, name);
auto matchingNavs = _loadedNavs.equal_range(loadedNavsKey);
for (auto it = matchingNavs.first; it != matchingNavs.second; ++it) {
if (isNearby(pos, it->second)) {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": skipping navaid '" <<
name << "' (already defined nearby)");
return 0;
}
}
_loadedNavs.emplace(loadedNavsKey, pos);
// Then, eliminate nearby with the same type and ident.
FGPositioned::TypeFilter dupTypeFilter(type);
FGPositionedRef ref = FGPositioned::findClosestWithIdent(ident, pos,
&dupTypeFilter);
if (ref.valid()) {
if (isNearby(pos, ref->geod())
&& canBeDuplicate(ref, type, name, pos, freq)) {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": skipping navaid '" <<
name << "' (nearby suspected duplicate '" << ref->name() << "')");
return 0;
}
}
if ((type >= FGPositioned::OM) && (type <= FGPositioned::IM)) {
AirportRunwayPair arp(cache->findAirportRunway(name));
if (!arp.first || !arp.second) {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": couldn't find matching runway " <<
"for marker '" << name << "', skipping.");
return 0;
}
if (arp.second && (elev_ft <= 0)) {
// snap to runway elevation
FGPositionedRef runway = cache->loadById(arp.second);
assert(runway);
pos.setElevationFt(runway->geod().getElevationFt());
}
return cache->insertNavaid(type, string(), name, pos, 0, 0, 0,
arp.first, arp.second);
}
if (range < 1) {
range = defaultNavRange(ident, type);
}
AirportRunwayPair arp;
FGRunwayRef runway;
PositionedID navaid_dme = 0;
if (type == FGPositioned::DME) {
// complication here: the database doesn't record TACAN sites at all,
// we simply infer their existence from DMEs whose name includes 'VORTAC'
// or 'TACAN' (since all TACAN stations include DME)
// hence the cache never actually includes entries of type TACAN
FGPositioned::TypeFilter f(FGPositioned::INVALID);
if ( name.find("VOR-DME") != std::string::npos ) {
f.addType(FGPositioned::VOR);
} else if ( name.find("DME-ILS") != std::string::npos ) {
f.addType(FGPositioned::ILS);
f.addType(FGPositioned::LOC);
} else if ( name.find("VORTAC") != std::string::npos ) {
f.addType(FGPositioned::VOR);
} else if ( name.find("NDB-DME") != std::string::npos ) {
f.addType(FGPositioned::NDB);
}
if (f.maxType() > 0) {
FGPositionedRef ref = FGPositioned::findClosestWithIdent(ident, pos, &f);
if (ref.valid()) {
string_list dme_part = simgear::strutils::split(name , 0 ,1);
string_list navaid_part = simgear::strutils::split(ref.get()->name(), 0 ,1);
if ( simgear::strutils::uppercase(navaid_part[0]) == simgear::strutils::uppercase(dme_part[0]) ) {
navaid_dme = ref.get()->guid();
} else {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": DME '" << ident << "' (" <<
name << "): while looking for a colocated navaid, found " <<
"that the closest match has wrong name: '" <<
ref->ident() << "' (" << ref->name() << ")");
}
} else {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": couldn't find any colocated "
"navaid for DME '" << ident << "' (" << name << ")");
}
} // of have a co-located navaid to locate
}
if ((type >= FGPositioned::ILS) && (type <= FGPositioned::GS)) {
arp = cache->findAirportRunway(name);
if (!arp.first || !arp.second) {
SG_LOG(SG_NAVAID, SG_INFO,
utf8Path << ":" << lineNum << ": couldn't find matching runway " <<
"for ILS/LOC/GS navaid '" << name << "', ignoring it.");
return 0;
}
if (arp.second) {
runway = FGPositioned::loadById<FGRunway>(arp.second);
assert(runway);
#if 0
// code is disabled since it's causing some problems, see
// https://sourceforge.net/p/flightgear/codetickets/926/
if (elev_ft <= 0) {
// snap to runway elevation
pos.setElevationFt(runway->geod().getElevationFt());
}
#endif
} // of found runway in the DB
} // of type is runway-related
bool isLoc = (type == FGPositioned::ILS) || (type == FGPositioned::LOC);
PositionedID r = cache->insertNavaid(type, ident, name, pos, freq, range,
multiuse, arp.first, arp.second);
if (isLoc) {
cache->setRunwayILS(arp.second, r);
}
if (navaid_dme) {
cache->setNavaidColocated(navaid_dme, r);
}
return r;
}
// load and initialize the navigational databases
void NavLoader::loadNav(const SGPath& path, std::size_t bytesReadSoFar,
std::size_t totalSizeOfAllDatFiles)
{
NavDataCache* cache = NavDataCache::instance();
const string utf8Path = path.utf8Str();
sg_gzifstream in(path);
if ( !in.is_open() ) {
throw sg_io_exception(
"Cannot open file (" + simgear::strutils::error_string(errno) + ")",
sg_location(path));
}
string line;
// Skip the first two lines
for (int i = 0; i < 2; i++) {
std::getline(in, line);
throwExceptionIfStreamError(in, path);
}
unsigned int lineNumber;
unsigned int version;
vector<string> fields(simgear::strutils::split(line, 0, 1));
try {
if (fields.empty()) {
throw sg_format_exception();
}
version = strutils::readNonNegativeInt<unsigned int>(fields[0]);
} catch (const sg_exception& exc) {
std::string strippedLine = simgear::strutils::stripTrailingNewlines(line);
std::string errMsg = utf8Path + ": ";
if (fields.empty()) {
errMsg += "unable to parse format version: empty line";
SG_LOG(SG_NAVAID, SG_ALERT, errMsg);
} else {
errMsg += "unable to parse format version";
SG_LOG(SG_NAVAID, SG_ALERT,
errMsg << " (" << exc.what() << "): " << strippedLine);
}
throw sg_format_exception(errMsg, strippedLine);
}
SG_LOG(SG_NAVAID, SG_INFO,
"nav.dat format version (" << utf8Path << "): " << version);
for (lineNumber = 3; std::getline(in, line); lineNumber++) {
processNavLine(line, utf8Path, lineNumber, FGPositioned::INVALID, version);
if ((lineNumber % 100) == 0) {
// every 100 lines
unsigned int percent = ((bytesReadSoFar + in.approxOffset()) * 100)
/ totalSizeOfAllDatFiles;
cache->setRebuildPhaseProgress(NavDataCache::REBUILD_NAVAIDS, percent);
}
} // of stream data loop
throwExceptionIfStreamError(in, path);
}
void NavLoader::loadCarrierNav(const SGPath& path)
{
SG_LOG( SG_NAVAID, SG_DEBUG, "Opening file: " << path );
const string utf8Path = path.utf8Str();
sg_gzifstream in(path);
if ( !in.is_open() ) {
throw sg_io_exception(
"Cannot open file (" + simgear::strutils::error_string(errno) + ")",
sg_location(path));
}
string line;
unsigned int lineNumber;
for (lineNumber = 1; std::getline(in, line); lineNumber++) {
// Force the navaid type to be MOBILE_TACAN
processNavLine(line, utf8Path, lineNumber, FGPositioned::MOBILE_TACAN);
}
throwExceptionIfStreamError(in, path);
}
bool NavLoader::loadTacan(const SGPath& path, FGTACANList *channellist)
{
SG_LOG( SG_NAVAID, SG_DEBUG, "opening file: " << path );
sg_gzifstream inchannel( path );
if ( !inchannel.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
return false;
}
// skip first line
inchannel >> skipeol;
while ( ! inchannel.eof() ) {
FGTACANRecord *r = new FGTACANRecord;
inchannel >> (*r);
channellist->add ( r );
} // end while
return true;
}
} // of namespace flightgear

66
src/Navaids/navdb.hxx Normal file
View File

@@ -0,0 +1,66 @@
// navdb.hxx -- top level navaids management routines
//
// Written by Curtis Olson, started May 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_NAVDB_HXX
#define _FG_NAVDB_HXX
#include <simgear/compiler.h>
#include <simgear/math/SGGeod.hxx>
#include <string>
#include <map>
#include <tuple>
#include <Navaids/positioned.hxx>
// forward decls
class FGTACANList;
class SGPath;
namespace flightgear
{
class NavLoader {
public:
// load and initialize the navigational databases
void loadNav(const SGPath& path, std::size_t bytesReadSoFar,
std::size_t totalSizeOfAllDatFiles);
void loadCarrierNav(const SGPath& path);
bool loadTacan(const SGPath& path, FGTACANList *channellist);
private:
// Maps (type, ident, name) tuples already loaded to their locations.
std::multimap<std::tuple<FGPositioned::Type, std::string, std::string>,
SGGeod> _loadedNavs;
PositionedID processNavLine(const std::string& line,
const std::string& utf8Path,
unsigned int lineNum,
FGPositioned::Type type = FGPositioned::INVALID,
unsigned int version = 810);
};
} // of namespace flightgear
#endif // _FG_NAVDB_HXX

350
src/Navaids/navlist.cxx Normal file
View File

@@ -0,0 +1,350 @@
// navlist.cxx -- navaids management class
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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>
#endif
#include <cassert>
#include <algorithm>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/sg_inlines.h>
#include "navlist.hxx"
#include <Airports/runways.hxx>
#include <Navaids/NavDataCache.hxx>
#include <Navaids/navrecord.hxx>
using std::string;
namespace { // anonymous
class NavRecordDistanceSortPredicate
{
public:
NavRecordDistanceSortPredicate( const SGGeod & position ) :
_position(SGVec3d::fromGeod(position)) {}
bool operator()( const nav_rec_ptr & n1, const nav_rec_ptr & n2 )
{
if( n1 == NULL || n2 == NULL ) return false;
return distSqr(n1->cart(), _position) < distSqr(n2->cart(), _position);
}
private:
SGVec3d _position;
};
// discount navids if they conflict with another on the same frequency
// this only applies to navids associated with opposite ends of a runway,
// with matching frequencies.
bool navidUsable(FGNavRecord* aNav, const SGGeod &aircraft)
{
FGRunway* r(aNav->runway());
if (!r || !r->reciprocalRunway()) {
return true;
}
// check if the runway frequency is paired
FGNavRecord* locA = r->ILS();
FGNavRecord* locB = r->reciprocalRunway()->ILS();
if (!locA || !locB || (locA->get_freq() != locB->get_freq())) {
return true; // not paired, ok
}
// okay, both ends have an ILS, and they're paired. We need to select based on
// aircraft position. What we're going to use is *runway* (not navid) position,
// ie whichever runway end we are closer too. This makes back-course / missed
// approach behaviour incorrect, but that's the price we accept.
double crs = SGGeodesy::courseDeg(aircraft, r->geod());
double hdgDiff = crs - r->headingDeg();
SG_NORMALIZE_RANGE(hdgDiff, -180.0, 180.0);
return (fabs(hdgDiff) < 90.0);
}
} // of anonymous namespace
// FGNavList ------------------------------------------------------------------
//------------------------------------------------------------------------------
FGNavList::TypeFilter::TypeFilter(const FGPositioned::Type type)
{
if (type == FGPositioned::INVALID) {
_mintype = FGPositioned::NDB;
_maxtype = FGPositioned::GS;
} else {
_mintype = _maxtype = type;
}
}
//------------------------------------------------------------------------------
FGNavList::TypeFilter::TypeFilter( const FGPositioned::Type minType,
const FGPositioned::Type maxType ):
_mintype(minType),
_maxtype(maxType)
{
}
//------------------------------------------------------------------------------
bool FGNavList::TypeFilter::fromTypeString(const std::string& type)
{
FGPositioned::Type t;
if( type == "any" ) t = FGPositioned::INVALID;
else if( type == "fix" ) t = FGPositioned::FIX;
else if( type == "vor" ) t = FGPositioned::VOR;
else if( type == "ndb" ) t = FGPositioned::NDB;
else if( type == "ils" ) t = FGPositioned::ILS;
else if( type == "dme" ) t = FGPositioned::DME;
else if( type == "tacan") t = FGPositioned::TACAN;
else return false;
_mintype = _maxtype = t;
return true;
}
/**
* Filter returning Tacan stations. Checks for both pure TACAN stations
* but also co-located VORTACs. This is done by searching for DMEs whose
* name indicates they are a TACAN or VORTAC; not a great solution.
*/
class TacanFilter : public FGNavList::TypeFilter
{
public:
TacanFilter() :
TypeFilter(FGPositioned::DME, FGPositioned::TACAN)
{
}
virtual bool pass(FGPositioned* pos) const
{
if (pos->type() == FGPositioned::TACAN) {
return true;
}
assert(pos->type() == FGPositioned::DME);
string::size_type loc1 = pos->name().find( "TACAN" );
string::size_type loc2 = pos->name().find( "VORTAC" );
return (loc1 != string::npos) || (loc2 != string::npos);
}
};
FGNavList::TypeFilter* FGNavList::locFilter()
{
static TypeFilter tf(FGPositioned::ILS, FGPositioned::LOC);
return &tf;
}
FGNavList::TypeFilter* FGNavList::ndbFilter()
{
static TypeFilter tf(FGPositioned::NDB);
return &tf;
}
FGNavList::TypeFilter* FGNavList::navFilter()
{
static TypeFilter tf(FGPositioned::VOR, FGPositioned::LOC);
return &tf;
}
FGNavList::TypeFilter* FGNavList::tacanFilter()
{
static TacanFilter tf;
return &tf;
}
FGNavList::TypeFilter* FGNavList::mobileTacanFilter()
{
static TypeFilter tf(FGPositioned::MOBILE_TACAN);
return &tf;
}
FGNavRecordRef FGNavList::findByFreq( double freq,
const SGGeod& position,
TypeFilter* filter )
{
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
int freqKhz = static_cast<int>(freq * 100 + 0.5);
PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, position, filter));
if (stations.empty()) {
return NULL;
}
// now walk the (sorted) results list to find a usable, in-range navaid
SGVec3d acCart(SGVec3d::fromGeod(position));
double min_dist
= FG_NAV_MAX_RANGE*SG_NM_TO_METER*FG_NAV_MAX_RANGE*SG_NM_TO_METER;
for (auto id : stations) {
FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
if (filter && !filter->pass(station)) {
continue;
}
double d2 = distSqr(station->cart(), acCart);
if (d2 > min_dist) {
// since results are sorted by proximity, as soon as we pass the
// distance cutoff we're done - fall out and return NULL
break;
}
if (navidUsable(station, position)) {
return station;
}
}
// fell out of the loop, no usable match
return NULL;
}
FGNavRecordRef FGNavList::findByFreq(double freq, TypeFilter* filter)
{
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
int freqKhz = static_cast<int>(freq * 100 + 0.5);
PositionedIDVec stations(cache->findNavaidsByFreq(freqKhz, filter));
if (stations.empty()) {
return NULL;
}
for (auto id : stations) {
FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
if (filter->pass(station)) {
return station;
}
}
return NULL;
}
nav_list_type FGNavList::findAllByFreq( double freq, const SGGeod& position,
TypeFilter* filter)
{
nav_list_type stations;
flightgear::NavDataCache* cache = flightgear::NavDataCache::instance();
// note this frequency is passed in 'database units', which depend on the
// type of navaid being requested
int f = static_cast<int>(freq * 100 + 0.5);
PositionedIDVec ids(cache->findNavaidsByFreq(f, position, filter));
for (PositionedID id : ids) {
FGNavRecordRef station = FGPositioned::loadById<FGNavRecord>(id);
if (!filter->pass(station)) {
continue;
}
stations.push_back(station);
}
return stations;
}
nav_list_type FGNavList::findByIdentAndFreq(const string& ident, const double freq,
TypeFilter* filter)
{
nav_list_type reply;
int f = (int)(freq*100.0 + 0.5);
FGPositionedList stations = FGPositioned::findAllWithIdent(ident, filter);
for (auto ref : stations) {
FGNavRecord* nav = static_cast<FGNavRecord*>(ref.ptr());
if ( f <= 0.0 || nav->get_freq() == f) {
reply.push_back( nav );
}
}
return reply;
}
// Given an Ident and optional frequency and type ,
// return a list of matching stations sorted by distance to the given position
nav_list_type FGNavList::findByIdentAndFreq( const SGGeod & position,
const std::string& ident, const double freq,
TypeFilter* filter)
{
nav_list_type reply = findByIdentAndFreq( ident, freq, filter );
NavRecordDistanceSortPredicate sortPredicate( position );
std::sort( reply.begin(), reply.end(), sortPredicate );
return reply;
}
// FGTACANList ----------------------------------------------------------------
FGTACANList::FGTACANList( void )
{
}
FGTACANList::~FGTACANList( void )
{
}
bool FGTACANList::init()
{
return true;
}
// add an entry to the lists
bool FGTACANList::add( FGTACANRecord *c )
{
ident_channels[c->get_channel()].push_back(c);
return true;
}
/*
* ref: 070031b3c01f64a44433e8cce742373f4c76b7ac
* The ground reply frequencies are used to keep channels 1-16 and 60-69 out of the VOR frequency range as those channels
* don't have VORs associated with them.
* - this method will therefore only be able to locate channels 17 to 59.
*/
FGTACANRecord *FGTACANList::findByFrequency(int frequency_kHz)
{
//029Y 10925 (encoded) = 109.25 = 109250khz - so we divide the input by 10
int tfreq = frequency_kHz / 10;
for (tacan_ident_map_type::iterator it = ident_channels.begin(); it != ident_channels.end(); it++)
{
for (tacan_list_type::iterator lit = it->second.begin(); lit != it->second.end(); lit++)
if ((*lit)->get_freq() == tfreq)
return (*lit);
}
return nullptr;
}
// Given a TACAN Channel return the first matching frequency
FGTACANRecord *FGTACANList::findByChannel( const string& channel )
{
const tacan_list_type& stations = ident_channels[channel];
SG_LOG( SG_NAVAID, SG_DEBUG, "findByChannel " << channel<< " size " << stations.size() );
if (!stations.empty()) {
return stations[0];
}
return NULL;
}

156
src/Navaids/navlist.hxx Normal file
View File

@@ -0,0 +1,156 @@
// navlist.hxx -- navaids management class
//
// Written by Curtis Olson, started April 2000.
//
// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_NAVLIST_HXX
#define _FG_NAVLIST_HXX
#include <simgear/compiler.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <map>
#include <vector>
#include <string>
#include "navrecord.hxx"
// forward decls
class SGGeod;
// FGNavList ------------------------------------------------------------------
typedef SGSharedPtr<FGNavRecord> nav_rec_ptr;
typedef std::vector < nav_rec_ptr > nav_list_type;
class FGNavList
{
public:
class TypeFilter : public FGPositioned::Filter
{
public:
TypeFilter( const FGPositioned::Type type = FGPositioned::INVALID );
TypeFilter( const FGPositioned::Type minType,
const FGPositioned::Type maxType );
/**
* Construct from string containing type
*
* @param type One of "fix"|"vor"|"ndb"|"ils"|"dme"|"tacan"|"any"
*/
bool fromTypeString(const std::string& type);
virtual FGPositioned::Type minType() const { return _mintype; }
virtual FGPositioned::Type maxType() const { return _maxtype; }
protected:
FGPositioned::Type _mintype;
FGPositioned::Type _maxtype;
};
/**
filter matching VOR & ILS/LOC transmitters
*/
static TypeFilter* navFilter();
/**
* filter matching ILS/LOC transmitter
*/
static TypeFilter* locFilter();
static TypeFilter* ndbFilter();
/**
* Filter returning TACANs and VORTACs
*/
static TypeFilter* tacanFilter();
static TypeFilter* mobileTacanFilter();
/** Query the database for the specified station. It is assumed
* that there will be multiple stations with matching frequencies
* so a position must be specified.
*/
static FGNavRecordRef findByFreq( double freq, const SGGeod& position,
TypeFilter* filter = nullptr);
/**
* Overloaded version above - no positioned supplied so can be used with
* mobile TACANs which have no valid position. The first match is
* returned only.
*/
static FGNavRecordRef findByFreq( double freq, TypeFilter* filter = NULL);
static nav_list_type findAllByFreq( double freq, const SGGeod& position,
TypeFilter* filter = NULL);
// Given an Ident and optional frequency and type ,
// return a list of matching stations.
static nav_list_type findByIdentAndFreq( const std::string& ident,
const double freq,
TypeFilter* filter = NULL);
// Given an Ident and optional frequency and type ,
// return a list of matching stations sorted by distance to the given position
static nav_list_type findByIdentAndFreq( const SGGeod & position,
const std::string& ident, const double freq = 0.0,
TypeFilter* filter = NULL);
};
// FGTACANList ----------------------------------------------------------------
typedef SGSharedPtr<FGTACANRecord> tacan_rec_ptr;
typedef std::vector < tacan_rec_ptr > tacan_list_type;
typedef std::map < int, tacan_list_type > tacan_map_type;
typedef std::map < std::string, tacan_list_type > tacan_ident_map_type;
class FGTACANList {
tacan_list_type channellist;
tacan_map_type channels;
tacan_ident_map_type ident_channels;
public:
FGTACANList();
~FGTACANList();
// initialize the TACAN list
bool init();
// add an entry
bool add( FGTACANRecord *r );
// Given a TACAN Channel, return the appropriate frequency.
FGTACANRecord *findByChannel(const std::string& channel);
// Given a TACAN Channel, return the appropriate frequency.
FGTACANRecord *findByFrequency(int frequency_kHz);
};
#endif // _FG_NAVLIST_HXX

267
src/Navaids/navrecord.cxx Normal file
View File

@@ -0,0 +1,267 @@
// navrecord.cxx -- generic vor/dme/ndb class
//
// Written by Curtis Olson, started May 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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"
#endif
#include <istream>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/sg_inlines.h>
#include <simgear/props/props.hxx>
#include <Navaids/navrecord.hxx>
#include <Navaids/navdb.hxx>
#include <Airports/runways.hxx>
#include <Airports/airport.hxx>
#include <Airports/xmlloader.hxx>
#include <Main/fg_props.hxx>
FGNavRecord::FGNavRecord(PositionedID aGuid, Type aTy, const std::string& aIdent,
const std::string& aName, const SGGeod& aPos,
int aFreq, int aRange, double aMultiuse, PositionedID aRunway) :
FGPositioned(aGuid, aTy, aIdent, aPos),
freq(aFreq),
range(aRange),
multiuse(aMultiuse),
mName(aName),
mRunway(aRunway),
mColocated(0),
serviceable(true)
{
}
FGRunwayRef FGNavRecord::runway() const
{
return loadById<FGRunway>(mRunway);
}
double FGNavRecord::localizerWidth() const
{
if (!mRunway) {
return 6.0;
}
FGRunway* rwy = runway();
SGVec3d thresholdCart(SGVec3d::fromGeod(rwy->threshold()));
double axisLength = dist(cart(), thresholdCart);
double landingLength = dist(thresholdCart, SGVec3d::fromGeod(rwy->end()));
// Reference: http://dcaa.slv.dk:8000/icaodocs/
// ICAO standard width at threshold is 210 m = 689 feet = approx 700 feet.
// ICAO 3.1.1 half course = DDM = 0.0775
// ICAO 3.1.3.7.1 Sensitivity 0.00145 DDM/m at threshold
// implies peg-to-peg of 214 m ... we will stick with 210.
// ICAO 3.1.3.7.1 "Course sector angle shall not exceed 6 degrees."
// Very short runway: less than 1200 m (4000 ft) landing length:
if (landingLength < 1200.0) {
// ICAO fudges localizer sensitivity for very short runways.
// This produces a non-monotonic sensitivity-versus length relation.
axisLength += 1050.0;
}
// Example: very short: San Diego KMYF (Montgomery Field) ILS RWY 28R
// Example: short: Tom's River KMJX (Robert J. Miller) ILS RWY 6
// Example: very long: Denver KDEN (Denver) ILS RWY 16R
double raw_width = 210.0 / axisLength * SGD_RADIANS_TO_DEGREES;
return raw_width < 6.0? raw_width : 6.0;
}
bool FGNavRecord::hasDME() const
{
return (mColocated > 0);
}
bool FGNavRecord::isVORTAC() const
{
if (mType != VOR)
return false;
return mName.find(" VORTAC") != std::string::npos;
}
void FGNavRecord::setColocatedDME(PositionedID other)
{
mColocated = other;
}
PositionedID FGNavRecord::colocatedDME() const
{
return mColocated;
}
void FGNavRecord::updateFromXML(const SGGeod& geod, double heading)
{
modifyPosition(geod);
multiuse = heading;
}
double FGNavRecord::glideSlopeAngleDeg() const
{
if (type() != FGPositioned::GS) {
SG_LOG(SG_NAVAID, SG_DEV_WARN, "called glideSlopeAngleDeg on non-GS navaid:" << ident());
return 0.0;
}
const auto tmp = static_cast<int>(get_multiuse() / 1000.0);
return static_cast<double>(tmp) / 100.0;
}
//------------------------------------------------------------------------------
FGMobileNavRecord::FGMobileNavRecord( PositionedID aGuid,
Type type,
const std::string& ident,
const std::string& name,
const SGGeod& aPos,
int freq,
int range,
double multiuse,
PositionedID aRunway ):
FGNavRecord(aGuid, type, ident, name, aPos, freq, range, multiuse, aRunway),
_initial_elevation_ft(aPos.getElevationFt())
{
}
//------------------------------------------------------------------------------
const SGGeod& FGMobileNavRecord::geod() const
{
const_cast<FGMobileNavRecord*>(this)->updatePos();
return FGNavRecord::geod();
}
//------------------------------------------------------------------------------
const SGVec3d& FGMobileNavRecord::cart() const
{
const_cast<FGMobileNavRecord*>(this)->updatePos();
return FGNavRecord::cart();
}
//------------------------------------------------------------------------------
void FGMobileNavRecord::clearVehicle()
{
_vehicle_node.clear();
}
void FGMobileNavRecord::updateVehicle()
{
_vehicle_node.clear();
SGPropertyNode* ai_branch = fgGetNode("ai/models");
if( !ai_branch )
{
SG_LOG( SG_NAVAID,
SG_INFO,
"Can not update mobile navaid position (no ai/models branch)" );
return;
}
const std::string& nav_name = name();
// Try any aircraft carriers first
simgear::PropertyList carrier = ai_branch->getChildren("carrier");
for(size_t i = 0; i < carrier.size(); ++i)
{
const std::string carrier_name = carrier[i]->getStringValue("name");
if( carrier_name.empty()
|| nav_name.find(carrier_name) == std::string::npos )
continue;
_vehicle_node = carrier[i];
return;
}
// Now the tankers
const std::string tanker_branches[] = {
// AI tankers
"tanker",
// And finally mp tankers
"multiplayer"
};
for(size_t i = 0; i < sizeof(tanker_branches)/sizeof(tanker_branches[0]); ++i)
{
simgear::PropertyList tanker = ai_branch->getChildren(tanker_branches[i]);
for(size_t j = 0; j < tanker.size(); ++j)
{
const std::string callsign = tanker[j]->getStringValue("callsign");
if( callsign.empty()
|| nav_name.find(callsign) == std::string::npos )
continue;
_vehicle_node = tanker[j];
return;
}
}
}
//------------------------------------------------------------------------------
void FGMobileNavRecord::updatePos()
{
SGTimeStamp now = SGTimeStamp::now();
if( (now - _last_vehicle_update).toSecs() > (_vehicle_node.valid() ? 5 : 2) )
{
updateVehicle();
_last_vehicle_update = now;
}
if( _vehicle_node.valid() )
modifyPosition(SGGeod::fromDegFt(
_vehicle_node->getDoubleValue("position/longitude-deg"),
_vehicle_node->getDoubleValue("position/latitude-deg"),
_vehicle_node->getNameString() == "carrier"
? _initial_elevation_ft
: _vehicle_node->getDoubleValue("position/altitude-ft")
));
else
invalidatePosition();
serviceable = _vehicle_node.valid();
}
//------------------------------------------------------------------------------
FGTACANRecord::FGTACANRecord(void) :
channel(""),
freq(0)
{
}
std::istream&
operator >> ( std::istream& in, FGTACANRecord& n )
{
in >> n.channel >> n.freq ;
//getline( in, n.name );
return in;
}

173
src/Navaids/navrecord.hxx Normal file
View File

@@ -0,0 +1,173 @@
// navrecord.hxx -- generic vor/dme/ndb class
//
// Written by Curtis Olson, started May 2004.
//
// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// 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_NAVRECORD_HXX
#define _FG_NAVRECORD_HXX
#include <iosfwd>
#include "navaids_fwd.hxx"
#include "positioned.hxx"
#include <Airports/airports_fwd.hxx>
#include <simgear/props/propsfwd.hxx>
#include <simgear/timing/timestamp.hxx>
const double FG_NAV_DEFAULT_RANGE = 50; // nm
const double FG_LOC_DEFAULT_RANGE = 18; // nm
const double FG_DME_DEFAULT_RANGE = 50; // nm
const double FG_TACAN_DEFAULT_RANGE = 250; // nm
const double FG_NAV_MAX_RANGE = 300; // nm
class FGNavRecord : public FGPositioned
{
int freq;
int range;
double multiuse; // can be slaved variation of VOR
// (degrees) or localizer heading
// (degrees) or dme bias (nm)
std::string mName; // verbose name in nav database
PositionedID mRunway; // associated runway, if there is one
PositionedID mColocated; // Colocated DME at a navaid (ILS, VOR, TACAN, NDB)
protected:
mutable bool serviceable; // for failure modeling
public:
static bool isType(FGPositioned::Type ty)
{ return (ty >= FGPositioned::NDB) && (ty <= FGPositioned::DME); }
FGNavRecord( PositionedID aGuid,
Type type,
const std::string& ident,
const std::string& name,
const SGGeod& aPos,
int freq,
int range,
double multiuse,
PositionedID aRunway );
inline double get_lon() const { return longitude(); } // degrees
inline double get_lat() const { return latitude(); } // degrees
inline double get_elev_ft() const { return elevation(); }
inline int get_freq() const { return freq; }
inline int get_range() const { return range; }
inline double get_multiuse() const { return multiuse; }
inline void set_multiuse( double m ) { multiuse = m; }
inline const char *get_ident() const { return ident().c_str(); }
inline bool get_serviceable() const { return serviceable; }
inline const char *get_trans_ident() const { return get_ident(); }
virtual const std::string& name() const
{ return mName; }
/**
* Retrieve the runway this navaid is associated with (for ILS/LOC/GS)
*/
FGRunwayRef runway() const;
/**
* return the localizer width, in degrees
* computation is based up ICAO stdandard width at the runway threshold
* see implementation for further details.
*/
double localizerWidth() const;
/**
* extract the glide slope angle, in degrees, from the multiuse field
* Return 0.0 for non-GS navaids (including an ILS or LOC - you need
* to call this on the paired GS record.
*/
double glideSlopeAngleDeg() const;
void bindToNode(SGPropertyNode* nd) const;
void unbindFromNode(SGPropertyNode* nd) const;
void setColocatedDME(PositionedID other);
bool hasDME() const;
PositionedID colocatedDME() const;
bool isVORTAC() const;
void updateFromXML(const SGGeod& geod, double heading);
};
/**
* A mobile navaid, aka. a navaid which can change its position (eg. a mobile
* TACAN)
*/
class FGMobileNavRecord:
public FGNavRecord
{
public:
static bool isType(FGPositioned::Type ty)
{
return (ty == MOBILE_TACAN);
}
FGMobileNavRecord( PositionedID aGuid,
Type type,
const std::string& ident,
const std::string& name,
const SGGeod& aPos,
int freq,
int range,
double multiuse,
PositionedID aRunway );
virtual const SGGeod& geod() const;
virtual const SGVec3d& cart() const;
void updateVehicle();
void updatePos();
void clearVehicle();
protected:
SGTimeStamp _last_vehicle_update;
SGPropertyNode_ptr _vehicle_node;
double _initial_elevation_ft; // Elevation as given in the config file
};
class FGTACANRecord : public SGReferenced {
std::string channel;
int freq;
public:
FGTACANRecord(void);
inline ~FGTACANRecord(void) {}
inline const std::string& get_channel() const { return channel; }
inline int get_freq() const { return freq; }
friend std::istream& operator>> ( std::istream&, FGTACANRecord& );
};
#endif // _FG_NAVRECORD_HXX

118
src/Navaids/poidb.cxx Normal file
View File

@@ -0,0 +1,118 @@
// 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$
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <istream> // std::ws
#include "poidb.hxx"
#include <simgear/compiler.h>
#include <simgear/debug/logstream.hxx>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <Navaids/NavDataCache.hxx>
using std::string;
static FGPositioned::Type
mapPOITypeToFGPType(int aTy)
{
switch (aTy) {
case 10: return FGPositioned::COUNTRY;
case 12: return FGPositioned::CITY;
case 13: return FGPositioned::TOWN;
case 14: return FGPositioned::VILLAGE;
default:
throw sg_range_exception("Unknown POI type", "FGNavDataCache::readPOIFromStream");
}
}
namespace flightgear
{
const int LINES_IN_POI_DAT = 769019;
static PositionedID readPOIFromStream(std::istream& aStream, NavDataCache* cache,
FGPositioned::Type type = FGPositioned::INVALID)
{
if (aStream.eof()) {
return 0;
}
aStream >> std::ws;
if (aStream.peek() == '#') {
aStream >> skipeol;
return 0;
}
int rawType;
aStream >> rawType;
double lat, lon;
std::string name;
aStream >> lat >> lon;
getline(aStream, name);
SGGeod pos(SGGeod::fromDeg(lon, lat));
name = simgear::strutils::strip(name);
// the type can be forced by our caller, but normally we use the value
// supplied in the .dat file
if (type == FGPositioned::INVALID) {
type = mapPOITypeToFGPType(rawType);
}
if (type == FGPositioned::INVALID) {
return 0;
}
return cache->createPOI(type, name, pos);
}
// load and initialize the POI database
bool poiDBInit(const SGPath& path)
{
sg_gzifstream in( path );
if ( !in.is_open() ) {
SG_LOG( SG_NAVAID, SG_ALERT, "Cannot open file: " << path );
return false;
}
unsigned int lineNumber = 0;
NavDataCache* cache = NavDataCache::instance();
while (!in.eof()) {
readPOIFromStream(in, cache);
++lineNumber;
if ((lineNumber % 100) == 0) {
// every 100 lines
unsigned int percent = (lineNumber * 100) / LINES_IN_POI_DAT;
cache->setRebuildPhaseProgress(NavDataCache::REBUILD_POIS, percent);
}
} // of stream data loop
return true;
}
} // of namespace flightgear

40
src/Navaids/poidb.hxx Normal file
View File

@@ -0,0 +1,40 @@
// 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$
#ifndef _FG_POIDB_HXX
#define _FG_POIDB_HXX
#include <simgear/compiler.h>
// forward decls
class SGPath;
namespace flightgear
{
// load and initialize the POI database
bool poiDBInit(const SGPath& path);
} // of namespace flightgear
#endif // _FG_NAVDB_HXX

487
src/Navaids/positioned.cxx Normal file
View File

@@ -0,0 +1,487 @@
// 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$
#include "config.h"
#include "positioned.hxx"
#include <map>
#include <set>
#include <cstring> // strcmp
#include <algorithm> // for sort
#include <queue>
#include <memory>
#include <simgear/timing/timestamp.hxx>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/exception.hxx>
#include <simgear/math/SGGeometry.hxx>
#include <simgear/sg_inlines.h>
#include "Navaids/PositionedOctree.hxx"
using std::string;
using namespace flightgear;
static void validateSGGeod(const SGGeod& geod)
{
if (SGMisc<double>::isNaN(geod.getLatitudeDeg()) ||
SGMisc<double>::isNaN(geod.getLongitudeDeg()))
{
throw sg_range_exception("position is invalid, NaNs");
}
}
static bool validateFilter(FGPositioned::Filter* filter)
{
if (filter->maxType() < filter->minType()) {
SG_LOG(SG_GENERAL, SG_WARN, "invalid positioned filter specified");
return false;
}
return true;
}
const PositionedID FGPositioned::TRANSIENT_ID = -2;
///////////////////////////////////////////////////////////////////////////////
FGPositioned::FGPositioned( PositionedID aGuid,
Type ty,
const std::string& aIdent,
const SGGeod& aPos ):
mGuid(aGuid),
mType(ty),
mIdent(aIdent),
mPosition(aPos),
mCart(SGVec3d::fromGeod(mPosition))
{
}
FGPositioned::~FGPositioned()
{
}
// Static method
bool FGPositioned::isAirportType(FGPositioned* pos)
{
if (!pos) {
return false;
}
return (pos->type() >= AIRPORT) && (pos->type() <= SEAPORT);
}
// Static method
bool FGPositioned::isRunwayType(FGPositioned* pos)
{
return (pos && pos->type() == RUNWAY);
}
// Static method
bool FGPositioned::isNavaidType(FGPositioned* pos)
{
if (!pos) {
return false;
}
switch (pos->type()) {
case NDB:
case VOR:
case ILS:
case LOC:
case GS:
case DME:
case TACAN:
return true;
default:
return false;
}
}
FGPositionedRef
FGPositioned::createUserWaypoint(const std::string& aIdent, const SGGeod& aPos)
{
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();
}
PositionedID id = cache->createPOI(WAYPOINT, aIdent, aPos);
return cache->loadById(id);
}
bool FGPositioned::deleteUserWaypoint(const std::string& aIdent)
{
NavDataCache* cache = NavDataCache::instance();
return cache->removePOI(WAYPOINT, aIdent);
}
const SGVec3d&
FGPositioned::cart() const
{
return mCart;
}
FGPositioned::Type FGPositioned::typeFromName(const std::string& aName)
{
if (aName.empty() || (aName == "")) {
return INVALID;
}
typedef struct {
const char* _name;
Type _ty;
} NameTypeEntry;
const NameTypeEntry names[] = {
{"airport", AIRPORT},
{"heliport", HELIPORT},
{"seaport", SEAPORT},
{"vor", VOR},
{"loc", LOC},
{"ils", ILS},
{"gs", GS},
{"ndb", NDB},
{"wpt", WAYPOINT},
{"fix", FIX},
{"tacan", TACAN},
{"dme", DME},
{"atis", FREQ_ATIS},
{"awos", FREQ_AWOS},
{"tower", FREQ_TOWER},
{"ground", FREQ_GROUND},
{"approach", FREQ_APP_DEP},
{"departure", FREQ_APP_DEP},
{"clearance", FREQ_CLEARANCE},
{"unicom", FREQ_UNICOM},
{"runway", RUNWAY},
{"helipad", HELIPAD},
{"country", COUNTRY},
{"city", CITY},
{"town", TOWN},
{"village", VILLAGE},
{"taxiway", TAXIWAY},
{"pavement", PAVEMENT},
{"om", OM},
{"mm", MM},
{"im", IM},
{"mobile-tacan", MOBILE_TACAN},
{"obstacle", OBSTACLE},
{"parking", PARKING},
{"taxi-node",TAXI_NODE},
// aliases
{"localizer", LOC},
{"gnd", FREQ_GROUND},
{"twr", FREQ_TOWER},
{"waypoint", WAYPOINT},
{"apt", AIRPORT},
{"arpt", AIRPORT},
{"rwy", RUNWAY},
{"any", INVALID},
{"all", INVALID},
{"outer-marker", OM},
{"middle-marker", MM},
{"inner-marker", IM},
{"parking-stand", PARKING},
{NULL, INVALID}
};
std::string lowerName = simgear::strutils::lowercase(aName);
for (const NameTypeEntry* n = names; (n->_name != NULL); ++n) {
if (::strcmp(n->_name, lowerName.c_str()) == 0) {
return n->_ty;
}
}
SG_LOG(SG_NAVAID, SG_WARN, "FGPositioned::typeFromName: couldn't match:" << aName);
return INVALID;
}
const char* FGPositioned::nameForType(Type aTy)
{
switch (aTy) {
case RUNWAY: return "runway";
case HELIPAD: return "helipad";
case TAXIWAY: return "taxiway";
case PAVEMENT: return "pavement";
case PARKING: return "parking stand";
case FIX: return "fix";
case VOR: return "VOR";
case NDB: return "NDB";
case ILS: return "ILS";
case LOC: return "localizer";
case GS: return "glideslope";
case OM: return "outer-marker";
case MM: return "middle-marker";
case IM: return "inner-marker";
case AIRPORT: return "airport";
case HELIPORT: return "heliport";
case SEAPORT: return "seaport";
case WAYPOINT: return "waypoint";
case DME: return "dme";
case TACAN: return "tacan";
case FREQ_TOWER: return "tower";
case FREQ_ATIS: return "atis";
case FREQ_AWOS: return "awos";
case FREQ_GROUND: return "ground";
case FREQ_CLEARANCE: return "clearance";
case FREQ_UNICOM: return "unicom";
case FREQ_APP_DEP: return "approach-departure";
case TAXI_NODE: return "taxi-node";
case COUNTRY: return "country";
case CITY: return "city";
case TOWN: return "town";
case VILLAGE: return "village";
default:
return "unknown";
}
}
///////////////////////////////////////////////////////////////////////////////
// search / query functions
FGPositionedRef
FGPositioned::findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter)
{
validateSGGeod(aPos);
return NavDataCache::instance()->findClosestWithIdent(aIdent, aPos, aFilter);
}
FGPositionedRef
FGPositioned::findFirstWithIdent(const std::string& aIdent, Filter* aFilter)
{
if (aIdent.empty()) {
return NULL;
}
FGPositionedList r =
NavDataCache::instance()->findAllWithIdent(aIdent, aFilter, true);
if (r.empty()) {
return NULL;
}
return r.front();
}
FGPositionedList
FGPositioned::findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter)
{
validateSGGeod(aPos);
if (!validateFilter(aFilter)) {
return FGPositionedList();
}
FGPositionedList result;
Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
aRangeNm * SG_NM_TO_METER, aFilter, result, 0xffffff);
return result;
}
FGPositionedList
FGPositioned::findWithinRangePartial(const SGGeod& aPos, double aRangeNm, Filter* aFilter, bool& aPartial)
{
validateSGGeod(aPos);
if (!validateFilter(aFilter)) {
return FGPositionedList();
}
int limitMsec = 32;
FGPositionedList result;
aPartial = Octree::findAllWithinRange(SGVec3d::fromGeod(aPos),
aRangeNm * SG_NM_TO_METER, aFilter, result,
limitMsec);
return result;
}
FGPositionedList
FGPositioned::findAllWithIdent(const std::string& aIdent, Filter* aFilter, bool aExact)
{
if (!validateFilter(aFilter)) {
return FGPositionedList();
}
return NavDataCache::instance()->findAllWithIdent(aIdent, aFilter, aExact);
}
FGPositionedList
FGPositioned::findAllWithName(const std::string& aName, Filter* aFilter, bool aExact)
{
if (!validateFilter(aFilter)) {
return FGPositionedList();
}
return NavDataCache::instance()->findAllWithName(aName, aFilter, aExact);
}
FGPositionedRef
FGPositioned::findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter)
{
validateSGGeod(aPos);
if (!validateFilter(aFilter)) {
return NULL;
}
FGPositionedList l(findClosestN(aPos, 1, aCutoffNm, aFilter));
if (l.empty()) {
return NULL;
}
assert(l.size() == 1);
return l.front();
}
FGPositionedList
FGPositioned::findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter)
{
validateSGGeod(aPos);
FGPositionedList result;
int limitMsec = 0xffff;
Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result, limitMsec);
return result;
}
FGPositionedList
FGPositioned::findClosestNPartial(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter, bool &aPartial)
{
validateSGGeod(aPos);
FGPositionedList result;
int limitMsec = 32;
aPartial = Octree::findNearestN(SGVec3d::fromGeod(aPos), aN, aCutoffNm * SG_NM_TO_METER, aFilter, result,
limitMsec);
return result;
}
void
FGPositioned::sortByRange(FGPositionedList& aResult, const SGGeod& aPos)
{
validateSGGeod(aPos);
SGVec3d cartPos(SGVec3d::fromGeod(aPos));
// computer ordering values
Octree::FindNearestResults r;
FGPositionedList::iterator it = aResult.begin(), lend = aResult.end();
for (; it != lend; ++it) {
double d2 = distSqr((*it)->cart(), cartPos);
r.push_back(Octree::OrderedPositioned(*it, d2));
}
// sort
std::sort(r.begin(), r.end());
// convert to a plain list
unsigned int count = aResult.size();
for (unsigned int i=0; i<count; ++i) {
aResult[i] = r[i].get();
}
}
//------------------------------------------------------------------------------
void FGPositioned::modifyPosition(const SGGeod& newPos)
{
const_cast<SGGeod&>(mPosition) = newPos;
const_cast<SGVec3d&>(mCart) = SGVec3d::fromGeod(newPos);
}
//------------------------------------------------------------------------------
void FGPositioned::invalidatePosition()
{
const_cast<SGGeod&>(mPosition) = SGGeod::fromDeg(999,999);
const_cast<SGVec3d&>(mCart) = SGVec3d::zeros();
}
//------------------------------------------------------------------------------
FGPositionedRef FGPositioned::loadByIdImpl(PositionedID id)
{
return flightgear::NavDataCache::instance()->loadById(id);
}
FGPositioned::TypeFilter::TypeFilter(Type aTy)
{
addType(aTy);
}
FGPositioned::TypeFilter::TypeFilter(std::initializer_list<Type> types)
{
for (auto t : types) {
addType(t);
}
}
void FGPositioned::TypeFilter::addType(Type aTy)
{
if (aTy == INVALID) {
return;
}
types.push_back(aTy);
mMinType = std::min(mMinType, aTy);
mMaxType = std::max(mMaxType, aTy);
}
FGPositioned::TypeFilter
FGPositioned::TypeFilter::fromString(const std::string& aFilterSpec)
{
if (aFilterSpec.empty()) {
throw sg_format_exception("empty filter spec:", aFilterSpec);
}
string_list parts = simgear::strutils::split(aFilterSpec, ",");
TypeFilter f;
for (std::string token : parts) {
f.addType(typeFromName(token));
}
return f;
}
bool
FGPositioned::TypeFilter::pass(FGPositioned* aPos) const
{
if (types.empty()) {
return true;
}
std::vector<Type>::const_iterator it = types.begin(),
end = types.end();
for (; it != end; ++it) {
if (aPos->type() == *it) {
return true;
}
}
return false;
}

331
src/Navaids/positioned.hxx Normal file
View File

@@ -0,0 +1,331 @@
// 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$
#ifndef FG_POSITIONED_HXX
#define FG_POSITIONED_HXX
#include <cassert>
#include <string>
#include <vector>
#include <stdint.h>
#include <simgear/sg_inlines.h>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/math/SGMath.hxx>
class FGPositioned;
typedef SGSharedPtr<FGPositioned> FGPositionedRef;
typedef std::vector<FGPositionedRef> FGPositionedList;
typedef int64_t PositionedID;
typedef std::vector<PositionedID> PositionedIDVec;
namespace flightgear { class NavDataCache; }
class FGPositioned : public SGReferenced
{
public:
static const PositionedID TRANSIENT_ID;
typedef enum {
INVALID = 0,
AIRPORT,
HELIPORT,
SEAPORT,
RUNWAY,
HELIPAD,
TAXIWAY,
PAVEMENT,
WAYPOINT,
FIX,
NDB,
VOR,
ILS,
LOC,
GS,
OM,
MM,
IM,
/// important that DME & TACAN are adjacent to keep the TacanFilter
/// efficient - DMEs are proxies for TACAN/VORTAC stations
DME,
TACAN,
MOBILE_TACAN,
OBSTACLE,
/// an actual airport tower - not a radio comms facility!
/// some airports have multiple towers, eg EHAM, although our data source
/// doesn't necessarily include them
TOWER,
FREQ_GROUND,
FREQ_TOWER,
FREQ_ATIS,
FREQ_AWOS,
FREQ_APP_DEP,
FREQ_ENROUTE,
FREQ_CLEARANCE,
FREQ_UNICOM,
// groundnet items
PARKING, ///< parking position - might be a gate, or stand
TAXI_NODE,
// POI items
COUNTRY,
CITY,
TOWN,
VILLAGE,
LAST_TYPE
} Type;
virtual ~FGPositioned();
Type type() const
{ return mType; }
// True for the following types: AIRPORT, HELIPORT, SEAPORT.
// False for other types, as well as if pos == nullptr.
static bool isAirportType(FGPositioned* pos);
// True for the following type: RUNWAY.
// False for other types, as well as if pos == nullptr.
static bool isRunwayType(FGPositioned* pos);
// True for the following types: NDB, VOR, ILS, LOC, GS, DME, TACAN.
// False for other types, as well as if pos == nullptr.
static bool isNavaidType(FGPositioned* pos);
const char* typeString() const
{ return nameForType(mType); }
const std::string& ident() const
{ return mIdent; }
/**
* Return the name of this positioned. By default this is the same as the
* ident, but for many derived classes it's more meaningful - the aiport or
* navaid name, for example.
*/
virtual const std::string& name() const
{ return mIdent; }
virtual const SGGeod& geod() const
{ return mPosition; }
PositionedID guid() const
{ return mGuid; }
/**
* The cartesian position associated with this object
*/
virtual const SGVec3d& cart() const;
double latitude() const
{ return geod().getLatitudeDeg(); }
double longitude() const
{ return geod().getLongitudeDeg(); }
double elevation() const
{ return geod().getElevationFt(); }
double elevationM() const
{ return geod().getElevationM(); }
/**
* Predicate class to support custom filtering of FGPositioned queries
* Default implementation of this passes any FGPositioned instance.
*/
class Filter
{
public:
virtual ~Filter() { ; }
/**
* Over-rideable filter method. Default implementation returns true.
*/
virtual bool pass(FGPositioned* aPos) const
{ return true; }
virtual Type minType() const
{ return INVALID; }
virtual Type maxType() const
{ return INVALID; }
bool operator()(FGPositioned* aPos) const
{ return pass(aPos); }
};
class TypeFilter : public Filter
{
public:
TypeFilter(Type aTy = INVALID);
TypeFilter(std::initializer_list<Type> types);
bool pass(FGPositioned* aPos) const override;
Type minType() const override
{ return mMinType; }
Type maxType() const override
{ return mMaxType; }
void addType(Type aTy);
static TypeFilter fromString(const std::string& aFilterSpec);
private:
std::vector<Type> types;
Type mMinType = LAST_TYPE,
mMaxType = INVALID;
};
static FGPositionedList findWithinRange(const SGGeod& aPos, double aRangeNm, Filter* aFilter);
static FGPositionedList findWithinRangePartial(const SGGeod& aPos, double aRangeNm, Filter* aFilter, bool& aPartial);
static FGPositionedRef findClosestWithIdent(const std::string& aIdent, const SGGeod& aPos, Filter* aFilter = NULL);
static FGPositionedRef findFirstWithIdent(const std::string& aIdent, Filter* aFilter);
/**
* Find all items with the specified ident
* @param aFilter - optional filter on items
*/
static FGPositionedList findAllWithIdent(const std::string& aIdent, Filter* aFilter = NULL, bool aExact = true);
/**
* As above, but searches names instead of idents
*/
static FGPositionedList findAllWithName(const std::string& aName, Filter* aFilter = NULL, bool aExact = true);
/**
* Sort an FGPositionedList by distance from a position
*/
static void sortByRange(FGPositionedList&, const SGGeod& aPos);
/**
* Find the closest item to a position, which pass the specified filter
* A cutoff range in NM must be specified, to constrain the search acceptably.
* Very large cutoff values will make this slow.
*
* @result The closest item passing the filter, or NULL
* @param aCutoffNm - maximum distance to search within, in nautical miles
*/
static FGPositionedRef findClosest(const SGGeod& aPos, double aCutoffNm, Filter* aFilter = NULL);
/**
* Find the closest N items to a position, which pass the specified filter
* A cutoff range in NM must be specified, to constrain the search acceptably.
* Very large cutoff values will make this slow.
*
* @result The matches (possibly less than N, depending on the filter and cutoff),
* sorted by distance from the search pos
* @param aN - number of matches to find
* @param aCutoffNm - maximum distance to search within, in nautical miles
*/
static FGPositionedList findClosestN(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter = NULL);
/**
* Same as above, but with a time-bound in msec too.
*/
static FGPositionedList findClosestNPartial(const SGGeod& aPos, unsigned int aN, double aCutoffNm, Filter* aFilter,
bool& aPartial);
template<class T>
static SGSharedPtr<T> loadById(PositionedID id)
{
return static_pointer_cast<T>( loadByIdImpl(id) );
}
template<class T>
static SGSharedPtr<T> loadById(const PositionedIDVec& id_vec, size_t index)
{
assert(index >= 0 && index < id_vec.size());
return loadById<T>(id_vec[index]);
}
template<class T>
static std::vector<SGSharedPtr<T> > loadAllById(const PositionedIDVec& id_vec)
{
std::vector<SGSharedPtr<T> > vec(id_vec.size());
for(size_t i = 0; i < id_vec.size(); ++i)
vec[i] = loadById<T>(id_vec[i]);
return vec;
}
/**
* Map a candidate type string to a real type. Returns INVALID if the string
* does not correspond to a defined type.
*/
static Type typeFromName(const std::string& aName);
/**
* Map a type to a human-readable string
*/
static const char* nameForType(Type aTy);
static FGPositionedRef createUserWaypoint(const std::string& aIdent, const SGGeod& aPos);
static bool deleteUserWaypoint(const std::string& aIdent);
protected:
friend class flightgear::NavDataCache;
FGPositioned(PositionedID aGuid, Type ty, const std::string& aIdent, const SGGeod& aPos);
void modifyPosition(const SGGeod& newPos);
void invalidatePosition();
static FGPositionedRef loadByIdImpl(PositionedID id);
const PositionedID mGuid;
const Type mType;
const std::string mIdent;
private:
SG_DISABLE_COPY(FGPositioned);
const SGGeod mPosition;
const SGVec3d mCart;
};
template <class T>
T* fgpositioned_cast(FGPositioned* p)
{
if (T::isType(p->type())) {
return static_cast<T*>(p);
}
return nullptr;
}
template <class T>
T* fgpositioned_cast(FGPositionedRef p)
{
if (!p) return nullptr;
if (T::isType(p->type())) {
return static_cast<T*>(p.ptr());
}
return nullptr;
}
#endif // of FG_POSITIONED_HXX

484
src/Navaids/procedure.cxx Normal file
View File

@@ -0,0 +1,484 @@
// procedure.cxx - define route storing an approach, arrival or departure procedure
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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 "procedure.hxx"
#include <cassert>
#include <algorithm> // for reverse_copy
#include <simgear/structure/exception.hxx>
#include <Airports/runways.hxx>
#include <Navaids/waypoint.hxx>
#include <iterator> // required for WIN
using std::string;
namespace flightgear
{
static void markWaypoints(WayptVec& wps, WayptFlag f)
{
for (unsigned int i=0; i<wps.size(); ++i) {
wps[i]->setFlag(f, true);
}
}
Procedure::Procedure(const string& aIdent) :
_ident(aIdent)
{
}
Approach::Approach(const string& aIdent, ProcedureType ty) :
Procedure(aIdent),
_type(ty)
{
}
Approach* Approach::createTempApproach(const std::string& aIdent, FGRunway* aRunway, const WayptVec& aPath)
{
Approach* app = new Approach(aIdent, PROCEDURE_APPROACH_RNAV);
app->setRunway(aRunway);
app->setPrimaryAndMissed(aPath, WayptVec());
return app;
}
void Approach::setRunway(FGRunwayRef aRwy)
{
_runway = aRwy;
}
FGAirport* Approach::airport() const
{
return _runway->airport();
}
RunwayVec Approach::runways() const
{
RunwayVec r;
r.push_back(_runway);
return r;
}
void Approach::setPrimaryAndMissed(const WayptVec& aPrimary, const WayptVec& aMissed)
{
_primary = aPrimary;
_primary[0]->setFlag(WPT_IAF, true);
_primary[_primary.size()-1]->setFlag(WPT_FAF, true);
markWaypoints(_primary, WPT_APPROACH);
_missed = aMissed;
if (!_missed.empty()) {
// mark the first point as the published missed-approach point
_missed[0]->setFlag(WPT_MAP, true);
markWaypoints(_missed, WPT_MISS);
markWaypoints(_missed, WPT_APPROACH);
}
}
void Approach::addTransition(Transition* aTrans)
{
WayptRef entry = aTrans->enroute();
_transitions[entry] = aTrans;
aTrans->mark(WPT_APPROACH);
}
bool Approach::routeWithTransition(FGRunwayRef runway, Transition* trans, WayptVec& aWps)
{
if (!trans)
return false;
trans->route(aWps);
bool ok = routeFromVectors(aWps);
return ok;
}
bool Approach::route(FGRunwayRef runway, WayptRef aIAF, WayptVec& aWps)
{
if (aIAF.valid()) {
bool haveTrans = false;
for (auto te : _transitions) {
auto t = te.second;
if (t->enroute()->matches(aIAF)) {
t->route(aWps);
haveTrans = true;
break;
}
} // of transitions iteration
if (!haveTrans) {
if (_primary.front()->matches(aIAF)) {
// direct IAF on the approach, no transition is needed
} else {
// we couldn't find the IAF at the front of any obvious thing - either
// the primary waypoints or any transition we have defined.
// warn and just use the primary waypoints down below
SG_LOG(SG_NAVAID, SG_INFO, "approach " << ident() << " has no transition " <<
"for IAF: " << aIAF->ident());
}
}
}
bool ok = routeFromVectors(aWps);
if (ok && !aWps.empty() && aIAF.valid() && aWps.front()->matches(aIAF)) {
// don't duplicate the IAF into the route we return. This avoids a
// duplicated waypt between the end of a STAR and the approach
aWps.erase(aWps.begin());
}
return ok;
}
bool Approach::routeFromVectors(WayptVec& aWps)
{
aWps.insert(aWps.end(), _primary.begin(), _primary.end());
RunwayWaypt* rwy = new RunwayWaypt(_runway, NULL);
rwy->setFlag(WPT_APPROACH);
aWps.push_back(rwy);
aWps.insert(aWps.end(), _missed.begin(), _missed.end());
return true;
}
bool Approach::isApproach(ProcedureType ty)
{
return (ty >= PROCEDURE_APPROACH_ILS) && (ty <= PROCEDURE_APPROACH_RNAV);
}
string_list Approach::transitionIdents() const
{
string_list r;
r.reserve(_transitions.size());
for (const auto& t : _transitions) {
r.push_back(t.second->ident());
}
return r;
}
Transition* Approach::findTransitionByName(const string& aIdent) const
{
auto it = std::find_if(_transitions.begin(), _transitions.end(), [aIdent](const WptTransitionMap::value_type& t) {
return aIdent == t.second->ident();
});
if (it == _transitions.end())
return nullptr;
return it->second;
}
//////////////////////////////////////////////////////////////////////////////
ArrivalDeparture::ArrivalDeparture(const string& aIdent, FGAirport* apt) :
Procedure(aIdent),
_airport(apt)
{
}
void ArrivalDeparture::addRunway(FGRunwayRef aWay)
{
assert(aWay->airport() == _airport);
_runways[aWay] = NULL;
}
bool ArrivalDeparture::isForRunway(const FGRunway* aWay) const
{
// null runway always passes
if (!aWay) {
return true;
}
FGRunwayRef r(const_cast<FGRunway*>(aWay));
return (_runways.count(r) > 0);
}
RunwayVec ArrivalDeparture::runways() const
{
RunwayVec r;
RunwayTransitionMap::const_iterator it = _runways.begin();
for (; it != _runways.end(); ++it) {
r.push_back(it->first);
}
return r;
}
void ArrivalDeparture::addTransition(Transition* aTrans)
{
WayptRef entry = aTrans->enroute();
aTrans->mark(flagType());
_enrouteTransitions[entry] = aTrans;
}
string_list ArrivalDeparture::transitionIdents() const
{
string_list r;
WptTransitionMap::const_iterator eit;
for (eit = _enrouteTransitions.begin(); eit != _enrouteTransitions.end(); ++eit) {
r.push_back(eit->second->ident());
}
return r;
}
void ArrivalDeparture::addRunwayTransition(FGRunwayRef aWay, Transition* aTrans)
{
assert(aWay->ident() == aTrans->ident());
if (!isForRunway(aWay)) {
throw sg_io_exception("adding transition for unspecified runway:" + aWay->ident(), ident());
}
aTrans->mark(flagType());
_runways[aWay] = aTrans;
}
void ArrivalDeparture::setCommon(const WayptVec& aWps)
{
_common = aWps;
markWaypoints(_common, flagType());
}
bool ArrivalDeparture::commonRoute(Transition* t, WayptVec& aPath, FGRunwayRef aRwy)
{
// assume we're routing from enroute, to the runway.
// for departures, we'll flip the result points
WayptVec::iterator firstCommon = _common.begin();
if (t) {
t->route(aPath);
Waypt* transEnd = t->procedureEnd();
for (; firstCommon != _common.end(); ++firstCommon) {
if ((*firstCommon)->matches(transEnd)) {
// found transition->common point, stop search
break;
}
} // of common points
// if we hit this point, the transition doesn't end (start, for a SID) on
// a common point. We assume this means we should just append the entire
// common section after the transition.
firstCommon = _common.begin();
} else {
// no tranasition
} // of not using a transition
// append (some) common points
aPath.insert(aPath.end(), firstCommon, _common.end());
if (!aRwy) {
// no runway specified, we're done
return true;
}
RunwayTransitionMap::iterator r = _runways.find(aRwy);
if (r == _runways.end()) {
// runway doesn't match STAR/SID; this may be intentional (cf. EDDF
// transitions), so we have to cater for it: we will just return at this
// point, and trust the calling code to insert VECTORS or select an
// appropriate approach transition.
return true;
}
if (!r->second) {
// no transitions specified. Not great, but not
// much we can do about it. Calling code will insert VECTORS to the approach
// if required, or maybe there's an approach transition defined.
return true;
}
SG_LOG(SG_NAVAID, SG_INFO, ident() << " using runway transition for " << r->first->ident());
r->second->route(aPath);
return true;
}
Transition* ArrivalDeparture::findTransitionByEnroute(Waypt* aEnroute) const
{
if (!aEnroute) {
return NULL;
}
WptTransitionMap::const_iterator eit;
for (eit = _enrouteTransitions.begin(); eit != _enrouteTransitions.end(); ++eit) {
if (eit->second->enroute()->matches(aEnroute)) {
return eit->second;
}
} // of enroute transition iteration
return NULL;
}
Transition* ArrivalDeparture::findTransitionByEnroute(FGPositioned* aEnroute) const
{
if (!aEnroute) {
return NULL;
}
WptTransitionMap::const_iterator eit;
for (eit = _enrouteTransitions.begin(); eit != _enrouteTransitions.end(); ++eit) {
if (eit->second->enroute()->matches(aEnroute)) {
return eit->second;
}
} // of enroute transition iteration
return NULL;
}
WayptRef ArrivalDeparture::findBestTransition(const SGGeod& aPos) const
{
// no transitions, that's easy
if (_enrouteTransitions.empty()) {
SG_LOG(SG_NAVAID, SG_INFO, "no enroute transitions for " << ident());
return _common.front();
}
double d = 1e9;
WayptRef w;
WptTransitionMap::const_iterator eit;
for (eit = _enrouteTransitions.begin(); eit != _enrouteTransitions.end(); ++eit) {
WayptRef c = eit->second->enroute();
SG_LOG(SG_NAVAID, SG_INFO, "findBestTransition for " << ident() << ", looking at " << c->ident());
// assert(c->hasFixedPosition());
double cd = SGGeodesy::distanceM(aPos, c->position());
if (cd < d) { // distance to 'c' is less, new best match
d = cd;
w = c;
}
} // of transitions iteration
assert(w);
return w;
}
Transition* ArrivalDeparture::findTransitionByName(const string& aIdent) const
{
WptTransitionMap::const_iterator eit;
for (eit = _enrouteTransitions.begin(); eit != _enrouteTransitions.end(); ++eit) {
if (eit->second->ident() == aIdent) {
return eit->second;
}
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////
SID::SID(const string& aIdent, FGAirport* apt) :
ArrivalDeparture(aIdent, apt)
{
}
bool SID::route(FGRunwayRef aWay, Transition* trans, WayptVec& aPath)
{
if (!isForRunway(aWay)) {
SG_LOG(SG_NAVAID, SG_WARN, "SID " << ident() << " not for runway " << aWay->ident());
return false;
}
WayptVec path;
if (!commonRoute(trans, path, aWay)) {
return false;
}
// SID waypoints (including transitions) are stored reversed, so we can
// re-use the routing code. This is where we fix the ordering for client code
std::back_insert_iterator<WayptVec> bi(aPath);
std::reverse_copy(path.begin(), path.end(), bi);
return true;
}
SID* SID::createTempSID(const std::string& aIdent, FGRunway* aRunway, const WayptVec& aPath)
{
// flip waypoints since SID stores them reversed
WayptVec path;
std::back_insert_iterator<WayptVec> bi(path);
std::reverse_copy(aPath.begin(), aPath.end(), bi);
SID* sid = new SID(aIdent, aRunway->airport());
sid->setCommon(path);
sid->addRunway(aRunway);
return sid;
}
////////////////////////////////////////////////////////////////////////////
STAR::STAR(const string& aIdent, FGAirport* apt) :
ArrivalDeparture(aIdent, apt)
{
}
bool STAR::route(FGRunwayRef aWay, Transition* trans, WayptVec& aPath)
{
if (aWay && !isForRunway(aWay)) {
return false;
}
return commonRoute(trans, aPath, aWay);
}
/////////////////////////////////////////////////////////////////////////////
Transition::Transition(const std::string& aIdent, ProcedureType ty, Procedure* aPr) :
Procedure(aIdent),
_type(ty),
_parent(aPr)
{
assert(aPr);
}
void Transition::setPrimary(const WayptVec& aWps)
{
_primary = aWps;
assert(!_primary.empty());
_primary[0]->setFlag(WPT_TRANSITION, true);
}
WayptRef Transition::enroute() const
{
assert(!_primary.empty());
return _primary[0];
}
WayptRef Transition::procedureEnd() const
{
assert(!_primary.empty());
return _primary[_primary.size() - 1];
}
bool Transition::route(WayptVec& aPath)
{
aPath.insert(aPath.end(), _primary.begin(), _primary.end());
return true;
}
FGAirport* Transition::airport() const
{
return _parent->airport();
}
void Transition::mark(WayptFlag f)
{
markWaypoints(_primary, f);
}
} // of namespace

292
src/Navaids/procedure.hxx Normal file
View File

@@ -0,0 +1,292 @@
/// procedure.hxx - define route storing an approach, arrival or departure procedure
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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_NAVAID_PROCEDURE_HXX
#define FG_NAVAID_PROCEDURE_HXX
#include <set>
#include <simgear/math/sg_types.hxx> // for string_list
#include <Airports/airports_fwd.hxx>
#include <Navaids/route.hxx>
namespace flightgear {
// forward decls
class NavdataVisitor;
typedef std::vector<FGRunwayRef> RunwayVec;
typedef enum {
PROCEDURE_INVALID,
PROCEDURE_APPROACH_ILS,
PROCEDURE_APPROACH_VOR,
PROCEDURE_APPROACH_NDB,
PROCEDURE_APPROACH_RNAV,
PROCEDURE_SID,
PROCEDURE_STAR,
PROCEDURE_TRANSITION,
PROCEDURE_RUNWAY_TRANSITION
} ProcedureType;
class Procedure : public RouteBase
{
public:
virtual ProcedureType type() const = 0;
virtual std::string ident() const
{ return _ident; }
virtual FGAirport* airport() const = 0;
virtual RunwayVec runways() const
{ return RunwayVec(); }
protected:
Procedure(const std::string& aIdent);
std::string _ident;
};
/**
* Encapsulate a transition segment
*/
class Transition : public Procedure
{
public:
virtual ~Transition() { ; }
bool route(WayptVec& aPath);
Procedure* parent() const
{ return _parent; }
virtual FGAirport* airport() const;
/**
* Return the enroute end of the transition
*/
WayptRef enroute() const;
/**
* Return the procedure end of the transition
*/
WayptRef procedureEnd() const;
virtual ProcedureType type() const
{ return _type; }
void mark(WayptFlag f);
private:
friend class NavdataVisitor;
Transition(const std::string& aIdent, ProcedureType ty, Procedure* aPr);
void setPrimary(const WayptVec& aWps);
ProcedureType _type;
Procedure* _parent;
WayptVec _primary;
};
typedef SGSharedPtr<Transition> TransitionRef;
/**
* Describe an approach procedure, including the missed approach
* segment
*/
class Approach : public Procedure
{
public:
virtual ~Approach() { ; }
FGRunwayRef runway()
{ return _runway; }
static bool isApproach(ProcedureType ty);
virtual FGAirport* airport() const;
virtual RunwayVec runways() const;
/**
* Build a route from a valid IAF to the runway, including the missed
* segment. Return false if no valid transition from the specified IAF
* could be found
*/
bool route(FGRunwayRef runway, WayptRef aIAF, WayptVec& aWps);
bool routeWithTransition(FGRunwayRef runway, Transition* trans, WayptVec& aWps);
/**
* Build route as above, but ignore transitions, and assume radar
* vectoring to the start of main approach
*/
bool routeFromVectors(WayptVec& aWps);
const WayptVec& primary() const
{ return _primary; }
const WayptVec& missed() const
{ return _missed; }
virtual ProcedureType type() const
{ return _type; }
static Approach* createTempApproach(const std::string& aIdent, FGRunway* aRunway, const WayptVec& aPath);
string_list transitionIdents() const;
Transition* findTransitionByName(const std::string& aIdent) const;
private:
friend class NavdataVisitor;
Approach(const std::string& aIdent, ProcedureType ty);
void setRunway(FGRunwayRef aRwy);
void setPrimaryAndMissed(const WayptVec& aPrimary, const WayptVec& aMissed);
void addTransition(Transition* aTrans);
FGRunwayRef _runway;
ProcedureType _type;
typedef std::map<WayptRef, TransitionRef> WptTransitionMap;
WptTransitionMap _transitions;
WayptVec _primary; // unify these?
WayptVec _missed;
};
class ArrivalDeparture : public Procedure
{
public:
virtual FGAirport* airport() const
{ return _airport; }
/**
* Predicate, test if this procedure applies to the requested runway
*/
virtual bool isForRunway(const FGRunway* aWay) const;
virtual RunwayVec runways() const;
/**
* Find a path between the runway and enroute structure. Waypoints
* corresponding to the appropriate transitions and segments will be created.
*/
virtual bool route(FGRunwayRef aWay, Transition* trans, WayptVec& aPath) = 0;
const WayptVec& common() const
{ return _common; }
string_list transitionIdents() const;
/**
* Given an enroute location, find the best enroute transition point for
* this arrival/departure. Best is currently determined as 'closest to the
* enroute location'.
*/
WayptRef findBestTransition(const SGGeod& aPos) const;
/**
* Find an enroute transition waypoint by identifier. This is necessary
* for the route-manager and similar code that that needs to talk about
* transitions in a human-meaningful way (including persistence).
*/
Transition* findTransitionByName(const std::string& aIdent) const;
Transition* findTransitionByEnroute(FGPositioned* aEnroute) const;
Transition* findTransitionByEnroute(Waypt* aEnroute) const;
protected:
bool commonRoute(Transition* t, WayptVec& aPath, FGRunwayRef aRwy);
ArrivalDeparture(const std::string& aIdent, FGAirport* apt);
void addRunway(FGRunwayRef aRwy);
typedef std::map<FGRunwayRef, TransitionRef> RunwayTransitionMap;
RunwayTransitionMap _runways;
virtual WayptFlag flagType() const = 0;
void setCommon(const WayptVec& aWps);
private:
friend class NavdataVisitor;
void addTransition(Transition* aTrans);
void addRunwayTransition(FGRunwayRef aRwy, Transition* aTrans);
FGAirport* _airport;
WayptVec _common;
typedef std::map<WayptRef, TransitionRef> WptTransitionMap;
WptTransitionMap _enrouteTransitions;
};
class SID : public ArrivalDeparture
{
public:
virtual ~SID() { ; }
virtual bool route(FGRunwayRef aWay, Transition* aTrans, WayptVec& aPath);
virtual ProcedureType type() const
{ return PROCEDURE_SID; }
static SID* createTempSID(const std::string& aIdent, FGRunway* aRunway, const WayptVec& aPath);
protected:
virtual WayptFlag flagType() const
{ return WPT_DEPARTURE; }
private:
friend class NavdataVisitor;
SID(const std::string& aIdent, FGAirport* apt);
};
class STAR : public ArrivalDeparture
{
public:
virtual ~STAR() { ; }
virtual bool route(FGRunwayRef aWay, Transition* aTrans, WayptVec& aPath);
virtual ProcedureType type() const
{ return PROCEDURE_STAR; }
protected:
virtual WayptFlag flagType() const
{ return WPT_ARRIVAL; }
private:
friend class NavdataVisitor;
STAR(const std::string& aIdent, FGAirport* apt);
};
} // of namespace
#endif

612
src/Navaids/route.cxx Normal file
View File

@@ -0,0 +1,612 @@
// route.cxx - classes supporting waypoints and route structures
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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 "route.hxx"
// std
#include <map>
#include <fstream>
// SimGear
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
#include <simgear/magvar/magvar.hxx>
#include <simgear/timing/sg_time.hxx>
#include <simgear/io/iostreams/sgstream.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/props/props_io.hxx>
// FlightGear
#include <Main/globals.hxx>
#include "Main/fg_props.hxx"
#include <Navaids/procedure.hxx>
#include <Navaids/waypoint.hxx>
#include <Navaids/LevelDXML.hxx>
#include <Airports/airport.hxx>
#include <Navaids/airways.hxx>
using std::string;
using std::vector;
using std::endl;
using std::fstream;
namespace flightgear {
const double NO_MAG_VAR = -1000.0; // an impossible mag-var value
bool isMachRestrict(RouteRestriction rr)
{
return (rr == SPEED_RESTRICT_MACH) || (rr == SPEED_COMPUTED_MACH);
}
namespace {
double magvarDegAt(const SGGeod& pos)
{
double jd = globals->get_time_params()->getJD();
return sgGetMagVar(pos, jd) * SG_RADIANS_TO_DEGREES;
}
WayptRef intersectionFromString(FGPositionedRef p1,
const SGGeod& basePosition,
const double magvar,
const string_list& pieces)
{
assert(pieces.size() == 4);
// navid/radial/navid/radial notation
FGPositionedRef p2 = FGPositioned::findClosestWithIdent(pieces[2], basePosition);
if (!p2) {
SG_LOG(SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces[2]);
return {};
}
double r1 = atof(pieces[1].c_str()),
r2 = atof(pieces[3].c_str());
r1 += magvar;
r2 += magvar;
SGGeod intersection;
bool ok = SGGeodesy::radialIntersection(p1->geod(), r1, p2->geod(), r2, intersection);
if (!ok) {
SG_LOG(SG_NAVAID, SG_INFO, "no valid intersection for:" << pieces[0] << "/" << pieces[2]);
return {};
}
std::string name = p1->ident() + "-" + p2->ident();
return new BasicWaypt(intersection, name, nullptr);
}
WayptRef viaFromString(const SGGeod& basePosition, const std::string& target)
{
assert(target.find("VIA-") == 0);
string_list pieces(simgear::strutils::split(target.substr(4), "/"));
if (pieces.size() != 2) {
SG_LOG(SG_NAVAID, SG_WARN, "Malformed VIA specification string:" << target);
return {};
}
// TO navaid is pieces[1]
FGPositionedRef nav = FGPositioned::findClosestWithIdent(pieces[1], basePosition, nullptr);
if (!nav) {
SG_LOG(SG_NAVAID, SG_WARN, "TO navaid:" << pieces[3] << " unknown");
return {};
}
// airway ident is pieces[1]
AirwayRef airway = Airway::findByIdentAndNavaid(pieces[0], nav);
if (!airway) {
SG_LOG(SG_NAVAID, SG_WARN, "Unknown airway:" << pieces[0]);
return {};
}
return new Via(nullptr, airway, nav);
}
} // namespace
Waypt::Waypt(RouteBase* aOwner) :
_owner(aOwner),
_magVarDeg(NO_MAG_VAR)
{
}
Waypt::~Waypt()
{
}
std::string Waypt::ident() const
{
return {};
}
bool Waypt::flag(WayptFlag aFlag) const
{
return ((_flags & aFlag) != 0);
}
void Waypt::setFlag(WayptFlag aFlag, bool aV)
{
if (aFlag == 0) {
throw sg_range_exception("invalid waypoint flag set");
}
_flags = (_flags & ~aFlag);
if (aV) _flags |= aFlag;
}
bool Waypt::matches(Waypt* aOther) const
{
assert(aOther);
if (ident() != aOther->ident()) { // cheap check first
return false;
}
return matches(aOther->position());
}
bool Waypt::matches(FGPositioned* aPos) const
{
if (!aPos)
return false;
// if w ehave no source, match on position and ident
if (!source()) {
return (ident() == aPos->ident()) && matches(aPos->geod());
}
return (aPos == source());
}
bool Waypt::matches(const SGGeod& aPos) const
{
double d = SGGeodesy::distanceM(position(), aPos);
return (d < 100.0); // 100 metres seems plenty
}
void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict)
{
_altitudeFt = aAlt;
_altRestrict = aRestrict;
}
void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict)
{
_speed = aSpeed;
_speedRestrict = aRestrict;
}
double Waypt::speedKts() const
{
assert(_speedRestrict != SPEED_RESTRICT_MACH);
return speed();
}
double Waypt::speedMach() const
{
assert(_speedRestrict == SPEED_RESTRICT_MACH);
return speed();
}
double Waypt::magvarDeg() const
{
if (_magVarDeg == NO_MAG_VAR) {
// derived classes with a default pos must override this method
assert(!(position() == SGGeod()));
double jd = globals->get_time_params()->getJD();
_magVarDeg = sgGetMagVar(position(), jd) * SG_RADIANS_TO_DEGREES;
}
return _magVarDeg;
}
double Waypt::headingRadialDeg() const
{
return 0.0;
}
std::string Waypt::icaoDescription() const
{
return ident();
}
///////////////////////////////////////////////////////////////////////////
// persistence
static RouteRestriction restrictionFromString(const char* aStr)
{
std::string l = simgear::strutils::lowercase(aStr);
if (l == "at") return RESTRICT_AT;
if (l == "above") return RESTRICT_ABOVE;
if (l == "below") return RESTRICT_BELOW;
if (l == "none") return RESTRICT_NONE;
if (l == "mach") return SPEED_RESTRICT_MACH;
if (l.empty()) return RESTRICT_NONE;
throw sg_io_exception("unknown restriction specification:" + l,
"Route restrictFromString");
}
const char* restrictionToString(RouteRestriction aRestrict)
{
switch (aRestrict) {
case RESTRICT_AT: return "at";
case RESTRICT_BELOW: return "below";
case RESTRICT_ABOVE: return "above";
case RESTRICT_NONE: return "none";
case SPEED_RESTRICT_MACH: return "mach";
default:
throw sg_exception("invalid route restriction",
"Route restrictToString");
}
}
WayptRef Waypt::createInstance(RouteBase* aOwner, const std::string& aTypeName)
{
WayptRef r;
if (aTypeName == "basic") {
r = new BasicWaypt(aOwner);
} else if (aTypeName == "navaid") {
r = new NavaidWaypoint(aOwner);
} else if (aTypeName == "offset-navaid") {
r = new OffsetNavaidWaypoint(aOwner);
} else if (aTypeName == "hold") {
r = new Hold(aOwner);
} else if (aTypeName == "runway") {
r = new RunwayWaypt(aOwner);
} else if (aTypeName == "hdgToAlt") {
r = new HeadingToAltitude(aOwner);
} else if (aTypeName == "dmeIntercept") {
r = new DMEIntercept(aOwner);
} else if (aTypeName == "radialIntercept") {
r = new RadialIntercept(aOwner);
} else if (aTypeName == "vectors") {
r = new ATCVectors(aOwner);
} else if (aTypeName == "discontinuity") {
r = new Discontinuity(aOwner);
} else if (aTypeName == "via") {
r = new Via(aOwner);
}
if (!r || (r->type() != aTypeName)) {
throw sg_exception("broken factory method for type:" + aTypeName,
"Waypt::createInstance");
}
return r;
}
WayptRef Waypt::createFromProperties(RouteBase* aOwner, SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("type")) {
SG_LOG(SG_GENERAL, SG_WARN, "Bad waypoint node: missing type");
return {};
}
flightgear::AirwayRef via;
if (aProp->hasChild("airway")) {
auto level = Airway::Both;
if (aProp->hasValue("network")) {
level = static_cast<flightgear::Airway::Level>(aProp->getIntValue("network"));
}
via = flightgear::Airway::findByIdent(aProp->getStringValue("airway"), level);
if (via) {
// override owner if we are from an airway
aOwner = via.get();
}
}
WayptRef nd(createInstance(aOwner, aProp->getStringValue("type")));
if (nd->initFromProperties(aProp)) {
return nd;
}
SG_LOG(SG_GENERAL, SG_WARN, "failed to create waypoint, trying basic");
// if we failed to make the waypoint, try again making a basic waypoint.
// this handles the case where a navaid waypoint is missing, for example
// we also reject navaids that don't look correct (too far form the specified
// lat-lon, eg see https://sourceforge.net/p/flightgear/codetickets/1814/ )
// and again fallback to here.
WayptRef bw(new BasicWaypt(aOwner));
if (bw->initFromProperties(aProp)) {
return bw;
}
return {}; // total failure
}
WayptRef Waypt::fromLatLonString(RouteBase* aOwner, const std::string& target)
{
// permit lat,lon/radial/offset format
string_list pieces(simgear::strutils::split(target, "/"));
if ((pieces.size() != 1) && (pieces.size() != 3)) {
return {};
}
SGGeod g;
const bool defaultToLonLat = true; // parseStringAsGeod would otherwise default to lat,lon
if (!simgear::strutils::parseStringAsGeod(pieces[0], &g, defaultToLonLat)) {
return {};
}
if (pieces.size() == 3) {
// process offset
const double bearing = std::stod(pieces[1]);
const double distanceNm = std::stod(pieces[2]);
g = SGGeodesy::direct(g, bearing, distanceNm * SG_NM_TO_METER);
}
// build a short name
const int lonDeg = static_cast<int>(g.getLongitudeDeg());
const int latDeg = static_cast<int>(g.getLatitudeDeg());
char buf[32];
char ew = (lonDeg < 0) ? 'W' : 'E';
char ns = (latDeg < 0) ? 'S' : 'N';
snprintf(buf, 32, "%c%03d%c%03d", ew, std::abs(lonDeg), ns, std::abs(latDeg));
return new BasicWaypt(g, buf, aOwner);
}
WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const SGGeod& aVicinity)
{
auto vicinity = aVicinity;
if (!vicinity.isValid()) {
vicinity = globals->get_aircraft_position();
}
auto target = simgear::strutils::uppercase(s);
// extract altitude
double altFt = 0.0;
RouteRestriction altSetting = RESTRICT_NONE;
size_t pos = target.find('@');
if (pos != string::npos) {
altFt = std::stof(target.substr(pos + 1));
target = target.substr(0, pos);
if (fgGetString("/sim/startup/units") == "meter") {
altFt *= SG_METER_TO_FEET;
}
altSetting = RESTRICT_AT;
}
// check for lon,lat
WayptRef wpt = fromLatLonString(aOwner, target);
const double magvar = magvarDegAt(vicinity);
if (wpt) {
// already handled in the lat/lon test above
} else if (target.find("VIA-") == 0) {
wpt = viaFromString(vicinity, target);
} else {
FGPositioned::TypeFilter filter({FGPositioned::Type::AIRPORT, FGPositioned::Type::HELIPORT,
FGPositioned::Type::SEAPORT, FGPositioned::Type::NDB, FGPositioned::Type::VOR,
FGPositioned::Type::FIX, FGPositioned::Type::WAYPOINT});
string_list pieces(simgear::strutils::split(target, "/"));
FGPositionedRef p = FGPositioned::findClosestWithIdent(pieces.front(), vicinity, &filter);
if (!p) {
SG_LOG(SG_NAVAID, SG_INFO, "Unable to find FGPositioned with ident:" << pieces.front());
return {};
}
if (pieces.size() == 1) {
wpt = new NavaidWaypoint(p, aOwner);
} else if (pieces.size() == 3) {
// navaid/radial/distance-nm notation
double radial = atof(pieces[1].c_str()),
distanceNm = atof(pieces[2].c_str());
radial += magvar;
wpt = new OffsetNavaidWaypoint(p, aOwner, radial, distanceNm);
} else if (pieces.size() == 2) {
FGAirport* apt = dynamic_cast<FGAirport*>(p.ptr());
if (!apt) {
SG_LOG(SG_NAVAID, SG_INFO, "Waypoint is not an airport:" << pieces.front());
return {};
}
if (!apt->hasRunwayWithIdent(pieces[1])) {
SG_LOG(SG_NAVAID, SG_INFO, "No runway: " << pieces[1] << " at " << pieces[0]);
return {};
}
FGRunway* runway = apt->getRunwayByIdent(pieces[1]);
wpt = new NavaidWaypoint(runway, aOwner);
} else if (pieces.size() == 4) {
wpt = intersectionFromString(p, vicinity, magvar, pieces);
}
}
if (!wpt) {
SG_LOG(SG_NAVAID, SG_INFO, "Unable to parse waypoint:" << target);
return {};
}
if (altSetting != RESTRICT_NONE) {
wpt->setAltitude(altFt, altSetting);
}
return wpt;
}
void Waypt::saveAsNode(SGPropertyNode* n) const
{
n->setStringValue("type", type());
writeToProperties(n);
}
bool Waypt::initFromProperties(SGPropertyNode_ptr aProp)
{
if (aProp->hasChild("generated")) {
setFlag(WPT_GENERATED, aProp->getBoolValue("generated"));
}
if (aProp->hasChild("overflight")) {
setFlag(WPT_OVERFLIGHT, aProp->getBoolValue("overflight"));
}
if (aProp->hasChild("arrival")) {
setFlag(WPT_ARRIVAL, aProp->getBoolValue("arrival"));
}
if (aProp->hasChild("approach")) {
setFlag(WPT_APPROACH, aProp->getBoolValue("approach"));
}
if (aProp->hasChild("departure")) {
setFlag(WPT_DEPARTURE, aProp->getBoolValue("departure"));
}
if (aProp->hasChild("miss")) {
setFlag(WPT_MISS, aProp->getBoolValue("miss"));
}
if (aProp->hasChild("airway")) {
setFlag(WPT_VIA, true);
}
if (aProp->hasChild("alt-restrict")) {
_altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict").c_str());
_altitudeFt = aProp->getDoubleValue("altitude-ft");
}
if (aProp->hasChild("speed-restrict")) {
_speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict").c_str());
_speed = aProp->getDoubleValue("speed");
}
return true;
}
void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
{
if (flag(WPT_OVERFLIGHT)) {
aProp->setBoolValue("overflight", true);
}
if (flag(WPT_DEPARTURE)) {
aProp->setBoolValue("departure", true);
}
if (flag(WPT_ARRIVAL)) {
aProp->setBoolValue("arrival", true);
}
if (flag(WPT_APPROACH)) {
aProp->setBoolValue("approach", true);
}
if (flag(WPT_VIA) && _owner) {
flightgear::AirwayRef awy = (flightgear::Airway*) _owner;
aProp->setStringValue("airway", awy->ident());
aProp->setIntValue("network", awy->level());
}
if (flag(WPT_MISS)) {
aProp->setBoolValue("miss", true);
}
if (flag(WPT_GENERATED)) {
aProp->setBoolValue("generated", true);
}
if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
aProp->setDoubleValue("altitude-ft", _altitudeFt);
}
if (_speedRestrict != RESTRICT_NONE) {
aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
aProp->setDoubleValue("speed", _speed);
}
}
RouteBase::~RouteBase()
{
}
void RouteBase::dumpRouteToKML(const WayptVec& aRoute, const std::string& aName)
{
SGPath p = "/Users/jmt/Desktop/" + aName + ".kml";
sg_ofstream f(p);
if (!f.is_open()) {
SG_LOG(SG_NAVAID, SG_WARN, "unable to open:" << p);
return;
}
// pre-amble
f << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
"<Document>\n";
dumpRouteToKMLLineString(aName, aRoute, f);
// post-amble
f << "</Document>\n"
"</kml>" << endl;
f.close();
}
void RouteBase::dumpRouteToKMLLineString(const std::string& aIdent,
const WayptVec& aRoute, std::ostream& aStream)
{
// preamble
aStream << "<Placemark>\n";
aStream << "<name>" << aIdent << "</name>\n";
aStream << "<LineString>\n";
aStream << "<tessellate>1</tessellate>\n";
aStream << "<coordinates>\n";
// waypoints
for (unsigned int i=0; i<aRoute.size(); ++i) {
SGGeod pos = aRoute[i]->position();
aStream << pos.getLongitudeDeg() << "," << pos.getLatitudeDeg() << " " << endl;
}
// postable
aStream << "</coordinates>\n"
"</LineString>\n"
"</Placemark>\n" << endl;
}
void RouteBase::loadAirportProcedures(const SGPath& aPath, FGAirport* aApt)
{
assert(aApt);
try {
NavdataVisitor visitor(aApt, aPath);
readXML(aPath, visitor);
} catch (sg_io_exception& ex) {
SG_LOG(SG_NAVAID, SG_WARN, "failure parsing procedures: " << aPath <<
"\n\t" << ex.getMessage() << "\n\tat:" << ex.getLocation().asString());
} catch (sg_exception& ex) {
SG_LOG(SG_NAVAID, SG_WARN, "failure parsing procedures: " << aPath <<
"\n\t" << ex.getMessage());
}
}
} // of namespace flightgear

273
src/Navaids/route.hxx Normal file
View File

@@ -0,0 +1,273 @@
/**
* route.hxx - defines basic route and route-element classes. Route elements
* are specialised into waypoints and related things. Routes are any class tha
* owns a collection (list, tree, graph) of route elements - such as airways,
* procedures or a flight plan.
*/
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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_ROUTE_HXX
#define FG_ROUTE_HXX
// std
#include <vector>
#include <map>
#include <iosfwd>
// Simgear
#include <simgear/structure/SGReferenced.hxx>
#include <simgear/structure/SGSharedPtr.hxx>
#include <simgear/props/props.hxx>
// forward decls
class FGPositioned;
class SGPath;
class FGAirport;
namespace flightgear
{
// forward decls
class RouteBase;
class Waypt;
class NavdataVisitor;
class Airway;
using AirwayRef = SGSharedPtr<Airway>;
typedef SGSharedPtr<Waypt> WayptRef;
typedef enum {
WPT_MAP = 1 << 0, ///< missed approach point
WPT_IAF = 1 << 1, ///< initial approach fix
WPT_FAF = 1 << 2, ///< final approach fix
WPT_OVERFLIGHT = 1 << 3, ///< must overfly the point directly
WPT_TRANSITION = 1 << 4, ///< transition to/from enroute structure
WPT_MISS = 1 << 5, ///< segment is part of missed approach
/// waypoint position is dynamic, i.e moves based on other criteria,
/// such as altitude, inbound course, or so on.
WPT_DYNAMIC = 1 << 6,
/// waypoint was created automatically (not manually entered/loaded)
/// for example waypoints from airway routing or a procedure
WPT_GENERATED = 1 << 7,
WPT_DEPARTURE = 1 << 8,
WPT_ARRIVAL = 1 << 9,
/// waypoint generated by VNAV / speed management profile,
/// for step climbs or top of descent
WPT_PSEUDO = 1 << 10,
WPT_APPROACH = 1 << 11,
/// waypoint prodcued by expanding a VIA segment
WPT_VIA = 1 << 12,
/// waypoint should not be shown in UI displays, etc
/// this is used to implement FMSs which delete waypoints after passing them
WPT_HIDDEN = 1 << 13
} WayptFlag;
typedef enum {
RESTRICT_NONE,
RESTRICT_AT,
RESTRICT_ABOVE,
RESTRICT_BELOW,
SPEED_RESTRICT_MACH, ///< encode an 'AT' restriction in Mach, not IAS
RESTRICT_DELETE, ///< ignore underlying restriction (on a leg)
RESTRICT_COMPUTED, ///< data is computed, not a real restriction
SPEED_COMPUTED_MACH ///< variant on above to encode a Mach value
} RouteRestriction;
bool isMachRestrict(RouteRestriction rr);
/**
* Abstract base class for waypoints (and things that are treated similarly
* by navigation systems). More precisely this is route path elements,
* including their terminator.
*/
class Waypt : public SGReferenced
{
public:
virtual ~Waypt();
RouteBase* owner() const
{ return const_cast<RouteBase*>(_owner); }
virtual SGGeod position() const = 0;
/**
* The Positioned associated with this element, if one exists
*/
virtual FGPositioned* source() const
{ return nullptr; }
virtual double altitudeFt() const
{ return _altitudeFt; }
virtual double speed() const
{ return _speed; }
// wrapper - asserts if restriction type is _MACH
double speedKts() const;
// wrapper - asserts if restriction type is not _MACH
double speedMach() const;
virtual RouteRestriction altitudeRestriction() const
{ return _altRestrict; }
virtual RouteRestriction speedRestriction() const
{ return _speedRestrict; }
void setAltitude(double aAlt, RouteRestriction aRestrict);
void setSpeed(double aSpeed, RouteRestriction aRestrict);
/**
* Identifier assoicated with the waypoint. Human-readable, but
* possibly quite terse, and definitiely not unique.
*/
virtual std::string ident() const;
/**
* Test if the specified flag is set for this element
*/
virtual bool flag(WayptFlag aFlag) const;
virtual unsigned int flags() const
{ return _flags; }
void setFlag(WayptFlag aFlag, bool aV = true);
/**
* Factory method
*/
static WayptRef createFromProperties(RouteBase* aOwner, SGPropertyNode_ptr aProp);
/**
Create a waypoint from the route manager's standard string format:
* - simple identifier
* - decimal-lon,decimal-lat
* - airport-id/runway-id
* - navaid/radial-deg/offset-nm
*/
static WayptRef createFromString(RouteBase* aOwner, const std::string& s, const SGGeod& vicinity);
static WayptRef fromLatLonString(RouteBase* aOwner, const std::string& target);
void saveAsNode(SGPropertyNode* node) const;
/**
* Test if this element and another are 'the same', i.e matching
* ident and lat/lon are approximately equal
*/
bool matches(Waypt* aOther) const;
/**
* Test if this element and positioned are the same, i.e matching
* ident and lat/lon are approximately equal
*/
bool matches(FGPositioned* aPos) const;
/**
* Test if this element and a position 'the same'
* this can be defined by either position, ident or both
*/
bool matches(const SGGeod& aPos) const;
virtual std::string type() const = 0;
/**
* Magentic variation at/in the vicinity of the waypoint.
* For some waypoint types this will always return 0.
*/
virtual double magvarDeg() const;
/**
* return the assoicated heading or radial for this waypoint.
* The exact meaning varies by type - for a hold it's the inbound radial,
* for a DME intercept it's the heading to hold, and so on.
*/
virtual double headingRadialDeg() const;
/**
* @brief icaoDescription - description of the waypoint in ICAO route plan format
* @return
*/
virtual std::string icaoDescription() const;
protected:
friend class NavdataVisitor;
Waypt(RouteBase* aOwner);
/**
* Persistence helper - read node properties from a file
*/
virtual bool initFromProperties(SGPropertyNode_ptr aProp);
/**
* Persistence helper - save this element to a node
*/
virtual void writeToProperties(SGPropertyNode_ptr aProp) const;
typedef Waypt*(FactoryFunction)(RouteBase* aOwner);
static void registerFactory(const std::string aNodeType, FactoryFunction* aFactory);
double _altitudeFt = 0.0;
double _speed = 0.0; // knots IAS or mach
RouteRestriction _altRestrict = RESTRICT_NONE;
RouteRestriction _speedRestrict = RESTRICT_NONE;
private:
/**
* Create an instance of a concrete subclass, or throw an exception
*/
static WayptRef createInstance(RouteBase* aOwner, const std::string& aTypeName);
const RouteBase* _owner = nullptr;
unsigned short _flags = 0;
mutable double _magVarDeg = 0.0; ///< cached mag var at this location
};
typedef std::vector<WayptRef> WayptVec;
class RouteBase : public SGReferenced
{
public:
virtual ~RouteBase();
/**
*
*/
virtual std::string ident() const = 0;
static void loadAirportProcedures(const SGPath& aPath, FGAirport* aApt);
static void dumpRouteToKML(const WayptVec& aRoute, const std::string& aName);
static void dumpRouteToKMLLineString(const std::string& aIdent,
const WayptVec& aRoute, std::ostream& aStream);
private:
};
} // of namespace flightgear
#endif // of FG_ROUTE_HXX

1301
src/Navaids/routePath.cxx Normal file

File diff suppressed because it is too large Load Diff

81
src/Navaids/routePath.hxx Normal file
View File

@@ -0,0 +1,81 @@
/**
* routePath.hxx - convert a route to straight line segments, for graphical
* output or display.
*/
// Written by James Turner, started 2010.
//
// Copyright (C) 2010 Curtis L. Olson
//
// 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_ROUTE_PATH_HXX
#define FG_ROUTE_PATH_HXX
#include <memory>
#include <Navaids/route.hxx>
namespace flightgear
{
class Hold;
class FlightPlan;
class Via;
typedef std::vector<SGGeod> SGGeodVec;
}
class RoutePath
{
public:
RoutePath(const flightgear::FlightPlan* fp);
~RoutePath();
RoutePath(const RoutePath& other);
RoutePath& operator=(const RoutePath& other);
flightgear::SGGeodVec pathForIndex(int index) const;
SGGeod positionForIndex(int index) const;
SGGeod positionForDistanceFrom(int index, double distanceM) const;
double trackForIndex(int index) const;
double distanceForIndex(int index) const;
double distanceBetweenIndices(int from, int to) const;
private:
class RoutePathPrivate;
void commonInit();
double computeDistanceForIndex(int index) const;
double distanceForVia(flightgear::Via *via, int index) const;
flightgear::SGGeodVec pathForHold(flightgear::Hold* hold) const;
flightgear::SGGeodVec pathForVia(flightgear::Via* via, int index) const;
SGGeod positionAlongVia(flightgear::Via* via, int previousIndex, double distanceM) const;
void interpolateGreatCircle(const SGGeod& aFrom, const SGGeod& aTo,
flightgear::SGGeodVec& r) const;
std::unique_ptr<RoutePathPrivate> d;
};
#endif

637
src/Navaids/waypoint.cxx Normal file
View File

@@ -0,0 +1,637 @@
// waypoint.cxx - waypoints that can occur in routes/procedures
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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.
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "waypoint.hxx"
#include <simgear/structure/exception.hxx>
#include <simgear/misc/strutils.hxx>
#include <Airports/airport.hxx>
#include <Airports/runways.hxx>
#include <Navaids/airways.hxx>
using std::string;
namespace flightgear
{
BasicWaypt::BasicWaypt(const SGGeod& aPos, const string& aIdent, RouteBase* aOwner) :
Waypt(aOwner),
_pos(aPos),
_ident(aIdent)
{
}
BasicWaypt::BasicWaypt(RouteBase* aOwner) :
Waypt(aOwner)
{
}
bool BasicWaypt::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("lon") || !aProp->hasChild("lat")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "missing lon/lat properties");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
_pos = SGGeod::fromDeg(aProp->getDoubleValue("lon"),
aProp->getDoubleValue("lat"));
_ident = aProp->getStringValue("ident");
return true;
}
void BasicWaypt::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _ident);
aProp->setDoubleValue("lon", _pos.getLongitudeDeg());
aProp->setDoubleValue("lat", _pos.getLatitudeDeg());
}
std::string BasicWaypt::icaoDescription() const
{
return simgear::strutils::formatGeodAsString(_pos, simgear::strutils::LatLonFormat::ICAO_ROUTE_DEGREES);
}
//////////////////////////////////////////////////////////////////////////////
NavaidWaypoint::NavaidWaypoint(FGPositioned* aPos, RouteBase* aOwner) :
Waypt(aOwner),
_navaid(aPos)
{
if (aPos->type() == FGPositioned::RUNWAY) {
SG_LOG(SG_NAVAID, SG_WARN, "sure you don't want to be building a runway waypt here?");
}
}
NavaidWaypoint::NavaidWaypoint(RouteBase* aOwner) :
Waypt(aOwner)
{
}
SGGeod NavaidWaypoint::position() const
{
return SGGeod::fromGeodFt(_navaid->geod(), _altitudeFt);
}
std::string NavaidWaypoint::ident() const
{
return _navaid->ident();
}
bool NavaidWaypoint::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("ident")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "missing navaid ident");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
std::string idn(aProp->getStringValue("ident"));
SGGeod p;
if (aProp->hasChild("lon")) {
p = SGGeod::fromDeg(aProp->getDoubleValue("lon"), aProp->getDoubleValue("lat"));
}
// FIXME - resolve co-located DME, etc
// is it sufficent just to ignore DMEs, actually?
FGPositionedRef nav = FGPositioned::findClosestWithIdent(idn, p, nullptr);
if (!nav) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "unknown navdaid ident:" << idn);
return false;
}
if (p.isValid() && (SGGeodesy::distanceM(nav->geod(), p) > 4000)) {
// the looked up navaid was more than 4000 metres from the lat/lon.
// in this case, throw an exception here so we fall back to using
// a basic waypoint
// see https://sourceforge.net/p/flightgear/codetickets/1814/
SG_LOG(SG_AUTOPILOT, SG_WARN, "Waypoint navaid for ident:" << idn << " is too far from the specified lat/lon");
return false;
}
_navaid = nav;
return true;
}
void NavaidWaypoint::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _navaid->ident());
// write lon/lat to disambiguate
aProp->setDoubleValue("lon", _navaid->geod().getLongitudeDeg());
aProp->setDoubleValue("lat", _navaid->geod().getLatitudeDeg());
}
OffsetNavaidWaypoint::OffsetNavaidWaypoint(FGPositioned* aPos, RouteBase* aOwner,
double aRadial, double aDistNm) :
NavaidWaypoint(aPos, aOwner),
_radial(aRadial),
_distanceNm(aDistNm)
{
init();
}
OffsetNavaidWaypoint::OffsetNavaidWaypoint(RouteBase* aOwner) :
NavaidWaypoint(aOwner)
{
}
void OffsetNavaidWaypoint::init()
{
SGGeod offset;
double az2;
SGGeodesy::direct(_navaid->geod(), _radial, _distanceNm * SG_NM_TO_METER, offset, az2);
_geod = SGGeod::fromGeodFt(offset, _altitudeFt);
}
bool OffsetNavaidWaypoint::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("radial-deg") || !aProp->hasChild("distance-nm")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "missing radial/offset distance creating offset waypoint");
return false;
}
if (!NavaidWaypoint::initFromProperties(aProp))
return false;
_radial = aProp->getDoubleValue("radial-deg");
_distanceNm = aProp->getDoubleValue("distance-nm");
init();
return true;
}
void OffsetNavaidWaypoint::writeToProperties(SGPropertyNode_ptr aProp) const
{
NavaidWaypoint::writeToProperties(aProp);
aProp->setDoubleValue("radial-deg", _radial);
aProp->setDoubleValue("distance-nm", _distanceNm);
}
/////////////////////////////////////////////////////////////////////////////
RunwayWaypt::RunwayWaypt(FGRunway* aPos, RouteBase* aOwner) :
Waypt(aOwner),
_runway(aPos)
{
assert(aPos != nullptr);
}
RunwayWaypt::RunwayWaypt(RouteBase* aOwner) :
Waypt(aOwner)
{
}
SGGeod RunwayWaypt::position() const
{
return _runway->threshold();
}
std::string RunwayWaypt::ident() const
{
return _runway->airport()->ident() + "-" + _runway->ident();
}
FGPositioned* RunwayWaypt::source() const
{
return _runway;
}
double RunwayWaypt::headingRadialDeg() const
{
return _runway->headingDeg();
}
bool RunwayWaypt::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("icao") || !aProp->hasChild("ident")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "missing ICAO/ident on runway waypoint");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
std::string idn(aProp->getStringValue("ident"));
const FGAirport* apt = FGAirport::getByIdent(aProp->getStringValue("icao"));
if (!apt) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Unknown airport:" << aProp->getStringValue("icao"));
return false;
}
const std::string ident = aProp->getStringValue("ident");
if (!apt->hasRunwayWithIdent(ident)) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Unknown runway " << ident << " at " << aProp->getStringValue("icao"));
return false;
}
_runway = apt->getRunwayByIdent(ident);
return true;
}
void RunwayWaypt::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _runway->ident());
aProp->setStringValue("icao", _runway->airport()->ident());
}
/////////////////////////////////////////////////////////////////////////////
Hold::Hold(const SGGeod& aPos, const string& aIdent, RouteBase* aOwner) :
BasicWaypt(aPos, aIdent, aOwner),
_righthanded(true),
_isDistance(false)
{
}
Hold::Hold(RouteBase* aOwner) :
BasicWaypt(aOwner),
_righthanded(true),
_isDistance(false)
{
}
void Hold::setHoldRadial(double aInboundRadial)
{
_bearing = aInboundRadial;
}
void Hold::setHoldDistance(double aDistanceNm)
{
_isDistance = true;
_holdTD = aDistanceNm;
}
void Hold::setHoldTime(double aTimeSec)
{
_isDistance = false;
_holdTD = aTimeSec;
}
void Hold::setRightHanded()
{
_righthanded = true;
}
void Hold::setLeftHanded()
{
_righthanded = false;
}
bool Hold::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!BasicWaypt::initFromProperties(aProp))
return false;
_righthanded = aProp->getBoolValue("right-handed");
_isDistance = aProp->getBoolValue("is-distance");
_bearing = aProp->getDoubleValue("inbound-radial-deg");
_holdTD = aProp->getDoubleValue("td");
return true;
}
void Hold::writeToProperties(SGPropertyNode_ptr aProp) const
{
BasicWaypt::writeToProperties(aProp);
aProp->setBoolValue("right-handed", _righthanded);
aProp->setBoolValue("is-distance", _isDistance);
aProp->setDoubleValue("inbound-radial-deg", _bearing);
aProp->setDoubleValue("td", _holdTD);
}
/////////////////////////////////////////////////////////////////////////////
HeadingToAltitude::HeadingToAltitude(RouteBase* aOwner, const string& aIdent,
double aMagHdg) :
Waypt(aOwner),
_ident(aIdent),
_magHeading(aMagHdg)
{
setFlag(WPT_DYNAMIC);
}
HeadingToAltitude::HeadingToAltitude(RouteBase* aOwner) :
Waypt(aOwner)
{
}
bool HeadingToAltitude::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("heading-deg")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Missing heading property creating HdgToAlt waypoint");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
_magHeading = aProp->getDoubleValue("heading-deg");
_ident = aProp->getStringValue("ident");
return true;
}
void HeadingToAltitude::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _ident);
aProp->setDoubleValue("heading-deg", _magHeading);
}
/////////////////////////////////////////////////////////////////////////////
DMEIntercept::DMEIntercept(RouteBase* aOwner, const string& aIdent, const SGGeod& aPos,
double aCourseDeg, double aDistanceNm) :
Waypt(aOwner),
_ident(aIdent),
_pos(aPos),
_magCourse(aCourseDeg),
_dmeDistanceNm(aDistanceNm)
{
setFlag(WPT_DYNAMIC);
}
DMEIntercept::DMEIntercept(RouteBase* aOwner) :
Waypt(aOwner)
{
}
bool DMEIntercept::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("lon") || !aProp->hasChild("lat")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Missing lat/lom properties creating DMEIntc waypoint");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
_pos = SGGeod::fromDeg(aProp->getDoubleValue("lon"), aProp->getDoubleValue("lat"));
_ident = aProp->getStringValue("ident");
// check it's a real DME?
_magCourse = aProp->getDoubleValue("course-deg");
_dmeDistanceNm = aProp->getDoubleValue("dme-distance-nm");
return true;
}
void DMEIntercept::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _ident);
aProp->setDoubleValue("lon", _pos.getLongitudeDeg());
aProp->setDoubleValue("lat", _pos.getLatitudeDeg());
aProp->setDoubleValue("course-deg", _magCourse);
aProp->setDoubleValue("dme-distance-nm", _dmeDistanceNm);
}
/////////////////////////////////////////////////////////////////////////////
RadialIntercept::RadialIntercept(RouteBase* aOwner, const string& aIdent, const SGGeod& aPos,
double aCourseDeg, double aRadial) :
Waypt(aOwner),
_ident(aIdent),
_pos(aPos),
_magCourse(aCourseDeg),
_radial(aRadial)
{
setFlag(WPT_DYNAMIC);
}
RadialIntercept::RadialIntercept(RouteBase* aOwner) :
Waypt(aOwner)
{
}
bool RadialIntercept::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("lon") || !aProp->hasChild("lat")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Missing lat/lom properties creating RadialIntercept waypoint");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
_pos = SGGeod::fromDeg(aProp->getDoubleValue("lon"), aProp->getDoubleValue("lat"));
_ident = aProp->getStringValue("ident");
// check it's a real VOR?
_magCourse = aProp->getDoubleValue("course-deg");
_radial = aProp->getDoubleValue("radial-deg");
return true;
}
void RadialIntercept::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("ident", _ident);
aProp->setDoubleValue("lon", _pos.getLongitudeDeg());
aProp->setDoubleValue("lat", _pos.getLatitudeDeg());
aProp->setDoubleValue("course-deg", _magCourse);
aProp->setDoubleValue("radial-deg", _radial);
}
/////////////////////////////////////////////////////////////////////////////
ATCVectors::ATCVectors(RouteBase* aOwner, FGAirport* aFacility) :
Waypt(aOwner),
_facility(aFacility)
{
setFlag(WPT_DYNAMIC);
}
ATCVectors::~ATCVectors()
{
}
ATCVectors::ATCVectors(RouteBase* aOwner) :
Waypt(aOwner)
{
}
SGGeod ATCVectors::position() const
{
return _facility->geod();
}
string ATCVectors::ident() const
{
return "VECTORS-" + _facility->ident();
}
bool ATCVectors::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!Waypt::initFromProperties(aProp))
return false;
_facility = FGAirport::getByIdent(aProp->getStringValue("icao"));
return true;
}
void ATCVectors::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
if (_facility) {
aProp->setStringValue("icao", _facility->ident());
}
}
/////////////////////////////////////////////////////////////////////////////
Discontinuity::Discontinuity(RouteBase* aOwner) :
Waypt(aOwner)
{
setFlag(WPT_DYNAMIC);
setFlag(WPT_GENERATED); // prevent drag, delete, etc
}
Discontinuity::~Discontinuity()
{
}
SGGeod Discontinuity::position() const
{
return SGGeod(); // deliberately invalid of course
}
string Discontinuity::ident() const
{
return "DISCONTINUITY";
}
bool Discontinuity::initFromProperties(SGPropertyNode_ptr aProp)
{
return true;
}
void Discontinuity::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
}
/////////////////////////////////////////////////////////////////////////////
SGGeod Via::position() const
{
return _to->geod();
}
string Via::ident() const
{
return "VIA " + _airway->ident() + " TO " + _to->ident();
}
Via::Via(RouteBase *aOwner) :
Waypt(aOwner)
{
}
Via::Via(RouteBase *aOwner, AirwayRef awy, FGPositionedRef to) :
Waypt(aOwner),
_airway(awy),
_to(to)
{
}
Via::~Via()
{
}
bool Via::initFromProperties(SGPropertyNode_ptr aProp)
{
if (!aProp->hasChild("airway") || !aProp->hasChild("to")) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "Missingairway/to properties on Via wp");
return false;
}
if (!Waypt::initFromProperties(aProp))
return false;
const std::string ident = aProp->getStringValue("airway");
const Airway::Level level = static_cast<Airway::Level>(aProp->getIntValue("level", Airway::Both));
_airway = Airway::findByIdent(ident, level);
if (!_airway) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "VIA: unknown airway:" << ident);
return false;
}
std::string idn(aProp->getStringValue("to"));
SGGeod p;
if (aProp->hasChild("lon")) {
p = SGGeod::fromDeg(aProp->getDoubleValue("lon"),
aProp->getDoubleValue("lat"));
}
FGPositionedRef nav = FGPositioned::findClosestWithIdent(idn, p, nullptr);
if (!nav) {
SG_LOG(SG_AUTOPILOT, SG_WARN, "VIA TO navaid: " << idn << " not found");
return false;
}
if (!_airway->containsNavaid(nav)) {
// warn but don't block this
SG_LOG(SG_AUTOPILOT, SG_WARN, "VIA TO navaid: " << idn << " not found on airway " << _airway);
}
_to = nav;
return true;
}
void Via::writeToProperties(SGPropertyNode_ptr aProp) const
{
Waypt::writeToProperties(aProp);
aProp->setStringValue("airway", _airway->ident());
aProp->setIntValue("level", _airway->level());
aProp->setStringValue("to", _to->ident());
// write lon/lat to disambiguate
aProp->setDoubleValue("lon", _to->geod().getLongitudeDeg());
aProp->setDoubleValue("lat", _to->geod().getLatitudeDeg());
}
WayptVec Via::expandToWaypoints(WayptRef aPreceeding) const
{
if (!aPreceeding) {
throw sg_exception("invalid preceeding waypoint");
}
// this waypoint is noly used for the search, it's not part
// of the result, so we don't need to set the owner
WayptRef toWp = new NavaidWaypoint(_to, nullptr);
return _airway->via(aPreceeding, toWp);
}
} // of namespace

376
src/Navaids/waypoint.hxx Normal file
View File

@@ -0,0 +1,376 @@
// waypoint.hxx - waypoints that can occur in routes/procedures
// Written by James Turner, started 2009.
//
// Copyright (C) 2009 Curtis L. Olson
//
// 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_WAYPOINT_HXX
#define FG_WAYPOINT_HXX
#include <Airports/airports_fwd.hxx>
#include <Navaids/route.hxx>
#include <Navaids/positioned.hxx>
#include <Navaids/airways.hxx>
namespace flightgear
{
class BasicWaypt : public Waypt
{
public:
BasicWaypt(const SGGeod& aPos, const std::string& aIdent, RouteBase* aOwner);
BasicWaypt(RouteBase* aOwner);
virtual SGGeod position() const
{ return _pos; }
virtual std::string ident() const
{ return _ident; }
std::string icaoDescription() const override;
protected:
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "basic"; }
SGGeod _pos;
std::string _ident;
};
/**
* Waypoint based upon a navaid. In practice this means any Positioned
* element, excluding runways (see below)
*/
class NavaidWaypoint : public Waypt
{
public:
NavaidWaypoint(FGPositioned* aPos, RouteBase* aOwner);
NavaidWaypoint(RouteBase* aOwner);
virtual SGGeod position() const;
virtual FGPositioned* source() const
{ return _navaid; }
virtual std::string ident() const;
protected:
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "navaid"; }
FGPositionedRef _navaid;
};
class OffsetNavaidWaypoint : public NavaidWaypoint
{
public:
OffsetNavaidWaypoint(FGPositioned* aPos, RouteBase* aOwner, double aRadial, double aDistNm);
OffsetNavaidWaypoint(RouteBase* aOwner);
virtual SGGeod position() const
{ return _geod; }
protected:
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "offset-navaid"; }
private:
void init();
SGGeod _geod;
double _radial; // true, degrees
double _distanceNm;
};
/**
* Waypoint based upon a runway.
* Runways are handled specially in various places, so it's cleaner
* to be able to distuinguish them from other navaid waypoints
*/
class RunwayWaypt : public Waypt
{
public:
RunwayWaypt(FGRunway* aPos, RouteBase* aOwner);
RunwayWaypt(RouteBase* aOwner);
virtual SGGeod position() const;
virtual FGPositioned* source() const;
virtual std::string ident() const;
FGRunway* runway() const
{ return _runway; }
virtual double headingRadialDeg() const;
protected:
virtual std::string type() const
{ return "runway"; }
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
private:
FGRunway* _runway;
};
class Hold : public BasicWaypt
{
public:
Hold(const SGGeod& aPos, const std::string& aIdent, RouteBase* aOwner);
Hold(RouteBase* aOwner);
void setHoldRadial(double aInboundRadial);
void setHoldDistance(double aDistanceNm);
void setHoldTime(double aTimeSec);
void setRightHanded();
void setLeftHanded();
double inboundRadial() const
{ return _bearing; }
bool isLeftHanded() const
{ return !_righthanded; }
bool isDistance() const
{ return _isDistance; }
double timeOrDistance() const
{ return _holdTD;}
virtual double headingRadialDeg() const
{ return inboundRadial(); }
protected:
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "hold"; }
private:
double _bearing;
bool _righthanded;
bool _isDistance;
double _holdTD;
};
class HeadingToAltitude : public Waypt
{
public:
HeadingToAltitude(RouteBase* aOwner, const std::string& aIdent, double aMagHdg);
HeadingToAltitude(RouteBase* aOwner);
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "hdgToAlt"; }
virtual SGGeod position() const
{ return SGGeod(); }
virtual std::string ident() const
{ return _ident; }
double headingDegMagnetic() const
{ return _magHeading; }
virtual double magvarDeg() const
{ return 0.0; }
virtual double headingRadialDeg() const
{ return headingDegMagnetic(); }
private:
std::string _ident;
double _magHeading;
};
class DMEIntercept : public Waypt
{
public:
DMEIntercept(RouteBase* aOwner, const std::string& aIdent, const SGGeod& aPos,
double aCourseDeg, double aDistanceNm);
DMEIntercept(RouteBase* aOwner);
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "dmeIntercept"; }
virtual SGGeod position() const
{ return _pos; }
virtual std::string ident() const
{ return _ident; }
double courseDegMagnetic() const
{ return _magCourse; }
double dmeDistanceNm() const
{ return _dmeDistanceNm; }
virtual double headingRadialDeg() const
{ return courseDegMagnetic(); }
private:
std::string _ident;
SGGeod _pos;
double _magCourse;
double _dmeDistanceNm;
};
class RadialIntercept : public Waypt
{
public:
RadialIntercept(RouteBase* aOwner, const std::string& aIdent, const SGGeod& aPos,
double aCourseDeg, double aRadialDeg);
RadialIntercept(RouteBase* aOwner);
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "radialIntercept"; }
virtual SGGeod position() const
{ return _pos; }
virtual std::string ident() const
{ return _ident; }
double courseDegMagnetic() const
{ return _magCourse; }
double radialDegMagnetic() const
{ return _radial; }
virtual double headingRadialDeg() const
{ return courseDegMagnetic(); }
private:
std::string _ident;
SGGeod _pos;
double _magCourse;
double _radial;
};
/**
* Represent ATC radar vectored segment. Common at the end of published
* missed approach procedures, and from STAR arrival points to final approach
*/
class ATCVectors : public Waypt
{
public:
ATCVectors(RouteBase* aOwner, FGAirport* aFacility);
virtual ~ATCVectors();
ATCVectors(RouteBase* aOwner);
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "vectors"; }
virtual SGGeod position() const;
virtual std::string ident() const;
private:
/**
* ATC facility. Using an airport here is incorrect, since often arrivals
* facilities will be shared between several nearby airports, but it
* suffices until we have a proper facility representation
*/
FGAirportRef _facility;
};
/**
* Represent a route discontinuity. These can occur while editing
* plans via certain interfaces (such as CDUs)
*/
class Discontinuity : public Waypt
{
public:
virtual ~Discontinuity();
Discontinuity(RouteBase* aOwner);
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
virtual std::string type() const
{ return "discontinuity"; }
virtual SGGeod position() const;
virtual std::string ident() const;
virtual double magvarDeg() const
{ return 0.0; }
private:
};
class Via : public Waypt
{
public:
Via(RouteBase* aOwner);
Via(RouteBase* aOwner, AirwayRef airway, FGPositionedRef to);
virtual ~Via();
bool initFromProperties(SGPropertyNode_ptr aProp) override;
void writeToProperties(SGPropertyNode_ptr aProp) const override;
std::string type() const override
{ return "via"; }
SGGeod position() const override;
std::string ident() const override;
AirwayRef airway() const
{ return _airway; }
FGPositioned* source() const override
{ return _to.ptr(); }
WayptVec expandToWaypoints(WayptRef aPreceeding) const;
private:
AirwayRef _airway;
FGPositionedRef _to;
};
} // of namespace flighgear
#endif // of FG_WAYPOINT_HXX