1
0
Fork 0

Merge branch 'next' of ssh://git.code.sf.net/p/flightgear/flightgear into next

This commit is contained in:
portree_kid 2022-12-03 21:48:42 +01:00
commit 097603452e
31 changed files with 842 additions and 262 deletions

View file

@ -0,0 +1,77 @@
# Strucutral Overview
## Foundation Components
There is a group os basic objects used widely throughout the codebase, which
are useful building blocks in all major simulation elements.
### Properties
Properties form a hierarchy of named, typed values. The hierarchy can be extended
and modified at runtime by aircraft, simulation components and external protocols.
### Listeners
Listeners are a callback interface `SGPropertyChangeListener` which can implemented
to receive notification when a property is modified, or its children added/removed.
Most commonly listeners are used to look for a user input or simulation event
changing some value (for example, a switch being turned from on to off), but it's
possible to monitor an entire sub-tree of properties.
It's important than listeners do not block or perform excessive amounts of work, since
the code modifying the property may not expect this. If non-trivial work needs to be
performed in response to a property changing, the listener should set a dirty flag
and a subsystem (etc) should check this in its `update()` method.
### Tied properties
Tied properties allow a property to be defined by a C++ memory location (such as a class
member variable) or by callback functions implementation the get/set operations. This
is very convenient for the implementating code. However, by default it has the
problematic side-effect that listeners do not correctly, since they have no way to be
notified thst the underlying value (or result of invoking the getter) has changed.
It is therefore critical, when using tied properties, to _manually_ invoke `fireValueChanged`
on dependent proeprties, when updating the internal state producing them. Care should be
taken to only fire the change, when the result of the getters would actually change; it is
_not_ acceptable to simply call `fireValueChanged` each iteration, since any connected
listener will be run at the simulation rate.
### Conditions
_Conditions_ are predicates which can be evaluated on demand. They can contain
logical operations such as 'and' and 'or', and comparisoms: for example checking that
a property is equal to a constant value, or that a property is less than another
property.
Conditions are widely used to drive other state from properties; for example
enabling or disabling user-interface in a dialog, or activating an autopilot
element.
### Bindings
_Bindings_ are a command invocation with associated arguments. In the simplest case
this might be invoking the `property-set` command with an argument of 42. At its
most complex, the command might invoke a Nasal script, supplied as part of the
binding, and substituing a runtime value into the Nasal logic.
Bindings are typically used in response to user actions, such as a menu item, key
press or interacting with a cockpit control, to update some part of the simulator
state.
## Structural Components
Most code within FlightGear is organized into subsystems. Subsystems are isolated from each other;
a well designed subsytem can be added, remove or restarting independently. Many subystems do not
full conform to these goals, especially regarding runtime adding and removal, but an increasing
number do support it.
=== Subsystems ===
=== Commands ===
## Finding data files
## Subsysterm Lifecycle

View file

