first commit
This commit is contained in:
46
src/Navaids/CMakeLists.txt
Normal file
46
src/Navaids/CMakeLists.txt
Normal 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
38
src/Navaids/CacheSchema.h
Normal 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
2352
src/Navaids/FlightPlan.cxx
Normal file
File diff suppressed because it is too large
Load Diff
517
src/Navaids/FlightPlan.hxx
Normal file
517
src/Navaids/FlightPlan.hxx
Normal 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
402
src/Navaids/LevelDXML.cxx
Normal 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
68
src/Navaids/LevelDXML.hxx
Normal 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
2915
src/Navaids/NavDataCache.cxx
Executable file
File diff suppressed because it is too large
Load Diff
372
src/Navaids/NavDataCache.hxx
Normal file
372
src/Navaids/NavDataCache.hxx
Normal 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
176
src/Navaids/PolyLine.cxx
Normal 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
127
src/Navaids/PolyLine.hxx
Normal 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
|
||||
405
src/Navaids/PositionedOctree.cxx
Normal file
405
src/Navaids/PositionedOctree.cxx
Normal 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
|
||||
256
src/Navaids/PositionedOctree.hxx
Normal file
256
src/Navaids/PositionedOctree.hxx
Normal 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
196
src/Navaids/SHPParser.cxx
Normal 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
46
src/Navaids/SHPParser.hxx
Normal 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
706
src/Navaids/airways.cxx
Normal 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
191
src/Navaids/airways.hxx
Normal 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
467
src/Navaids/awynet.cxx
Normal 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
201
src/Navaids/awynet.hxx
Normal 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
47
src/Navaids/fix.hxx
Normal 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
175
src/Navaids/fixlist.cxx
Normal 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
59
src/Navaids/fixlist.hxx
Normal 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
|
||||
45
src/Navaids/markerbeacon.cxx
Normal file
45
src/Navaids/markerbeacon.cxx
Normal 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);
|
||||
}
|
||||
44
src/Navaids/markerbeacon.hxx
Normal file
44
src/Navaids/markerbeacon.hxx
Normal 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
|
||||
30
src/Navaids/navaids_fwd.hxx
Normal file
30
src/Navaids/navaids_fwd.hxx
Normal 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
538
src/Navaids/navdb.cxx
Normal 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
66
src/Navaids/navdb.hxx
Normal 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
350
src/Navaids/navlist.cxx
Normal 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
156
src/Navaids/navlist.hxx
Normal 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
267
src/Navaids/navrecord.cxx
Normal 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
173
src/Navaids/navrecord.hxx
Normal 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
118
src/Navaids/poidb.cxx
Normal 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
40
src/Navaids/poidb.hxx
Normal 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
487
src/Navaids/positioned.cxx
Normal 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
331
src/Navaids/positioned.hxx
Normal 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
484
src/Navaids/procedure.cxx
Normal 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
292
src/Navaids/procedure.hxx
Normal 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
612
src/Navaids/route.cxx
Normal 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
273
src/Navaids/route.hxx
Normal 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
1301
src/Navaids/routePath.cxx
Normal file
File diff suppressed because it is too large
Load Diff
81
src/Navaids/routePath.hxx
Normal file
81
src/Navaids/routePath.hxx
Normal 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
637
src/Navaids/waypoint.cxx
Normal 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
376
src/Navaids/waypoint.hxx
Normal 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
|
||||
Reference in New Issue
Block a user