1
0
Fork 0

Extend FlightPlan to handle ‘between’ restrictons

As part of this, also improve units handling in speed/altitude
restrictions.
This commit is contained in:
James Turner 2022-11-28 15:34:00 +00:00
parent 73463a3a98
commit 0a85e23cde
9 changed files with 464 additions and 144 deletions

View file

@ -1302,15 +1302,6 @@ bool FlightPlan::loadVersion2XMLRoute(SGPropertyNode_ptr routeData)
} }
LegRef l = new Leg{this, wp}; 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());
}
if (wpNode->hasChild("hold-count")) { if (wpNode->hasChild("hold-count")) {
l->setHoldCount(wpNode->getIntValue("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); double altFt = aWP->getDoubleValue("altitude-ft", -9999.9);
if (altFt > -9990.0) { if (altFt > -9990.0) {
w->setAltitude(altFt, RESTRICT_AT); w->setAltitude(altFt, RESTRICT_AT, ALTITUDE_FEET);
} }
return w; return w;
@ -1564,8 +1555,11 @@ FlightPlan::Leg* FlightPlan::Leg::cloneFor(FlightPlan* owner) const
// clone local data // clone local data
c->_speed = _speed; c->_speed = _speed;
c->_speedRestrict = _speedRestrict; c->_speedRestrict = _speedRestrict;
c->_altitudeFt = _altitudeFt; c->_speedUnits = _speedUnits;
c->_altitude = _altitude;
c->_altRestrict = _altRestrict; c->_altRestrict = _altRestrict;
c->_altitudeUnits = _altitudeUnits;
c->_holdCount = c->_holdCount;
return c; return c;
} }
@ -1583,36 +1577,37 @@ unsigned int FlightPlan::Leg::index() const
return _parent->findLegIndex(this); return _parent->findLegIndex(this);
} }
int FlightPlan::Leg::altitudeFt() const double FlightPlan::Leg::altitude(RouteUnits aUnits) const
{ {
if (_altRestrict != RESTRICT_NONE) { 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) { if (_speedRestrict != RESTRICT_NONE) {
return _speed; return convertSpeedUnits(_speedUnits, units, altitudeFt(), _speed);
} }
return _waypt->speed(); return _waypt->speed(units);
} }
int FlightPlan::Leg::speedKts() const int FlightPlan::Leg::speedKts() const
{ {
return speed(); return static_cast<int>(speed(SPEED_KNOTS));
} }
double FlightPlan::Leg::speedMach() const double FlightPlan::Leg::speedMach() const
{ {
if (!isMachRestrict(_speedRestrict)) { return speed(SPEED_MACH);
return 0.0;
}
return -(_speed / 100.0);
} }
RouteRestriction FlightPlan::Leg::altitudeRestriction() const RouteRestriction FlightPlan::Leg::altitudeRestriction() const
@ -1633,20 +1628,29 @@ RouteRestriction FlightPlan::Leg::speedRestriction() const
return _waypt->speedRestriction(); return _waypt->speedRestriction();
} }
void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed) void FlightPlan::Leg::setSpeed(RouteRestriction ty, double speed, RouteUnits aUnit)
{ {
_speedRestrict = ty; _speedRestrict = ty;
if (aUnit == DEFAULT_UNITS) {
if (isMachRestrict(ty)) { if (isMachRestrict(ty)) {
_speed = (speed * -100); aUnit = SPEED_MACH;
} else { } else {
// TODO: check for system in metric?
aUnit = SPEED_KNOTS;
}
}
_speedUnits = aUnit;
_speed = speed; _speed = speed;
} }
}
void FlightPlan::Leg::setAltitude(RouteRestriction ty, int altFt) void FlightPlan::Leg::setAltitude(RouteRestriction ty, double alt, RouteUnits aUnit)
{ {
_altRestrict = ty; _altRestrict = ty;
_altitudeFt = altFt; if (aUnit == DEFAULT_UNITS) {
aUnit = ALTITUDE_FEET;
}
_altitudeUnits = aUnit;
_altitude = alt;
} }
double FlightPlan::Leg::courseDeg() const double FlightPlan::Leg::courseDeg() const
@ -1722,8 +1726,10 @@ void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const
{ {
if (_speedRestrict != RESTRICT_NONE) { if (_speedRestrict != RESTRICT_NONE) {
aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict)); aProp->setStringValue("speed-restrict", restrictionToString(_speedRestrict));
if (_speedRestrict == SPEED_RESTRICT_MACH) { if (_speedUnits == SPEED_MACH) {
aProp->setDoubleValue("speed", speedMach()); aProp->setDoubleValue("speed-mach", _speed);
} else if (_speedUnits == SPEED_KPH) {
aProp->setDoubleValue("speed-kph", _speed);
} else { } else {
aProp->setDoubleValue("speed", _speed); aProp->setDoubleValue("speed", _speed);
} }
@ -1731,7 +1737,13 @@ void FlightPlan::Leg::writeToProperties(SGPropertyNode* aProp) const
if (_altRestrict != RESTRICT_NONE) { if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict)); 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) { if (_holdCount > 0) {

View file

@ -92,7 +92,7 @@ public:
FlightPlanRef clone(const std::string& newIdent = {}, bool convertToFlightPlan = false) const; 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 Routes can contain Via, and cannot be active: FlightPlans contain Legs for procedures and
airways, i.e what the GPS/FMC actally flies. airways, i.e what the GPS/FMC actally flies.
*/ */
@ -131,7 +131,8 @@ public:
unsigned int index() const; unsigned int index() const;
int altitudeFt() const; int altitudeFt() const;
int speed() const; double speed(RouteUnits units = DEFAULT_UNITS) const;
double altitude(RouteUnits units = DEFAULT_UNITS) const;
int speedKts() const; int speedKts() const;
double speedMach() const; double speedMach() const;
@ -139,8 +140,8 @@ public:
RouteRestriction altitudeRestriction() const; RouteRestriction altitudeRestriction() const;
RouteRestriction speedRestriction() const; RouteRestriction speedRestriction() const;
void setSpeed(RouteRestriction ty, double speed); void setSpeed(RouteRestriction ty, double speed, RouteUnits units = DEFAULT_UNITS);
void setAltitude(RouteRestriction ty, int altFt); void setAltitude(RouteRestriction ty, double alt, RouteUnits units = DEFAULT_UNITS);
double courseDeg() const; double courseDeg() const;
double distanceNm() const; double distanceNm() const;
@ -164,8 +165,10 @@ public:
const FlightPlan* _parent; const FlightPlan* _parent;
RouteRestriction _speedRestrict = RESTRICT_NONE, RouteRestriction _speedRestrict = RESTRICT_NONE,
_altRestrict = RESTRICT_NONE; _altRestrict = RESTRICT_NONE;
int _speed = 0; double _speed = 0.0;
int _altitudeFt = 0; double _altitude = 0.0;
RouteUnits _speedUnits = SPEED_KNOTS;
RouteUnits _altitudeUnits = ALTITUDE_FEET;
// if > 0, we will hold at the waypoint using // if > 0, we will hold at the waypoint using
// the published hold side/course // the published hold side/course

View file

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

View file

@ -44,6 +44,7 @@
#include <Navaids/LevelDXML.hxx> #include <Navaids/LevelDXML.hxx>
#include <Airports/airport.hxx> #include <Airports/airport.hxx>
#include <Navaids/airways.hxx> #include <Navaids/airways.hxx>
#include <Environment/atmosphere.hxx> // for Mach conversions
using std::string; using std::string;
using std::vector; using std::vector;
@ -123,7 +124,67 @@ WayptRef viaFromString(const SGGeod& basePosition, const std::string& target)
return new Via(nullptr, airway, nav); 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) : Waypt::Waypt(RouteBase* aOwner) :
_owner(aOwner), _owner(aOwner),
@ -184,28 +245,80 @@ bool Waypt::matches(const SGGeod& aPos) const
return (d < 100.0); // 100 metres seems plenty 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; _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; _speed = aSpeed;
_speedUnits = aUnit;
_speedRestrict = aRestrict; _speedRestrict = aRestrict;
} }
double Waypt::speedKts() const double Waypt::speedKts() const
{ {
assert(_speedRestrict != SPEED_RESTRICT_MACH); return speed(SPEED_KNOTS);
return speed();
} }
double Waypt::speedMach() const double Waypt::speedMach() const
{ {
assert(_speedRestrict == SPEED_RESTRICT_MACH); return speed(SPEED_MACH);
return speed(); }
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 double Waypt::magvarDeg() const
@ -234,13 +347,14 @@ std::string Waypt::icaoDescription() const
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// persistence // persistence
static RouteRestriction restrictionFromString(const char* aStr) RouteRestriction restrictionFromString(const std::string& aStr)
{ {
std::string l = simgear::strutils::lowercase(aStr); std::string l = simgear::strutils::lowercase(aStr);
if (l == "at") return RESTRICT_AT; if (l == "at") return RESTRICT_AT;
if (l == "above") return RESTRICT_ABOVE; if (l == "above") return RESTRICT_ABOVE;
if (l == "below") return RESTRICT_BELOW; if (l == "below") return RESTRICT_BELOW;
if (l == "between") return RESTRICT_BETWEEN;
if (l == "none") return RESTRICT_NONE; if (l == "none") return RESTRICT_NONE;
if (l == "mach") return SPEED_RESTRICT_MACH; if (l == "mach") return SPEED_RESTRICT_MACH;
@ -256,6 +370,7 @@ const char* restrictionToString(RouteRestriction aRestrict)
case RESTRICT_BELOW: return "below"; case RESTRICT_BELOW: return "below";
case RESTRICT_ABOVE: return "above"; case RESTRICT_ABOVE: return "above";
case RESTRICT_NONE: return "none"; case RESTRICT_NONE: return "none";
case RESTRICT_BETWEEN: return "between";
case SPEED_RESTRICT_MACH: return "mach"; case SPEED_RESTRICT_MACH: return "mach";
default: default:
@ -382,16 +497,22 @@ WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const
auto target = simgear::strutils::uppercase(s); auto target = simgear::strutils::uppercase(s);
// extract altitude // extract altitude
double altFt = 0.0; double alt = 0.0;
RouteRestriction altSetting = RESTRICT_NONE; RouteRestriction altSetting = RESTRICT_NONE;
RouteUnits altitudeUnits = ALTITUDE_FEET;
size_t pos = target.find('@'); size_t pos = target.find('@');
if (pos != string::npos) { if (pos != string::npos) {
altFt = std::stof(target.substr(pos + 1)); auto altStr = simgear::strutils::uppercase(target.substr(pos + 1));
target = target.substr(0, pos); if (simgear::strutils::starts_with(altStr, "FL")) {
if (fgGetString("/sim/startup/units") == "meter") { altitudeUnits = ALTITUDE_FLIGHTLEVEL;
altFt *= SG_METER_TO_FEET; 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; altSetting = RESTRICT_AT;
} }
@ -448,7 +569,7 @@ WayptRef Waypt::createFromString(RouteBase* aOwner, const std::string& s, const
} }
if (altSetting != RESTRICT_NONE) { if (altSetting != RESTRICT_NONE) {
wpt->setAltitude(altFt, altSetting); wpt->setAltitude(alt, altSetting, altitudeUnits);
} }
return wpt; return wpt;
} }
@ -491,15 +612,43 @@ bool Waypt::initFromProperties(SGPropertyNode_ptr aProp)
} }
if (aProp->hasChild("alt-restrict")) { if (aProp->hasChild("alt-restrict")) {
_altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict").c_str()); _altRestrict = restrictionFromString(aProp->getStringValue("alt-restrict"));
_altitudeFt = aProp->getDoubleValue("altitude-ft"); 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")) { if (aProp->hasChild("speed-restrict")) {
_speedRestrict = restrictionFromString(aProp->getStringValue("speed-restrict").c_str()); _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"); _speed = aProp->getDoubleValue("speed");
} }
_speedUnits = units;
}
return true; return true;
} }
@ -537,7 +686,18 @@ void Waypt::writeToProperties(SGPropertyNode_ptr aProp) const
if (_altRestrict != RESTRICT_NONE) { if (_altRestrict != RESTRICT_NONE) {
aProp->setStringValue("alt-restrict", restrictionToString(_altRestrict)); 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) { if (_speedRestrict != RESTRICT_NONE) {

View file

@ -4,32 +4,16 @@
* owns a collection (list, tree, graph) of route elements - such as airways, * owns a collection (list, tree, graph) of route elements - such as airways,
* procedures or a flight plan. * procedures or a flight plan.
*/ */
// Copyright (C) 2009 James Turner
// SPDX-License-Identifier: GPL-2.0-or-later
// Written by James Turner, started 2009. #pragma once
//
// 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 // std
#include <vector> #include <vector>
#include <map> #include <map>
#include <iosfwd> #include <iosfwd>
#include <optional>
// Simgear // Simgear
#include <simgear/structure/SGReferenced.hxx> #include <simgear/structure/SGReferenced.hxx>
@ -88,14 +72,30 @@ typedef enum {
RESTRICT_AT, RESTRICT_AT,
RESTRICT_ABOVE, RESTRICT_ABOVE,
RESTRICT_BELOW, RESTRICT_BELOW,
RESTRICT_BETWEEN,
SPEED_RESTRICT_MACH, ///< encode an 'AT' restriction in Mach, not IAS SPEED_RESTRICT_MACH, ///< encode an 'AT' restriction in Mach, not IAS
RESTRICT_DELETE, ///< ignore underlying restriction (on a leg) RESTRICT_DELETE, ///< ignore underlying restriction (on a leg)
RESTRICT_COMPUTED, ///< data is computed, not a real restriction RESTRICT_COMPUTED, ///< data is computed, not a real restriction
SPEED_COMPUTED_MACH ///< variant on above to encode a Mach value SPEED_COMPUTED_MACH ///< variant on above to encode a Mach value
} RouteRestriction; } 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); 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 * Abstract base class for waypoints (and things that are treated similarly
* by navigation systems). More precisely this is route path elements, * by navigation systems). More precisely this is route path elements,
@ -117,11 +117,13 @@ public:
virtual FGPositioned* source() const virtual FGPositioned* source() const
{ return nullptr; } { return nullptr; }
virtual double altitudeFt() const double altitudeFt() const;
{ return _altitudeFt; }
virtual double speed() const double speed(RouteUnits aUnits = DEFAULT_UNITS) const;
{ return _speed; }
double altitude(RouteUnits aUnits = DEFAULT_UNITS) const;
double constraintAltitude(RouteUnits aUnits = DEFAULT_UNITS) const;
// wrapper - asserts if restriction type is _MACH // wrapper - asserts if restriction type is _MACH
double speedKts() const; double speedKts() const;
@ -135,8 +137,10 @@ public:
virtual RouteRestriction speedRestriction() const virtual RouteRestriction speedRestriction() const
{ return _speedRestrict; } { return _speedRestrict; }
void setAltitude(double aAlt, RouteRestriction aRestrict); void setAltitude(double aAlt, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);
void setSpeed(double aSpeed, RouteRestriction aRestrict); void setSpeed(double aSpeed, RouteRestriction aRestrict, RouteUnits aUnits = DEFAULT_UNITS);
void setConstraintAltitude(double aAlt);
/** /**
* Identifier assoicated with the waypoint. Human-readable, but * Identifier assoicated with the waypoint. Human-readable, but
@ -230,8 +234,16 @@ protected:
typedef Waypt*(FactoryFunction)(RouteBase* aOwner); typedef Waypt*(FactoryFunction)(RouteBase* aOwner);
static void registerFactory(const std::string aNodeType, FactoryFunction* aFactory); static void registerFactory(const std::string aNodeType, FactoryFunction* aFactory);
double _altitudeFt = 0.0; double _altitude = 0.0;
double _speed = 0.0; // knots IAS or mach /// 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 _altRestrict = RESTRICT_NONE;
RouteRestriction _speedRestrict = RESTRICT_NONE; RouteRestriction _speedRestrict = RESTRICT_NONE;
private: private:
@ -269,5 +281,3 @@ private:
}; };
} // of namespace flightgear } // of namespace flightgear
#endif // of FG_ROUTE_HXX

View file

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

View file

@ -464,9 +464,27 @@ static RouteRestriction routeRestrictionFromArg(naRef arg)
if (u == "mach") return SPEED_RESTRICT_MACH; if (u == "mach") return SPEED_RESTRICT_MACH;
if (u == "computed-mach") return SPEED_COMPUTED_MACH; if (u == "computed-mach") return SPEED_COMPUTED_MACH;
if (u == "delete") return RESTRICT_DELETE; if (u == "delete") return RESTRICT_DELETE;
if (u == "between") return RESTRICT_BETWEEN;
return RESTRICT_NONE; 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) naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
{ {
switch (rr) { switch (rr) {
@ -478,6 +496,7 @@ naRef routeRestrictionToNasal(naContext c, RouteRestriction rr)
case RESTRICT_COMPUTED: return stringToNasal(c, "computed"); case RESTRICT_COMPUTED: return stringToNasal(c, "computed");
case SPEED_COMPUTED_MACH: return stringToNasal(c, "computed-mach"); case SPEED_COMPUTED_MACH: return stringToNasal(c, "computed-mach");
case RESTRICT_DELETE: return stringToNasal(c, "delete"); case RESTRICT_DELETE: return stringToNasal(c, "delete");
case RESTRICT_BETWEEN: return stringToNasal(c, "between");
} }
return naNil(); return naNil();
@ -1984,6 +2003,8 @@ static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
double speed = 0.0; double speed = 0.0;
RouteRestriction rr = RESTRICT_AT; RouteRestriction rr = RESTRICT_AT;
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) { if (argc > 0) {
if (naIsNil(args[0])) { if (naIsNil(args[0])) {
// clear the restriction to NONE // clear the restriction to NONE
@ -1994,9 +2015,13 @@ static naRef f_leg_setSpeed(naContext c, naRef me, int argc, naRef* args)
} else { } else {
naRuntimeError(c, "bad arguments to setSpeed"); naRuntimeError(c, "bad arguments to setSpeed");
} }
if (argc > 2) {
units = routeUnitsFromArg(args[2]);
}
} }
leg->setSpeed(rr, speed); leg->setSpeed(rr, speed, units);
} else { } else {
naRuntimeError(c, "bad arguments to setSpeed"); 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; double altitude = 0.0;
RouteRestriction rr = RESTRICT_AT; RouteRestriction rr = RESTRICT_AT;
RouteUnits units = DEFAULT_UNITS;
if (argc > 0) { if (argc > 0) {
if (naIsNil(args[0])) { if (naIsNil(args[0])) {
// clear the restriction to NONE // clear the restriction to NONE
@ -2023,9 +2050,13 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
} else { } else {
naRuntimeError(c, "bad arguments to leg.setAltitude"); naRuntimeError(c, "bad arguments to leg.setAltitude");
} }
if (argc > 2) {
units = routeUnitsFromArg(args[2]);
}
} }
leg->setAltitude(rr, altitude); leg->setAltitude(rr, altitude, units);
} else { } else {
naRuntimeError(c, "bad arguments to setleg.setAltitude"); naRuntimeError(c, "bad arguments to setleg.setAltitude");
} }
@ -2033,6 +2064,40 @@ static naRef f_leg_setAltitude(naContext c, naRef me, int argc, naRef* args)
return naNil(); 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) {
// make a 2-tuple for the between case
}
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) static naRef f_leg_courseAndDistanceFrom(naContext c, naRef me, int argc, naRef* args)
{ {
FlightPlan::Leg* leg = fpLegGhost(me); FlightPlan::Leg* leg = fpLegGhost(me);
@ -2301,6 +2366,8 @@ naRef initNasalFlightPlan(naRef globals, naContext c)
naSave(c, fpLegPrototype); naSave(c, fpLegPrototype);
hashset(c, fpLegPrototype, "setSpeed", naNewFunc(c, naNewCCode(c, f_leg_setSpeed))); 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, "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, "path", naNewFunc(c, naNewCCode(c, f_leg_path)));
hashset(c, fpLegPrototype, "courseAndDistanceFrom", naNewFunc(c, naNewCCode(c, f_leg_courseAndDistanceFrom))); hashset(c, fpLegPrototype, "courseAndDistanceFrom", naNewFunc(c, naNewCCode(c, f_leg_courseAndDistanceFrom)));

View file

@ -702,6 +702,77 @@ void FlightplanTests::testLoadSaveMachRestriction()
CPPUNIT_ASSERT_EQUAL(8, f2->legAtIndex(2)->holdCount()); 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() void FlightplanTests::testBasicDiscontinuity()
{ {
FlightPlanRef fp1 = makeTestFP("LIRF"s, "34L"s, "LEBL"s, "25R"s, FlightPlanRef fp1 = makeTestFP("LIRF"s, "34L"s, "LEBL"s, "25R"s,

View file

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