@ -19,9 +19,7 @@
// 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 <config.h>
#include <simgear/math/sg_random.hxx>
#include <simgear/math/sg_geodesy.hxx>
@ -33,6 +31,7 @@
#include <Main/util.hxx>
#include <Environment/gravity.hxx>
#include <Environment/atmosphere.hxx>
#include <Main/fg_props.hxx>
using namespace simgear;
@ -695,6 +694,10 @@ void FGAIBallistic::Run(double dt) {
// for a conventional shell/bullet (no boat-tail).
double Cdm;
const double Mach = FGAtmo::machFromKnotsAtAltitudeFt(speed, altitude_ft);
const double rhoKgM3 = FGAtmo::densityAtAltitudeFt(altitude_ft);
const double rho = rhoKgM3 / SG_SLUGFT3_TO_KGPM3;
if (Mach < 0.7)
Cdm = 0.0125 * Mach + _cd;
else if (Mach < 1.2)

View file

@ -193,12 +193,6 @@ FGAIBase::FGAIBase(object_type ot, bool enableHot) : replay_time(fgGetNode("sim/
serviceable = false;
rho = 1;
T = 280;
p = 1e5;
a = 340;
Mach = 0;
// explicitly disable HOT for (most) AI models
if (!enableHot)
aip.getSceneGraph()->setNodeMask(~SG_NODEMASK_TERRAIN_BIT);
@ -309,9 +303,6 @@ void FGAIBase::update(double dt) {
if (_otype == object_type::otStatic)
return;
if (_otype == object_type::otBallistic)
CalculateMach();
ft_per_deg_lat = 366468.96 - 3717.12 * cos(pos.getLatitudeRad());
ft_per_deg_lon = 365228.16 * cos(pos.getLatitudeRad());
@ -1193,38 +1184,6 @@ int FGAIBase::_getFallbackModelIndex() const {
return _fallback_model_index;
}
void FGAIBase::CalculateMach() {
// Calculate rho at altitude, using standard atmosphere
// For the temperature T and the pressure p,
double altitude = altitude_ft;
if (altitude < 36152) { // curve fits for the troposphere
T = 59 - 0.00356 * altitude;
p = 2116 * pow( ((T + 459.7) / 518.6) , 5.256);
} else if ( 36152 < altitude && altitude < 82345 ) { // lower stratosphere
T = -70;
p = 473.1 * pow( e , 1.73 - (0.000048 * altitude) );
} else { // upper stratosphere
T = -205.05 + (0.00164 * altitude);
p = 51.97 * pow( ((T + 459.7) / 389.98) , -11.388);
}
rho = p / (1718 * (T + 459.7));
// calculate the speed of sound at altitude
// a = sqrt ( g * R * (T + 459.7))
// where:
// a = speed of sound [ft/s]
// g = specific heat ratio, which is usually equal to 1.4
// R = specific gas constant, which equals 1716 ft-lb/slug/R
a = sqrt ( 1.4 * 1716 * (T + 459.7));
// calculate Mach number
Mach = speed/a;
// cout << "Speed(ft/s) "<< speed <<" Altitude(ft) "<< altitude << " Mach " << Mach << endl;
}
int FGAIBase::_newAIModelID() {
static int id = 0;

View file

@ -292,7 +292,6 @@ protected:
ModelSearchOrder _searchOrder = ModelSearchOrder::DATA_ONLY;
void Transform();
void CalculateMach();
double UpdateRadar(FGAIManager* manager);
void removeModel();
@ -377,13 +376,6 @@ public:
const char* _getSubmodel() const;
int _getFallbackModelIndex() const;
// These are used in the Mach number calculations
double rho;
double T; // temperature, degs farenheit
double p; // pressure lbs/sq ft
double a; // speed of sound at altitude (ft/s)
double Mach; // Mach number
static const double e;
static const double lbs_to_slugs;

View file

@ -123,4 +123,5 @@ void FGReplay::resetStatisticsProperties()
// Register the subsystem.
SGSubsystemMgr::Registrant<FGReplay> registrantFGReplay;
SGSubsystemMgr::Registrant<FGReplay> registrantFGReplay(
SGSubsystemMgr::POST_FDM);

View file

@ -1,26 +1,8 @@
// route_mgr.cxx - manage a route (i.e. a collection of waypoints)
//
// Written by Curtis Olson, started January 2004.
// Norman Vine
// Melchior FRANZ
//
// 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$
/*
* SPDX-FileCopyrightText: (C) 2004 Curtis L. Olson http://www.flightgear.org/~curt
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config.h>
@ -372,7 +354,8 @@ void FGRouteMgr::init() {
_edited = fgGetNode(RM "signals/edited", true);
_flightplanChanged = fgGetNode(RM "signals/flightplan-changed", true);
_isRoute = fgGetNode(RM "is-route", true);
_currentWpt = fgGetNode(RM "current-wp", true);
_currentWpt->setAttribute(SGPropertyNode::LISTENER_SAFE, true);
_currentWpt->tie(SGRawValueMethods<FGRouteMgr, int>
@ -411,6 +394,8 @@ void FGRouteMgr::postinit()
loadRoute(path);
}
_isRoute->setBoolValue(_plan->isRoute());
// this code only matters for the --wp option now - perhaps the option
// should be deprecated in favour of an explicit flight-plan file?
// then the global initial waypoint list could die.
@ -486,7 +471,7 @@ void FGRouteMgr::setFlightPlan(const FlightPlanRef& plan)
_plan = plan;
_plan->addDelegate(this);
_isRoute->setBoolValue(_plan->isRoute());
_flightplanChanged->fireValueChanged();
// fire all the callbacks!

View file

@ -1,24 +1,8 @@
// route_mgr.hxx - manage a route (i.e. a collection of waypoints)
//
// Written by Curtis Olson, started January 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$
/*
* SPDX-FileCopyrightText: (C) 2004 Curtis L. Olson http://www.flightgear.org/~curt
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
@ -144,7 +128,7 @@ private:
SGPropertyNode_ptr _pathNode;
SGPropertyNode_ptr _currentWpt;
SGPropertyNode_ptr _isRoute;
/**
* Signal property to notify people that the route was edited

View file

@ -151,6 +151,47 @@ double FGAtmo::a_vs_p(const double press, const double qnh) {
return T0 * ( pow(qnh/P0,nn) - pow(press/P0,nn) ) / lam0;
}
double FGAtmo::ISATemperatureKAtAltitudeFt(const double alt, const double Tsl)
{
double pressure, temp;
std::tie(pressure, temp) = PT_vs_hpt(alt * SG_FEET_TO_METER, atmodel::ISA::P0, Tsl);
return temp;
}
double FGAtmo::CSMetersPerSecondAtAltitudeFt(const double alt, const double Tsl)
{
const double oatK = ISATemperatureKAtAltitudeFt(alt, Tsl);
// calculate the speed of sound at altitude
const double g = 1.4; // specific heat ratio of air
const double R = 287.053; // specific gas constant (J/kgK)
return sqrt (g * R * oatK);
}
double FGAtmo::densityAtAltitudeFt(const double alt, const double Tsl)
{
double pressure, temp;
std::tie(pressure, temp) = PT_vs_hpt(alt * SG_FEET_TO_METER, atmodel::ISA::P0, Tsl);
const double R = 287.053; // specific gas constant (J/kgK)
return pressure / (temp * R);
}
double FGAtmo::machFromKnotsAtAltitudeFt(const double knots,
const double altFt,
const double Tsl)
{
const auto cs = CSMetersPerSecondAtAltitudeFt(altFt, Tsl);
return (knots * SG_KT_TO_MPS) / cs;
}
double FGAtmo::knotsFromMachAtAltitudeFt(const double mach,
const double altFt,
const double Tsl)
{
const auto cs = CSMetersPerSecondAtAltitudeFt(altFt, Tsl);
return mach * cs * SG_MPS_TO_KT;
}
// force retabulation
void FGAtmoCache::tabulate() {
using namespace atmodel;

View file

@ -117,6 +117,30 @@ public:
* or millibars
*/
static double fieldPressure(const double field_elev, const double qnh);
/**
Compute the outisde temperature at an altitude, according to the standard atmosphere
model. Optionally allow offseting the temperature at seal level, but default to the ISA standard
for that value as well
*/
static double ISATemperatureKAtAltitudeFt(const double alt,
const double Tsl = atmodel::ISA::T0);
/**
Compute the speed of sound at an altitude
*/
static double CSMetersPerSecondAtAltitudeFt(const double alt,
const double Tsl = atmodel::ISA::T0);
static double densityAtAltitudeFt(const double alt, const double Tsl = atmodel::ISA::T0);
static double machFromKnotsAtAltitudeFt(const double knots,
const double altFt,
const double Tsl = atmodel::ISA::T0);
static double knotsFromMachAtAltitudeFt(const double mach,
const double altFt,
const double Tsl = atmodel::ISA::T0);
};

View file

@ -1246,6 +1246,7 @@ void FGJSBsim::init_gear(void )
{
FGGroundReactions* gr=fdmex->GetGroundReactions();
int Ngear=GroundReactions->GetNumGearUnits();
double max = 1.0;
for (int i=0;i<Ngear;i++) {
FGLGear *gear = gr->GetGearUnit(i);
SGPropertyNode * node = fgGetNode("gear/gear", i, true);
@ -1254,16 +1255,23 @@ void FGJSBsim::init_gear(void )
node->setDoubleValue("yoffset-in", gear->GetBodyLocation()(2) * 12);
node->setDoubleValue("zoffset-in", gear->GetBodyLocation()(3) * 12);
node->setDoubleValue("xoffset-m", gear->GetBodyLocation()(1) * 0.08333333);
node->setDoubleValue("yoffset-m", gear->GetBodyLocation()(2) * 0.08333333);
node->setDoubleValue("zoffset-m", gear->GetBodyLocation()(3) * 0.08333333);
node->setDoubleValue("xoffset-m", gear->GetBodyLocation()(1) * SG_FEET_TO_METER);
node->setDoubleValue("yoffset-m", gear->GetBodyLocation()(2) * SG_FEET_TO_METER);
node->setDoubleValue("zoffset-m", gear->GetBodyLocation()(3) * SG_FEET_TO_METER);
node->setBoolValue("wow", gear->GetWOW());
node->setDoubleValue("rollspeed-ms", gear->GetWheelRollVel()*0.3043);
node->setDoubleValue("rollspeed-ms", gear->GetWheelRollVel() * SG_FEET_TO_METER);
node->setBoolValue("has-brake", gear->GetBrakeGroup() > 0);
node->setDoubleValue("position-norm", gear->GetGearUnitPos());
// node->setDoubleValue("tire-pressure-norm", gear->GetTirePressure());
node->setDoubleValue("compression-norm", gear->GetCompLen());
max = node->getDoubleValue("max-compression-ft");
if (max < 0.00001) {
max = node->getDoubleValue("max-compression-m");
if (max < 0.00001) max = 1.0;
else max /= SG_FEET_TO_METER;
}
node->setDoubleValue("compression-norm", gear->GetCompLen() / max);
node->setDoubleValue("compression-m", gear->GetCompLen() * SG_FEET_TO_METER);
node->setDoubleValue("compression-ft", gear->GetCompLen());
if ( gear->GetSteerable() )
node->setDoubleValue("steering-norm", gear->GetSteerNorm());
@ -1274,14 +1282,22 @@ void FGJSBsim::update_gear(void)
{
FGGroundReactions* gr=fdmex->GetGroundReactions();
int Ngear=GroundReactions->GetNumGearUnits();
double max = 1.0;
for (int i=0;i<Ngear;i++) {
FGLGear *gear = gr->GetGearUnit(i);
SGPropertyNode * node = fgGetNode("gear/gear", i, true);
node->getChild("wow", 0, true)->setBoolValue( gear->GetWOW());
node->getChild("rollspeed-ms", 0, true)->setDoubleValue(gear->GetWheelRollVel()*0.3043);
node->getChild("rollspeed-ms", 0, true)->setDoubleValue(gear->GetWheelRollVel() * SG_FEET_TO_METER);
node->getChild("position-norm", 0, true)->setDoubleValue(gear->GetGearUnitPos());
// gear->SetTirePressure(node->getDoubleValue("tire-pressure-norm"));
node->setDoubleValue("compression-norm", gear->GetCompLen());
max = node->getDoubleValue("max-compression-ft");
if (max < 0.00001) {
max = node->getDoubleValue("max-compression-m");
if (max < 0.00001) max = 1.0;
else max /= SG_FEET_TO_METER;
}
node->setDoubleValue("compression-norm", gear->GetCompLen() / max);
node->setDoubleValue("compression-m", gear->GetCompLen() * SG_FEET_TO_METER);
node->setDoubleValue("compression-ft", gear->GetCompLen());
if ( gear->GetSteerable() )
node->setDoubleValue("steering-norm", gear->GetSteerNorm());

View file

@ -649,7 +649,8 @@ void HUDText::draw()
// Register the subsystem.
SGSubsystemMgr::Registrant<HUD> registrantHUD;
SGSubsystemMgr::Registrant<HUD> registrantHUD(
SGSubsystemMgr::DISPLAY);
void TextList::align(const char *s, int align, float *x, float *y,

View file

@ -1051,7 +1051,7 @@ void fgCreateSubsystems(bool duringReset) {
// autopilot.)
mgr->add<FGSystemMgr>();
mgr->add<FGInstrumentMgr>();
mgr->add("xml-autopilot", FGXMLAutopilotGroup::createInstance("autopilot"));
mgr->add("xml-autopilot", FGXMLAutopilotGroup::createInstance("autopilot"), SGSubsystemMgr::FDM);
}
// SGSubsystemMgr::POST_FDM
@ -1106,7 +1106,7 @@ void fgCreateSubsystems(bool duringReset) {
mgr->add<CanvasMgr>();
auto canvasGui = new GUIMgr;
mgr->add("CanvasGUI", canvasGui);
mgr->add("CanvasGUI", canvasGui, SGSubsystemMgr::DISPLAY);
auto guiCamera = flightgear::getGUICamera(flightgear::CameraGroup::getDefault());
canvasGui->setGUIViewAndCamera(globals->get_renderer()->getView(), guiCamera);
@ -1117,7 +1117,7 @@ void fgCreateSubsystems(bool duringReset) {
// ordering here is important : Nasal (via events), then models, then views
if (!duringReset) {
mgr->add<FGLight>();
mgr->add("events", globals->get_event_mgr());
mgr->add("events", globals->get_event_mgr(), SGSubsystemMgr::DISPLAY);
}
mgr->add<FGAircraftModel>();
@ -1435,20 +1435,22 @@ void fgStartNewReset()
sgUserDataInit( globals->get_props() );
unsigned int numDBPagerThreads = fgGetNode("/sim/rendering/database-pager/threads", true)->getIntValue(2);
if (composite_viewer) {
composite_viewer_view->getDatabasePager()->setUpThreads(2, 1);
composite_viewer_view->setDatabasePager(FGScenery::getPagerSingleton());
composite_viewer_view->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false);
composite_viewer_view->getDatabasePager()->setUpThreads(numDBPagerThreads, 1);
composite_viewer_view->getDatabasePager()->setAcceptNewDatabaseRequests(true);
flightgear::CameraGroup::buildDefaultGroup(composite_viewer_view);
composite_viewer_view->setFrameStamp(composite_viewer->getFrameStamp());
composite_viewer_view->setDatabasePager(FGScenery::getPagerSingleton());
composite_viewer_view->getDatabasePager()->setUnrefImageDataAfterApplyPolicy(true, false);
osg::GraphicsContext::createNewContextID();
render->setView(composite_viewer_view);
render->preinit();
composite_viewer->startThreading();
}
else {
viewer->getDatabasePager()->setUpThreads(2, 1);
viewer->getDatabasePager()->setUpThreads(numDBPagerThreads, 1);
viewer->getDatabasePager()->setAcceptNewDatabaseRequests(true);
// must do this before preinit for Rembrandthe
flightgear::CameraGroup::buildDefaultGroup(viewer.get());

View file

@ -1301,16 +1301,7 @@ bool FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
continue;
}
LegRef l = new Leg{this, wp};
// sync leg restrictions with waypoint ones
if (wp->speedRestriction() != RESTRICT_NONE) {
l->setSpeed(wp->speedRestriction(), wp->speed());
}
if (wp->altitudeRestriction() != RESTRICT_NONE) {
l->setAltitude(wp->altitudeRestriction(), wp->altitudeFt());
}
LegRef l = new Leg{this, wp};
if (wpNode->hasChild("hold-count")) {
l->setHoldCount(wpNode->getIntValue("hold-count"));
}
@ -1383,7 +1374,7 @@ WayptRef FlightPlan::parseVersion1XMLWaypt(SGPropertyNode* aWP)
double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
if (altFt > -9990.0) {
w->setAltitude(altFt, RESTRICT_AT);
w->setAltitude(altFt, RESTRICT_AT, ALTITUDE_FEET);
}
return w;
@ -1564,9 +1555,12 @@ FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
// clone local data
c->_speed = _speed;
c->_speedRestrict = _speedRestrict;
c->_altitudeFt = _altitudeFt;
c->_speedUnits = _speedUnits;
c->_altitude = _altitude;
c->_altRestrict = _altRestrict;
c->_altitudeUnits = _altitudeUnits;
c->_holdCount = c->_holdCount;
return c;
}
@ -1583,36 +1577,37 @@ unsigned int FlightPlan::Leg::index() const
return _parent->findLegIndex(this);
}
int FlightPlan::Leg::altitudeFt() const
double FlightPlan::Leg::altitude(RouteUnits aUnits) const
{
if (_altRestrict != RESTRICT_NONE) {
return _altitudeFt;
return convertAltitudeUnits(_altitudeUnits, aUnits, _altitude);
}
return _waypt->altitudeFt();
return _waypt->altitude(aUnits);
}
int FlightPlan::Leg::speed() const
int FlightPlan::Leg::altitudeFt() const
{
return static_cast<int>(altitude(ALTITUDE_FEET));
}
double FlightPlan::Leg::speed(RouteUnits units) const
{
if (_speedRestrict != RESTRICT_NONE) {
return _speed;
return convertSpeedUnits(_speedUnits, units, altitudeFt(), _speed);
}
return _waypt->speed();
return _waypt->speed(units);
}
int FlightPlan::Leg::speedKts() const
{
return speed();
return static_cast<int>(speed(SPEED_KNOTS));
}
double FlightPlan::Leg::speedMach() const
{
if (!isMachRestrict(_speedRestrict)) {
return 0.0;
}
return -(_speed / 100.0);
return speed(SPEED_MACH);
}
RouteRestriction FlightPlan::Leg::altitudeRestriction() const
@ -1632,21 +1627,30 @@ RouteRestriction FlightPlan::Leg::speedRestriction() const
return _waypt->speedRestriction();
}
void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed)
void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed, RouteUnits aUnit)
{
_speedRestrict = ty;
if (isMachRestrict(ty)) {
_speed = (speed * -100);
} else {
_speed = speed;
if (aUnit == DEFAULT_UNITS) {
if (isMachRestrict(ty)) {
aUnit = SPEED_MACH;
} else {
// TODO: check for system in metric?
aUnit = SPEED_KNOTS;
}
}
_speedUnits = aUnit;
_speed = speed;
}
void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt)
void FlightPlan::Leg::setAltitude(RouteRestriction ty, double alt, RouteUnits aUnit)
{
_altRestrict = ty;
_altitudeFt = altFt;
if (aUnit == DEFAULT_UNITS) {
aUnit = ALTITUDE_FEET;
}
_altitudeUnits = aUnit;
_altitude = alt;
}
double FlightPlan::Leg::courseDeg() const
@ -1722,16 +1726,24 @@ void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const
{
if (_speedRestrict != RESTRICT_NONE) {
aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
if (_speedRestrict == SPEED_RESTRICT_MACH) {
aProp->setDoubleValue("speed", speedMach());
if (_speedUnits == SPEED_MACH) {
aProp->setDoubleValue("speed-mach", _speed);
} else if (_speedUnits == SPEED_KPH) {
aProp->setDoubleValue("speed-kph", _speed);
} else {
aProp->setDoubleValue("speed", _speed);
aProp->setDoubleValue("speed", _speed);
}
}
if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
aProp->setDoubleValue("altitude-ft", _altitudeFt);
if (_altitudeUnits == ALTITUDE_FLIGHTLEVEL) {
aProp->setDoubleValue("flight-level", _altitude);
} else if (_altitudeUnits == ALTITUDE_METER) {
aProp->setDoubleValue("altitude-m", _altitude);
} else {
aProp->setDoubleValue("altitude-ft", _altitude);
}
}
if (_holdCount > 0) {

View file

@ -92,7 +92,7 @@ public:
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?)
is this flight-pan a route (for planning) or an active flight-plan (which can be flown?)
Routes can contain Via, and cannot be active: FlightPlans contain Legs for procedures and
airways, i.e what the GPS/FMC actally flies.
*/
@ -131,16 +131,17 @@ public:
unsigned int index() const;
int altitudeFt() const;
int speed() const;
double speed(RouteUnits units = DEFAULT_UNITS) const;
double altitude(RouteUnits units = DEFAULT_UNITS) 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);
void setSpeed(RouteRestriction ty, double speed, RouteUnits units = DEFAULT_UNITS);
void setAltitude(RouteRestriction ty, double alt, RouteUnits units = DEFAULT_UNITS);
double courseDeg() const;
double distanceNm() const;
@ -164,8 +165,10 @@ public:
const FlightPlan* _parent;
RouteRestriction _speedRestrict = RESTRICT_NONE,
_altRestrict = RESTRICT_NONE;
int _speed = 0;
int _altitudeFt = 0;
double _speed = 0.0;
double _altitude = 0.0;
RouteUnits _speedUnits = SPEED_KNOTS;
RouteUnits _altitudeUnits = ALTITUDE_FEET;
// if > 0, we will hold at the waypoint using
// the published hold side/course

View file

@ -12,6 +12,7 @@
#include <Navaids/waypoint.hxx>
#include <Airports/airport.hxx>
#include <Navaids/route.hxx>
using std::string;
using std::vector;
@ -203,15 +204,7 @@ void NavdataVisitor::endElement(const char* name)
} 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);
}
_altRestrict = restrictionFromString(_text);
} else if (tag == "Hld_Rad_or_Inbd") {
if (_text == "Inbd") {
_holdRadial = -1.0;

View file

@ -44,6 +44,7 @@
#include <Navaids/LevelDXML.hxx>
#include <Airports/airport.hxx>
#include <Navaids/airways.hxx>
#include <Environment/atmosphere.hxx> // for Mach conversions
using std::string;
using std::vector;
@ -123,7 +124,67 @@ WayptRef viaFromString(const SGGeod& basePosition, const std::string& target)
return new Via(nullptr, airway, nav);
}
} // namespace
static double convertSpeedToKnots(RouteUnits aUnits, double aAltitudeFt, double aValue)
{
switch (aUnits) {
case SPEED_KNOTS: return aValue;
case SPEED_KPH: return aValue * SG_KMH_TO_MPS * SG_MPS_TO_KT;
case SPEED_MACH: return FGAtmo::knotsFromMachAtAltitudeFt(aValue, aAltitudeFt);
default:
throw sg_format_exception("Can't convert unit to Knots", "convertSpeedToKnots");
}
}
static double convertSpeedFromKnots(RouteUnits aUnits, double aAltitudeFt, double aValue)
{
if (aUnits == DEFAULT_UNITS) {
// TODO : use KPH is simulator is in metric
aUnits = SPEED_KNOTS;
}
switch (aUnits) {
case SPEED_KNOTS: return aValue;
case SPEED_KPH: return aValue * SG_KT_TO_MPS * SG_MPS_TO_KMH;
case SPEED_MACH: return FGAtmo::machFromKnotsAtAltitudeFt(aValue, aAltitudeFt);
default:
throw sg_format_exception("Can't convert to unit", "convertSpeedFromKnots");
}
}
} // anonymous namespace
double convertSpeedUnits(RouteUnits aSrc, RouteUnits aDest, double aAltitudeFt, double aValue)
{
const double valueKnots = convertSpeedToKnots(aSrc, aAltitudeFt, aValue);
return convertSpeedFromKnots(aDest, aAltitudeFt, valueKnots);
}
double convertAltitudeUnits(RouteUnits aSrc, RouteUnits aDest, double aValue)
{
if (aDest == DEFAULT_UNITS) {
// TODO : use meters if sim is in metric
aDest = ALTITUDE_FEET;
}
double altFt = 0.0;
switch (aSrc) {
case ALTITUDE_FEET: altFt = aValue; break;
case ALTITUDE_METER: altFt = aValue * SG_METER_TO_FEET; break;
case ALTITUDE_FLIGHTLEVEL: altFt = aValue * 100; break;
default:
throw sg_format_exception("Unsupported source altitude units", "convertAltitudeUnits");
}
switch (aDest) {
case ALTITUDE_FEET: return altFt;
case ALTITUDE_METER: return altFt * SG_FEET_TO_METER;
case ALTITUDE_FLIGHTLEVEL: return round(altFt / 100);
default:
throw sg_format_exception("Unsupported destination altitude units", "convertAltitudeUnits");
}
}
Waypt::Waypt(RouteBase* aOwner) :
_owner(aOwner),
@ -184,28 +245,80 @@ bool Waypt::matches(const SGGeod& aPos) const
return (d < 100.0); // 100 metres seems plenty
}
void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict)
void Waypt::setAltitude(double aAlt, RouteRestriction aRestrict, RouteUnits aUnit)
{
_altitudeFt = aAlt;
if (aUnit == DEFAULT_UNITS) {
aUnit = ALTITUDE_FEET;
}
_altitude = aAlt;
_altitudeUnits = aUnit;
_altRestrict = aRestrict;
}
void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict)
void Waypt::setConstraintAltitude(double aAlt)
{
_constraintAltitude = aAlt;
}
void Waypt::setSpeed(double aSpeed, RouteRestriction aRestrict, RouteUnits aUnit)
{
if (aUnit == DEFAULT_UNITS) {
if ((aRestrict == SPEED_RESTRICT_MACH) || (aRestrict == SPEED_RESTRICT_MACH)) {
aUnit = SPEED_MACH;
} else {
aUnit = SPEED_KNOTS;
}
}
_speed = aSpeed;
_speedUnits = aUnit;
_speedRestrict = aRestrict;
}
double Waypt::speedKts() const
{
assert(_speedRestrict != SPEED_RESTRICT_MACH);
return speed();
return speed(SPEED_KNOTS);
}
double Waypt::speedMach() const
{
assert(_speedRestrict == SPEED_RESTRICT_MACH);
return speed();
return speed(SPEED_MACH);
}
double Waypt::altitudeFt() const
{
return altitude(ALTITUDE_FEET);
}
double Waypt::speed(RouteUnits aUnits) const
{
if (aUnits == _speedUnits) {
return _speed;
}
return convertSpeedUnits(_speedUnits, aUnits, altitudeFt(), _speed);
}
double Waypt::altitude(RouteUnits aUnits) const
{
if (aUnits == _altitudeUnits) {
return _altitude;
}
return convertAltitudeUnits(_altitudeUnits, aUnits, _altitude);
}
double Waypt::constraintAltitude(RouteUnits aUnits) const
{
if (!_constraintAltitude.has_value())
return 0.0;
if (aUnits == _altitudeUnits) {
return _constraintAltitude.value_or(0.0);
}
return convertAltitudeUnits(_altitudeUnits, aUnits, _constraintAltitude.value_or(0.0));
}
double Waypt::magvarDeg() const
@ -234,13 +347,14 @@ std::string Waypt::icaoDescription() const
///////////////////////////////////////////////////////////////////////////
// persistence
static RouteRestriction restrictionFromString(const char* aStr)
RouteRestriction restrictionFromString(const std::string& 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 == "between") return RESTRICT_BETWEEN;
if (l == "none") return RESTRICT_NONE;
if (l == "mach") return SPEED_RESTRICT_MACH;
@ -256,6 +370,7 @@ const char* restrictionToString(RouteRestriction aRestrict)
case RESTRICT_BELOW: return "below";
case RESTRICT_ABOVE: return "above";
case RESTRICT_NONE: return "none";
case RESTRICT_BETWEEN: return "between";
case SPEED_RESTRICT_MACH: return "mach";
default:
@ -382,16 +497,22 @@ WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const
auto target = simgear::strutils::uppercase(s);
// extract altitude
double altFt = 0.0;
double alt = 0.0;
RouteRestriction altSetting = RESTRICT_NONE;
RouteUnits altitudeUnits = ALTITUDE_FEET;
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;
auto altStr = simgear::strutils::uppercase(target.substr(pos + 1));
if (simgear::strutils::starts_with(altStr, "FL")) {
altitudeUnits = ALTITUDE_FLIGHTLEVEL;
altStr = altStr.substr(2); // trim leading 'FL'
} else if (fgGetString("/sim/startup/units") == "meter") {
altitudeUnits = ALTITUDE_METER;
}
alt = std::stof(altStr);
target = target.substr(0, pos);
altSetting = RESTRICT_AT;
}
@ -448,7 +569,7 @@ WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const
}
if (altSetting != RESTRICT_NONE) {
wpt->setAltitude(altFt, altSetting);
wpt->setAltitude(alt, altSetting, altitudeUnits);
}
return wpt;
}
@ -462,42 +583,70 @@ void Waypt::saveAsNode(SGPropertyNode* n) const
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("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"));
if (aProp->hasChild("altitude-ft")) {
_altitude = aProp->getDoubleValue("altitude-ft");
_altitudeUnits = ALTITUDE_FEET;
} else if (aProp->hasChild("altitude-m")) {
_altitude = aProp->getDoubleValue("altitude-m");
_altitudeUnits = ALTITUDE_METER;
} else if (aProp->hasChild("flight-level")) {
_altitude = aProp->getIntValue("flight-level");
_altitudeUnits = ALTITUDE_FLIGHTLEVEL;
}
if (aProp->hasChild("constraint-altitude")) {
_constraintAltitude = aProp->getDoubleValue("constraint-altitude");
}
}
if (aProp->hasChild("speed-restrict")) {
_speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict").c_str());
_speed = aProp->getDoubleValue("speed");
_speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict"));
RouteUnits units = SPEED_KNOTS;
if (_speedRestrict == SPEED_RESTRICT_MACH) {
units = SPEED_MACH;
}
if (aProp->hasChild("speed-mach")) {
units = SPEED_MACH;
_speed = aProp->getDoubleValue("speed-mach");
} else if (aProp->hasChild("speed-kph")) {
units = SPEED_KPH;
_speed = aProp->getDoubleValue("speed-kph");
} else {
_speed = aProp->getDoubleValue("speed");
}
_speedUnits = units;
}
return true;
@ -537,8 +686,19 @@ void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict));
aProp->setDoubleValue("altitude-ft", _altitudeFt);
if (_altitudeUnits == ALTITUDE_METER) {
aProp->setDoubleValue("altitude-m", _altitude);
} else if (_altitudeUnits == ALTITUDE_FLIGHTLEVEL) {
aProp->setDoubleValue("flight-level", _altitude);
} else {
aProp->setDoubleValue("altitude-ft", _altitude);
}
}
if (_constraintAltitude.has_value()) {
aProp->setDoubleValue("constraint-altitude", _constraintAltitude.value_or(0.0));
}
if (_speedRestrict != RESTRICT_NONE) {
aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));

View file

@ -4,32 +4,16 @@
* 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.
// Copyright (C) 2009 James Turner
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef FG_ROUTE_HXX
#define FG_ROUTE_HXX
#pragma once
// std
#include <vector>
#include <map>
#include <iosfwd>
#include <optional>
// Simgear
#include <simgear/structure/SGReferenced.hxx>
@ -88,14 +72,30 @@ typedef enum {
RESTRICT_AT,
RESTRICT_ABOVE,
RESTRICT_BELOW,
RESTRICT_BETWEEN,
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;
typedef enum {
DEFAULT_UNITS = 0,
ALTITUDE_FEET,
ALTITUDE_METER,
ALTITUDE_FLIGHTLEVEL,
SPEED_KNOTS,
SPEED_MACH,
SPEED_KPH
} RouteUnits;
RouteRestriction restrictionFromString(const std::string& t);
bool isMachRestrict(RouteRestriction rr);
double convertSpeedUnits(RouteUnits aSrc, RouteUnits aDest, double aAltitudeFt, double aValue);
double convertAltitudeUnits(RouteUnits aSrc, RouteUnits aDest, double aValue);
/**
* Abstract base class for waypoints (and things that are treated similarly
* by navigation systems). More precisely this is route path elements,
@ -117,12 +117,14 @@ public:
virtual FGPositioned* source() const
{ return nullptr; }
virtual double altitudeFt() const
{ return _altitudeFt; }
double altitudeFt() const;
virtual double speed() const
{ return _speed; }
double speed(RouteUnits aUnits = DEFAULT_UNITS) const;
double altitude(RouteUnits aUnits = DEFAULT_UNITS) const;
double constraintAltitude(RouteUnits aUnits = DEFAULT_UNITS) const;
// wrapper - asserts if restriction type is _MACH
double speedKts() const;
@ -135,9 +137,11 @@ public:
virtual RouteRestriction speedRestriction() const
{ return _speedRestrict; }
void setAltitude(double aAlt, RouteRestriction aRestrict);
void setSpeed(double aSpeed, RouteRestriction aRestrict);
void setAltitude(double aAlt, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);
void setSpeed(double aSpeed, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);
void setConstraintAltitude(double aAlt);
/**
* Identifier assoicated with the waypoint. Human-readable, but
* possibly quite terse, and definitiely not unique.
@ -230,8 +234,16 @@ protected:
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
double _altitude = 0.0;
/// some restriction types specify two altitudes, in which case this is the second value, corresponding to
/// AltitudeCons in the level-D XML procedures format.
std::optional<double> _constraintAltitude;
RouteUnits _altitudeUnits = ALTITUDE_FEET;
double _speed = 0.0;
RouteUnits _speedUnits = SPEED_KNOTS;
RouteRestriction _altRestrict = RESTRICT_NONE;
RouteRestriction _speedRestrict = RESTRICT_NONE;
private:
@ -269,5 +281,3 @@ private:
};
} // of namespace flightgear
#endif // of FG_ROUTE_HXX

View file

@ -96,7 +96,7 @@ NavaidWaypoint::NavaidWaypoint(RouteBase* aOwner) :
SGGeod NavaidWaypoint::position() const
{
return SGGeod::fromGeodFt(_navaid->geod(), _altitudeFt);
return SGGeod::fromGeodFt(_navaid->geod(), altitudeFt());
}
std::string NavaidWaypoint::ident() const
@ -170,7 +170,7 @@ void OffsetNavaidWaypoint::init()
SGGeod offset;
double az2;
SGGeodesy::direct(_navaid->geod(), _radial, _distanceNm * SG_NM_TO_METER, offset, az2);
_geod = SGGeod::fromGeodFt(offset, _altitudeFt);
_geod = SGGeod::fromGeodFt(offset, altitude(ALTITUDE_FEET));
}
bool OffsetNavaidWaypoint::initFromProperties(SGPropertyNode_ptr aProp)

View file

@ -745,4 +745,5 @@ bool FGCom::isInRange(const double &freq) const
// Register the subsystem.
SGSubsystemMgr::Registrant<FGCom> registrantFGCom;
SGSubsystemMgr::Registrant<FGCom> registrantFGCom(
SGSubsystemMgr::SOUND);

View file

@ -36,6 +36,7 @@
#include <simgear/structure/exception.hxx>
#include <simgear/scene/model/modellib.hxx>
#include <simgear/scene/util/SGReaderWriterOptions.hxx>
#include <simgear/scene/tgdb/VPBTechnique.hxx>
#include <simgear/scene/tsync/terrasync.hxx>
#include <simgear/misc/strutils.hxx>
#include <simgear/scene/material/matlib.hxx>
@ -497,6 +498,11 @@ void FGTileMgr::update_queues(bool& isDownloadingScenery)
tile_cache.clear_entry(drop_index);
if (_use_vpb) {
// Clear out any VPB data - e.g. roads
simgear::VPBTechnique::unloadFeatures(old->get_tile_bucket());
}
osg::ref_ptr<osg::Object> subgraph = old->getNode();
old->removeFromSceneGraph();
delete old;

View file

@ -464,9 +464,27 @@ static RouteRestriction routeRestrictionFromArg(naRef arg)
if (u == "mach") return SPEED_RESTRICT_MACH;
if (u == "computed-mach") return SPEED_COMPUTED_MACH;
if (u == "delete") return RESTRICT_DELETE;
if (u == "between") return RESTRICT_BETWEEN;
return RESTRICT_NONE;
};
static RouteUnits routeUnitsFromArg(naRef arg)
{
if (naIsNil(arg) || !naIsString(arg)) {
return DEFAULT_UNITS;
}
const auto u = simgear::strutils::lowercase(naStr_data(arg));
if ((u == "knots") || (u == "kt")) return SPEED_KNOTS;
if (u == "kph") return SPEED_KPH;
if (u == "mach") return SPEED_MACH;
if ((u == "ft") || (u == "\'") || (u == "feet")) return ALTITUDE_FEET;
if ((u == "m") || (u == "meter") || (u == "meters")) return ALTITUDE_METER;
if ((u == "fl") || (u == "flight-level")) return ALTITUDE_FLIGHTLEVEL;
throw sg_format_exception("routeUnitsFromArg: unknown units", u);
}
naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
{
switch (rr) {
@ -478,6 +496,7 @@ naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
case RESTRICT_COMPUTED: return stringToNasal(c, "computed");
case SPEED_COMPUTED_MACH: return stringToNasal(c, "computed-mach");
case RESTRICT_DELETE: return stringToNasal(c, "delete");
case RESTRICT_BETWEEN: return stringToNasal(c, "between");
}
return naNil();
@ -1984,6 +2003,8 @@ static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
double speed = 0.0;
RouteRestriction rr = RESTRICT_AT;
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) {
if (naIsNil(args[0])) {
// clear the restriction to NONE
@ -1994,9 +2015,13 @@ static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
} else {
naRuntimeError(c, "bad arguments to setSpeed");
}
if (argc > 2) {
units = routeUnitsFromArg(args[2]);
}
}
leg->setSpeed(rr, speed);
leg->setSpeed(rr, speed, units);
} else {
naRuntimeError(c, "bad arguments to setSpeed");
}
@ -2013,6 +2038,8 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
double altitude = 0.0;
RouteRestriction rr = RESTRICT_AT;
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) {
if (naIsNil(args[0])) {
// clear the restriction to NONE
@ -2023,9 +2050,37 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
} else {
naRuntimeError(c, "bad arguments to leg.setAltitude");
}
if (argc > 2) {
units = routeUnitsFromArg(args[2]);
}
} else if (naIsVector(args[0])) {
const auto altTuple = args[0];
// we need a second restriction type arg, and the tuple should be of length 2
if ((argc < 2) || (naVec_size(altTuple) != 2)) {
naRuntimeError(c, "missing/bad arguments to leg.setAltitude");
}
rr = routeRestrictionFromArg(args[1]);
if (rr != RESTRICT_BETWEEN) {
naRuntimeError(c, "leg.setAltitude: passed a 2-tuple, but restriction type is not 'between'");
}
double constraintAltitude;
const auto ok = convertToNum(naVec_get(altTuple, 0), constraintAltitude)
&& convertToNum(naVec_get(altTuple, 1), altitude);
if (!ok) {
naRuntimeError(c, "leg.setAltitude: tuple members not convertible to numeric altitudes");
}
if (argc > 2) {
units = routeUnitsFromArg(args[2]);
}
// TODO: store constraint altitude
}
leg->setAltitude(rr, altitude);
leg->setAltitude(rr, altitude, units);
} else {
naRuntimeError(c, "bad arguments to setleg.setAltitude");
}
@ -2033,6 +2088,43 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
return naNil();
}
static naRef f_leg_altitude(naContext c, naRef me, int argc, naRef* args)
{
FlightPlan::Leg* leg = fpLegGhost(me);
if (!leg) {
naRuntimeError(c, "leg.altitude called on non-flightplan-leg object");
}
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) {
units = routeUnitsFromArg(args[9]);
}
if (leg->altitudeRestriction() == RESTRICT_BETWEEN) {
naRef result = naNewVector(c);
naVec_append(result, naNum(leg->waypoint()->constraintAltitude(units)));
naVec_append(result, naNum(leg->altitude(units)));
return result;
}
return naNum(leg->altitude(units));
}
static naRef f_leg_speed(naContext c, naRef me, int argc, naRef* args)
{
FlightPlan::Leg* leg = fpLegGhost(me);
if (!leg) {
naRuntimeError(c, "leg.speed called on non-flightplan-leg object");
}
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) {
units = routeUnitsFromArg(args[9]);
}
return naNum(leg->speed(units));
}
static naRef f_leg_courseAndDistanceFrom(naContext c, naRef me, int argc, naRef* args)
{
FlightPlan::Leg* leg = fpLegGhost(me);
@ -2301,6 +2393,8 @@ naRef initNasalFlightPlan(naRef globals, naContext c)
naSave(c, fpLegPrototype);
hashset(c, fpLegPrototype, "setSpeed", naNewFunc(c, naNewCCode(c, f_leg_setSpeed)));
hashset(c, fpLegPrototype, "setAltitude", naNewFunc(c, naNewCCode(c, f_leg_setAltitude)));
hashset(c, fpLegPrototype, "altitude", naNewFunc(c, naNewCCode(c, f_leg_altitude)));
hashset(c, fpLegPrototype, "speed", naNewFunc(c, naNewCCode(c, f_leg_speed)));
hashset(c, fpLegPrototype, "path", naNewFunc(c, naNewCCode(c, f_leg_path)));
hashset(c, fpLegPrototype, "courseAndDistanceFrom", naNewFunc(c, naNewCCode(c, f_leg_courseAndDistanceFrom)));

View file

@ -4,6 +4,7 @@ foreach( simgear_test_category
props
canvas
structure
timing
)
add_subdirectory(${simgear_test_category})

View file

@ -0,0 +1,12 @@
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_SGTime.cxx
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_SGTime.hxx
PARENT_SCOPE
)

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: (C) 2022 James Turner <james@flightgear.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "test_SGTime.hxx"
// Set up the tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SimgearTimingTests, "Simgear unit tests");

View file

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: (C) 2022 James Turner <james@flightgear.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <algorithm>
#include <memory>
#include "test_SGTime.hxx"
#include "test_suite/dataStore.hxx"
#include <simgear/math/SGGeod.hxx>
using namespace std;
// Set up function for each test.
void SimgearTimingTests::setUp()
{
}
// Clean up after each test.
void SimgearTimingTests::tearDown()
{
}
void SimgearTimingTests::testBadZoneDetectPosition()
{
DataStore &data = DataStore::get();
const SGPath rootPath{data.getFGRoot()};
if (rootPath.isNull()) {
return;
}
std::unique_ptr<SGTime> t(new SGTime{rootPath});
const auto goodPos = SGGeod::fromDeg(-69.5, 12.0);
CPPUNIT_ASSERT(t->updateLocal(goodPos, rootPath / "Timezone"));
// discovered while flying with the Shuttle, but actually
// can happen at sea level.
// https://sourceforge.net/p/flightgear/codetickets/2780/
// this was fixed by updating the timezone data from
// https://github.com/BertoldVdb/ZoneDetect into FGData/Timezone/timezone16.bin
const auto pos = SGGeod::fromDeg(-69.0, 12.0);
CPPUNIT_ASSERT(t->updateLocal(pos, rootPath / "Timezone"));
}

View file

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: (C) 2022 James Turner <james@flightgear.org>
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include <cppunit/TestFixture.h>
#include <cppunit/extensions/HelperMacros.h>
#include <simgear/timing/sg_time.hxx>
// The unit tests of the simgear property tree.
class SimgearTimingTests : public CppUnit::TestFixture
{
// Set up the test suite.
CPPUNIT_TEST_SUITE(SimgearTimingTests);
CPPUNIT_TEST(testBadZoneDetectPosition);
CPPUNIT_TEST_SUITE_END();
public:
// Set up function for each test.
void setUp();
// Clean up after each test.
void tearDown();
// The tests.
void testBadZoneDetectPosition();
private:
};

View file

@ -702,6 +702,77 @@ void FlightplanTests::testLoadSaveMachRestriction()
CPPUNIT_ASSERT_EQUAL(8, f2->legAtIndex(2)->holdCount());
}
void FlightplanTests::testLoadSaveBetweenRestriction()
{
const std::string fpXML = R"(<?xml version="1.0" encoding="UTF-8"?>
<PropertyList>
<version type="int">2</version>
<departure>
<airport type="string">SAWG</airport>
<runway type="string">25</runway>
</departure>
<destination>
<airport type="string">SUMU</airport>
</destination>
<route>
<wp n="0">
<type type="string">navaid</type>
<ident type="string">PUGLI</ident>
<lon type="double">-60.552200</lon>
<lat type="double">-40.490000</lat>
</wp>
<wp n="1">
<type type="string">basic</type>
<alt-restrict type="string">between</alt-restrict>
<altitude-ft type="double">36000</altitude-ft>
<constraint-altitude type="double">32000</constraint-altitude>
<ident type="string">SV002</ident>
<lon type="double">-115.50531</lon>
<lat type="double">37.89523</lat>
</wp>
<wp n="2">
<type type="string">navaid</type>
<ident type="string">SIGUL</ident>
<lon type="double">-60.552200</lon>
<lat type="double">-40.490000</lat>
</wp>
</route>
</PropertyList>
)";
std::istringstream stream(fpXML);
FlightPlanRef f = FlightPlan::create();
bool ok = f->load(stream);
CPPUNIT_ASSERT(ok);
auto leg = f->legAtIndex(1);
CPPUNIT_ASSERT_EQUAL(RESTRICT_BETWEEN, leg->altitudeRestriction());
CPPUNIT_ASSERT_DOUBLES_EQUAL(36000, leg->altitudeFt(), 0.01);
CPPUNIT_ASSERT_DOUBLES_EQUAL(32000, leg->waypoint()->constraintAltitude(ALTITUDE_FEET), 0.01);
}
void FlightplanTests::testRestrictionUnits()
{
FlightPlanRef fp1 = makeTestFP("LIRF"s, "34L"s, "LEBL"s, "25R"s,
"ESINO GITRI BALEN MUREN TOSNU"s);
auto leg = fp1->legAtIndex(2);
leg->setAltitude(RESTRICT_ABOVE, 120, ALTITUDE_FLIGHTLEVEL);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->altitude(), 12000.0, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->altitude(ALTITUDE_METER), 12000.0 * SG_FEET_TO_METER, 0.1);
leg->setSpeed(RESTRICT_AT, 300);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->speedKts(), 300, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->speed(), 300, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->speed(SPEED_KPH), 300 * SG_KT_TO_MPS * SG_MPS_TO_KMH, 0.1);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->speedMach(), 0.473, 0.1);
// setting a mach restriction assumes Mach units by default
leg->setSpeed(SPEED_RESTRICT_MACH, 0.7);
leg->setAltitude(RESTRICT_AT, 38000);
CPPUNIT_ASSERT_DOUBLES_EQUAL(leg->speedKts(), 401, 1);
}
void FlightplanTests::testBasicDiscontinuity()
{
FlightPlanRef fp1 = makeTestFP("LIRF"s, "34L"s, "LEBL"s, "25R"s,

View file

@ -58,9 +58,11 @@ class FlightplanTests : public CppUnit::TestFixture
CPPUNIT_TEST(testViaInsertIntoFP);
CPPUNIT_TEST(testViaInsertIntoRoute);
CPPUNIT_TEST(loadFGFPAsRoute);
// CPPUNIT_TEST(testParseICAORoute);
// CPPUNIT_TEST(testParseICANLowLevelRoute);
CPPUNIT_TEST(testLoadSaveBetweenRestriction);
CPPUNIT_TEST(testRestrictionUnits);
// CPPUNIT_TEST(testParseICAORoute);
// CPPUNIT_TEST(testParseICANLowLevelRoute);
CPPUNIT_TEST_SUITE_END();
public:
@ -100,6 +102,8 @@ public:
void testViaInsertIntoFP();
void testViaInsertIntoRoute();
void loadFGFPAsRoute();
void testLoadSaveBetweenRestriction();
void testRestrictionUnits();
};
#endif // FG_FLIGHTPLAN_UNIT_TESTS_HXX

View file

@ -197,6 +197,19 @@ void FPNasalTests::testRestrictions()
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_DELETE, fp1->legAtIndex(3)->speedRestriction());
ok = FGTestApi::executeNasal(R"(
var fp = flightplan(); # retrieve the global flightplan
var leg = fp.getWP(3);
leg.setSpeed(0.8, 'at', "Mach");
leg.setAltitude(300, 'above', 'FL');
)");
CPPUNIT_ASSERT(ok);
CPPUNIT_ASSERT_EQUAL(RESTRICT_AT, fp1->legAtIndex(3)->speedRestriction());
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.8, fp1->legAtIndex(3)->speedMach(), 0.01);
CPPUNIT_ASSERT_EQUAL(RESTRICT_ABOVE, fp1->legAtIndex(3)->altitudeRestriction());
CPPUNIT_ASSERT_DOUBLES_EQUAL(30000, fp1->legAtIndex(3)->altitudeFt(), 1.0);
}
void FPNasalTests::testSegfaultWaypointGhost()

View file

@ -1,14 +1,28 @@
if (ENABLE_SWIFT)
set(SWIFT_TESTS_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.cxx
)
set(SWIFT_TESTS_HEADERS
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.hxx
)
endif()
set(TESTSUITE_SOURCES
${TESTSUITE_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/TestSuite.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.cxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.cxx
${SWIFT_TESTS_SOURCES}
PARENT_SCOPE
)
set(TESTSUITE_HEADERS
${TESTSUITE_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftService.hxx
${CMAKE_CURRENT_SOURCE_DIR}/test_swiftAircraftManager.hxx
${SWIFT_TESTS_HEADERS}
PARENT_SCOPE
)

View file

@ -3,9 +3,15 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#if defined(ENABLE_SWIFT)
#include "test_swiftAircraftManager.hxx"
#include "test_swiftService.hxx"
// Set up the unit tests.
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwiftAircraftManagerTest, "Unit tests");
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SwiftServiceTest, "Unit tests");
#endif