1
0
Fork 0

Launcher: initial carrier support

Allow selecting carriers from scenarios, and starting at either a
parking position, or a distance offset from the FLOLS (effectively
a crude ‘on-final’)

Extend the —carrier startup option to accept a runway ident of FLOLS,
in conjunction with the existing —offset-distance argument.
This commit is contained in:
James Turner 2020-03-18 16:34:02 +00:00
parent f903cdfa50
commit 177fc565da
22 changed files with 962 additions and 171 deletions

View file

@ -17,9 +17,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 <algorithm>
#include <string>
@ -35,11 +33,13 @@
#include "AICarrier.hxx"
FGAICarrier::FGAICarrier() : FGAIShip(otCarrier), deck_altitude(65.0065) {
FGAICarrier::FGAICarrier() :
FGAIShip(otCarrier),
deck_altitude(65.0065)
{
}
FGAICarrier::~FGAICarrier() {
}
FGAICarrier::~FGAICarrier() = default;
void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
if (!scFileNode)
@ -67,11 +67,14 @@ void FGAICarrier::readFromScenario(SGPropertyNode* scFileNode) {
// Transform to the right coordinate frame, configuration is done in
// the usual x-back, y-right, z-up coordinates, computations
// in the simulation usual body x-forward, y-right, z-down coordinates
flols_off(0) = - flols->getDoubleValue("x-offset-m", 0);
flols_off(1) = flols->getDoubleValue("y-offset-m", 0);
flols_off(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsPosOffset(0) = - flols->getDoubleValue("x-offset-m", 0);
_flolsPosOffset(1) = flols->getDoubleValue("y-offset-m", 0);
_flolsPosOffset(2) = - flols->getDoubleValue("z-offset-m", 0);
_flolsHeadingOffsetDeg = flols->getDoubleValue("heading-offset-deg", 0.0);
_flolsApproachAngle = flols->getDoubleValue("glidepath-angle-deg", 3.0);
} else
flols_off = SGVec3d::zeros();
_flolsPosOffset = SGVec3d::zeros();
std::vector<SGPropertyNode_ptr> props = scFileNode->getChildren("parking-pos");
std::vector<SGPropertyNode_ptr>::const_iterator it;
@ -180,13 +183,13 @@ void FGAICarrier::update(double dt) {
// rotate the eyepoint wrt carrier vector into the carriers frame
eyeWrtCarrier = ec2body.transform(eyeWrtCarrier);
// the eyepoints vector wrt the flols position
SGVec3d eyeWrtFlols = eyeWrtCarrier - flols_off;
SGVec3d eyeWrtFlols = eyeWrtCarrier - _flolsPosOffset;
// the distance from the eyepoint to the flols
dist = norm(eyeWrtFlols);
// now the angle, positive angles are upwards
if (fabs(dist) < SGLimits<float>::min()) {
if (fabs(dist) < SGLimits<double>::min()) {
angle = 0;
} else {
double sAngle = -eyeWrtFlols(2)/dist;
@ -327,11 +330,9 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
{
// FIXME: does not yet cover rotation speeds.
list<ParkPosition>::iterator it = ppositions.begin();
while (it != ppositions.end()) {
for (const auto& ppos : ppositions) {
// Take either the specified one or the first one ...
if ((*it).name == id || id.empty()) {
ParkPosition ppos = *it;
if (ppos.name == id || id.empty()) {
SGVec3d cartPos = getCartPosAt(ppos.offset);
geodPos = SGGeod::fromCart(cartPos);
hdng = hdg + ppos.heading_deg;
@ -341,12 +342,28 @@ bool FGAICarrier::getParkPosition(const string& id, SGGeod& geodPos,
uvw = SGVec3d(chdng*speed_fps, shdng*speed_fps, 0);
return true;
}
++it;
}
return false;
}
bool FGAICarrier::getFLOLSPositionHeading(SGGeod& geodPos, double& heading) const
{
SGVec3d cartPos = getCartPosAt(_flolsPosOffset);
geodPos = SGGeod::fromCart(cartPos);
// at present we don't support a heading offset for the FLOLS, so
// heading is just the carrier heading
heading = hdg + _flolsHeadingOffsetDeg;
return true;
}
double FGAICarrier::getFLOLFSGlidepathAngleDeg() const
{
return _flolsApproachAngle;
}
// find relative wind
void FGAICarrier::UpdateWind( double dt) {
@ -371,8 +388,7 @@ void FGAICarrier::UpdateWind( double dt) {
+ (rel_wind_speed_from_north_kts * rel_wind_speed_from_north_kts));
//calculate the relative wind direction
rel_wind_from_deg = atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts)
* SG_RADIANS_TO_DEGREES;
rel_wind_from_deg = SGMiscd::rad2deg(atan2(rel_wind_speed_from_east_kts, rel_wind_speed_from_north_kts));
//calculate rel wind
rel_wind = rel_wind_from_deg - hdg;
@ -640,7 +656,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
return {};
}
for (const auto aiObject : aiManager->get_ai_list()) {
for (const auto& aiObject : aiManager->get_ai_list()) {
if (aiObject->isa(FGAIBase::otCarrier)) {
SGSharedPtr<FGAICarrier> c = static_cast<FGAICarrier*>(aiObject.get());
if ((c->sign == namePennant) || (c->_getName() == namePennant)) {
@ -652,7 +668,7 @@ SGSharedPtr<FGAICarrier> FGAICarrier::findCarrierByNameOrPennant(const std::stri
return {};
}
void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
void FGAICarrier::extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario)
{
for (auto c : xmlNode->getChildren("entry")) {
if (c->getStringValue("type") != std::string("carrier"))
@ -673,6 +689,14 @@ void FGAICarrier::extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, S
// the find code above just looks for anything called a name (so alias
// are possible, for example)
if (!name.empty()) carrierNode->addChild("name")->setStringValue(name);
if (!pennant.empty()) carrierNode->addChild("name")->setStringValue(pennant);
if (!pennant.empty()) {
carrierNode->addChild("name")->setStringValue(pennant);
carrierNode->addChild("pennant-number")->setStringValue(pennant);
}
// extact parkings
for (auto p : c->getChildren("parking-pos")) {
carrierNode->addChild("parking-pos")->setStringValue(p->getStringValue("name"));
}
}
}

View file

@ -86,8 +86,11 @@ public:
* This is used to support 'start on a carrier', since we can quickly find
* the corresponding scenario file to be loaded.
*/
static void extractNamesPennantsFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
static void extractCarriersFromScenario(SGPropertyNode_ptr xmlNode, SGPropertyNode_ptr scenario);
bool getFLOLSPositionHeading(SGGeod &pos, double &heading) const;
double getFLOLFSGlidepathAngleDeg() const;
private:
/// Is sufficient to be private, stores a possible position to place an
/// aircraft on start
@ -115,8 +118,10 @@ private:
list<ParkPosition> ppositions; // List of positions where an aircraft can start.
string sign; // The sign of this carrier.
// these describe the flols
SGVec3d flols_off;
// these describe the flols
SGVec3d _flolsPosOffset;
double _flolsHeadingOffsetDeg = 0.0; ///< angle in degrees offset from the carrier centerline
double _flolsApproachAngle = 3.0; ///< glidepath angle for the FLOLS
double dist; // the distance of the eyepoint from the flols
double angle;

View file

@ -71,11 +71,11 @@ public:
_unloadScript = nasalScripts->getStringValue("unload");
std::string loadScript = nasalScripts->getStringValue("load");
if (!loadScript.empty()) {
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
std::string moduleName = "scenario_" + _internalName;
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
loadScript.c_str(), loadScript.size(),
0);
nullptr);
}
}
@ -85,7 +85,7 @@ public:
[](FGAIBasePtr ai) { ai->setDie(true); });
FGNasalSys* nasalSys = (FGNasalSys*) globals->get_subsystem("nasal");
FGNasalSys* nasalSys = globals->get_subsystem<FGNasalSys>();
if (!nasalSys)
return;
@ -93,7 +93,7 @@ public:
if (!_unloadScript.empty()) {
nasalSys->createModule(moduleName.c_str(), moduleName.c_str(),
_unloadScript.c_str(), _unloadScript.size(),
0);
nullptr);
}
nasalSys->deleteModule(moduleName.c_str());
@ -165,16 +165,19 @@ FGAIManager::init() {
registerScenarios();
}
void FGAIManager::registerScenarios()
void FGAIManager::registerScenarios(SGPropertyNode_ptr root)
{
// depending on if we're using a carrier startup, this function may get
// called early or during normal FGAIManager init, so guard against double
// invocation.
// we clear this flag on shudtdown so reset works as expected
if (static_haveRegisteredScenarios)
return;
static_haveRegisteredScenarios = true;
if (!root) {
// depending on if we're using a carrier startup, this function may get
// called early or during normal FGAIManager init, so guard against double
// invocation.
// we clear this flag on shudtdown so reset works as expected
if (static_haveRegisteredScenarios)
return;
static_haveRegisteredScenarios = true;
root = globals->get_props();
}
// find all scenarios at standard locations (for driving the GUI)
std::vector<SGPath> scenarioSearchPaths;
@ -184,27 +187,29 @@ void FGAIManager::registerScenarios()
// add-on scenario directories
const auto& addonsManager = flightgear::addons::AddonManager::instance();
for (auto a : addonsManager->registeredAddons()) {
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
if (addonsManager) {
for (auto a : addonsManager->registeredAddons()) {
scenarioSearchPaths.push_back(a->getBasePath() / "Scenarios");
}
}
SGPropertyNode_ptr scenariosNode = fgGetNode("/sim/ai/scenarios", true);
SGPropertyNode_ptr scenariosNode = root->getNode("/sim/ai/scenarios", true);
for (auto p : scenarioSearchPaths) {
if (!p.exists())
continue;
simgear::Dir dir(p);
for (auto xmlPath : dir.children(simgear::Dir::TYPE_FILE, ".xml")) {
registerScenarioFile(xmlPath);
registerScenarioFile(root, xmlPath);
} // of xml files in the scenario dir iteration
} // of scenario dirs iteration
}
SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath)
SGPropertyNode_ptr FGAIManager::registerScenarioFile(SGPropertyNode_ptr root, const SGPath& xmlPath)
{
if (!xmlPath.exists()) return {};
auto scenariosNode = fgGetNode("/sim/ai/scenarios", true);
auto scenariosNode = root->getNode("/sim/ai/scenarios", true);
SGPropertyNode_ptr sNode;
try {
@ -235,7 +240,7 @@ SGPropertyNode_ptr FGAIManager::registerScenarioFile(const SGPath& xmlPath)
sNode->setStringValue("description", xs->getStringValue("description"));
}
FGAICarrier::extractNamesPennantsFromScenario(xs, sNode);
FGAICarrier::extractCarriersFromScenario(xs, sNode);
} // of scenarios in the XML file
} catch (std::exception&) {
SG_LOG(SG_AI, SG_WARN, "Skipping malformed scenario file:" << xmlPath);
@ -367,7 +372,7 @@ FGAIManager::update(double dt)
for (FGAIBase* base : ai_list) {
try {
if (base->isa(FGAIBase::otThermal)) {
processThermal(dt, (FGAIThermal*)base);
processThermal(dt, static_cast<FGAIThermal*>(base));
} else {
base->update(dt);
}
@ -428,7 +433,7 @@ bool FGAIManager::isVisible(const SGGeod& pos) const
int
FGAIManager::getNumAiObjects() const
{
return ai_list.size();
return static_cast<int>(ai_list.size());
}
void
@ -489,12 +494,14 @@ bool FGAIManager::loadScenarioCommand(const SGPropertyNode* args, SGPropertyNode
bool FGAIManager::unloadScenarioCommand(const SGPropertyNode * arg, SGPropertyNode * root)
{
SG_UNUSED(root);
std::string name = arg->getStringValue("name");
return unloadScenario(name);
}
bool FGAIManager::addObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{
SG_UNUSED(root);
if (!arg){
return false;
}
@ -506,7 +513,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
{
const std::string& type = definition->getStringValue("type", "aircraft");
FGAIBase* ai = NULL;
FGAIBase* ai = nullptr;
if (type == "tanker") { // refueling scenarios
ai = new FGAITanker;
} else if (type == "wingman") {
@ -545,6 +552,7 @@ FGAIBasePtr FGAIManager::addObject(const SGPropertyNode* definition)
bool FGAIManager::removeObjectCommand(const SGPropertyNode* arg, const SGPropertyNode* root)
{
SG_UNUSED(root);
if (!arg) {
return false;
}
@ -703,7 +711,7 @@ FGAIManager::calcCollision(double alt, double lat, double lon, double fuse_range
}
++ai_list_itr;
}
return 0;
return nullptr;
}
double

View file

@ -73,8 +73,8 @@ public:
* we need carrier scenarios to start the position-init process for a
* carrier start.
*/
static void registerScenarios();
static SGPropertyNode_ptr registerScenarioFile(const SGPath& p);
static void registerScenarios(SGPropertyNode_ptr root = {});
static SGPropertyNode_ptr registerScenarioFile(SGPropertyNode_ptr root, const SGPath& p);
static SGPropertyNode_ptr loadScenarioFile(const std::string& id);
FGAIBasePtr addObject(const SGPropertyNode* definition);

View file

@ -126,6 +126,8 @@ if (HAVE_QT)
PixmapImageItem.hxx
PathListModel.cxx
PathListModel.hxx
CarriersLocationModel.cxx
CarriersLocationModel.hxx
${uic_sources}
${qrc_sources}
${qml_sources})

View file

@ -0,0 +1,132 @@
#include "CarriersLocationModel.hxx"
#include <algorithm>
#include <QPixmap>
#include "AIModel/AIManager.hxx"
CarriersLocationModel::CarriersLocationModel(QObject *parent)
: QAbstractListModel(parent)
{
SGPropertyNode_ptr localRoot(new SGPropertyNode);
FGAIManager::registerScenarios(localRoot);
// this code encodes some scenario structre, sorry
for (auto s : localRoot->getNode("sim/ai/scenarios")->getChildren("scenario")) {
const std::string scenarioId = s->getStringValue("id");
for (auto c : s->getChildren("carrier")) {
processCarrier(scenarioId, c);
}
}
}
void CarriersLocationModel::processCarrier(const string &scenario, SGPropertyNode_ptr carrierNode)
{
const auto name = QString::fromStdString(carrierNode->getStringValue("name"));
const auto pennant = QString::fromStdString(carrierNode->getStringValue("pennant-number"));
const auto tacan = QString::fromStdString(carrierNode->getStringValue("TACAN-channel-ID"));
SGGeod geod = SGGeod::fromDeg(carrierNode->getDoubleValue("longitude"),
carrierNode->getDoubleValue("latitude"));
QStringList parkings;
for (auto c : carrierNode->getChildren("parking-pos")) {
parkings.append(QString::fromStdString(c->getStringValue()));
}
mCarriers.push_back(Carrier{
QString::fromStdString(scenario),
pennant,
name,
geod,
tacan,
parkings
});
}
int CarriersLocationModel::rowCount(const QModelIndex &parent) const
{
// For list models only the root node (an invalid parent) should return the list's size. For all
// other (valid) parents, rowCount() should return 0 so that it does not become a tree model.
if (parent.isValid())
return 0;
return static_cast<int>(mCarriers.size());
}
QVariant CarriersLocationModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
const auto& c = mCarriers.at(static_cast<size_t>(index.row()));
switch (role) {
case Qt::DisplayRole:
case NameRole: return c.mName;
// case GeodRole: return QVariant::fromValue(c.mInitialLocation);
case IdentRole: return c.mCallsign;
case IconRole: return QPixmap(":/svg/aircraft-carrier");
default:
break;
}
return {};
}
QHash<int, QByteArray> CarriersLocationModel::roleNames() const
{
QHash<int, QByteArray> result = QAbstractListModel::roleNames();
result[GeodRole] = "geod";
result[GuidRole] = "guid";
result[IdentRole] = "ident";
result[NameRole] = "name";
result[IconRole] = "icon";
result[TypeRole] = "type";
result[NavFrequencyRole] = "frequency";
return result;
}
int CarriersLocationModel::indexOf(const QString name) const
{
auto it = std::find_if(mCarriers.begin(), mCarriers.end(), [name]
(const Carrier& carrier)
{ return name == carrier.mName || name == carrier.mCallsign; });
if (it == mCarriers.end())
return -1;
return static_cast<int>(std::distance(mCarriers.begin(), it));
}
SGGeod CarriersLocationModel::geodForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mInitialLocation;
}
QString CarriersLocationModel::pennantForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mCallsign;
}
QStringList CarriersLocationModel::parkingsForIndex(int index) const
{
const auto uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= mCarriers.size())) {
return {};
}
const auto& c = mCarriers.at(uIndex);
return c.mParkings;
}

View file

@ -0,0 +1,61 @@
#ifndef CARRIERSMODEL_H
#define CARRIERSMODEL_H
#include <vector>
#include <simgear/math/sg_geodesy.hxx>
#include <simgear/props/props.hxx>
#include <QAbstractListModel>
class CarriersLocationModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit CarriersLocationModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
// copied from NavaidSearchModel
enum Roles {
GeodRole = Qt::UserRole + 1,
GuidRole = Qt::UserRole + 2,
IdentRole = Qt::UserRole + 3,
NameRole = Qt::UserRole + 4,
IconRole = Qt::UserRole + 5,
TypeRole = Qt::UserRole + 6,
NavFrequencyRole = Qt::UserRole + 7
};
int indexOf(const QString name) const;
SGGeod geodForIndex(int index) const;
QString pennantForIndex(int index) const;
QStringList parkingsForIndex(int index) const;
private:
struct Carrier
{
QString mScenario; // scenario ID for loading
QString mCallsign; // pennant-number
QString mName;
SGGeod mInitialLocation;
// icon?
QString mTACAN;
QStringList mParkings;
};
using CarrierVec = std::vector<Carrier>;
CarrierVec mCarriers;
void processCarrier(const std::string& scenario, SGPropertyNode_ptr carrierNode);
};
#endif // CARRIERSMODEL_H

View file

@ -52,6 +52,7 @@
#include "NavaidSearchModel.hxx"
#include "FlightPlanController.hxx"
#include "ModelDataExtractor.hxx"
#include "CarriersLocationModel.hxx"
using namespace simgear::pkg;
@ -142,6 +143,7 @@ void LauncherController::initQML()
qmlRegisterUncreatableType<MPServersModel>("FlightGear.Launcher", 1, 0, "MPServers", "Singleton API");
qmlRegisterType<NavaidSearchModel>("FlightGear", 1, 0, "NavaidSearch");
qmlRegisterType<CarriersLocationModel>("FlightGear", 1, 0, "CarriersModel");
qmlRegisterUncreatableType<Units>("FlightGear", 1, 0, "Units", "Only for enum");
qmlRegisterType<UnitsModel>("FlightGear", 1, 0, "UnitsModel");

View file

@ -35,6 +35,7 @@
#include "LaunchConfig.hxx"
#include "DefaultAircraftLocator.hxx"
#include "NavaidSearchModel.hxx"
#include "CarriersLocationModel.hxx"
#include <Airports/airport.hxx>
#include <Airports/groundnetwork.hxx>
@ -53,10 +54,8 @@ const unsigned int MAX_RECENT_LOCATIONS = 64;
QVariant savePositionList(const FGPositionedList& posList)
{
QVariantList vl;
FGPositionedList::const_iterator it;
for (it = posList.begin(); it != posList.end(); ++it) {
for (const auto& pos : posList) {
QVariantMap vm;
FGPositionedRef pos = *it;
vm.insert("ident", QString::fromStdString(pos->ident()));
vm.insert("type", pos->type());
vm.insert("lat", pos->geod().getLatitudeDeg());
@ -70,7 +69,7 @@ FGPositionedList loadPositionedList(QVariant v)
{
QVariantList vl = v.toList();
FGPositionedList result;
result.reserve(vl.size());
result.reserve(static_cast<size_t>(vl.size()));
NavDataCache* cache = NavDataCache::instance();
Q_FOREACH(QVariant v, vl) {
@ -93,9 +92,10 @@ FGPositionedList loadPositionedList(QVariant v)
LocationController::LocationController(QObject *parent) :
QObject(parent)
{
m_searchModel = new NavaidSearchModel;
m_searchModel = new NavaidSearchModel(this);
m_detailQml = new QmlPositioned(this);
m_baseQml = new QmlPositioned(this);
m_carriersModel = new CarriersLocationModel(this);
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
m_defaultAirspeed = QuantityValue{Units::Knots, 120};
@ -112,9 +112,7 @@ LocationController::LocationController(QObject *parent) :
this, &LocationController::descriptionChanged);
}
LocationController::~LocationController()
{
}
LocationController::~LocationController() = default;
void LocationController::setLaunchConfig(LaunchConfig *config)
{
@ -196,35 +194,62 @@ void LocationController::setBaseGeod(QmlGeod geod)
if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
return;
clearLocation();
m_locationIsLatLon = true;
m_geodLocation = geod.geod();
emit baseLocationChanged();
}
QString LocationController::carrierName() const
{
return m_carrierName;
}
void LocationController::setCarrierLocation(QString name)
{
const auto cIndex = m_carriersModel->indexOf(name);
clearLocation();
if (cIndex < 0) {
qWarning() << "invalid carrier name:" << name;
return;
}
m_locationIsCarrier = true;
m_carrierName = name;
m_geodLocation = m_carriersModel->geodForIndex(cIndex);
m_carrierParkings = m_carriersModel->parkingsForIndex(cIndex);
emit baseLocationChanged();
}
void LocationController::clearLocation()
{
m_locationIsLatLon = false;
m_locationIsCarrier = false;
m_location.clear();
m_carrierName.clear();
m_airportLocation.clear();
m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_carrierParkings.clear();
m_carrierParking.clear();
emit baseLocationChanged();
}
void LocationController::setBaseLocation(QmlPositioned* pos)
{
if (!pos) {
m_location.clear();
m_detailLocation.clear();
m_detailQml->setGuid(0);
m_baseQml->setGuid(0);
m_airportLocation.clear();
m_locationIsLatLon = false;
emit baseLocationChanged();
clearLocation();
return;
}
if (pos->inner() == m_location)
return;
m_locationIsLatLon = false;
clearLocation();
m_location = pos->inner();
m_baseQml->setGuid(pos->guid());
m_detailLocation.clear();
m_detailQml->setGuid(0);
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
@ -249,7 +274,6 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
m_detailLocation.clear();
m_detailQml->setInner({});
} else {
qInfo() << Q_FUNC_INFO << "pos:" << pos->ident();
m_detailLocation = pos->inner();
m_useActiveRunway = false;
m_useAvailableParking = false;
@ -261,7 +285,7 @@ void LocationController::setDetailLocation(QmlPositioned* pos)
QmlGeod LocationController::baseGeod() const
{
if (m_locationIsLatLon)
if (m_locationIsLatLon || m_locationIsCarrier)
return m_geodLocation;
if (m_location)
@ -368,6 +392,33 @@ QmlGeod LocationController::parseStringAsGeod(QString string) const
return QmlGeod(g);
}
QString LocationController::carrierParking() const
{
if (!m_locationIsCarrier)
return {};
return m_carrierParking;
}
void LocationController::setCarrierParking(QString name)
{
if (!m_locationIsCarrier) {
qWarning() << "active location is not a carrier";
return;
}
if (m_carrierParking == name)
return;
if (!m_carrierParkings.contains(name)) {
qWarning() << "parking '" << name << "' not found in carrier parking list";
return;
}
m_carrierParking = name;
m_useCarrierFLOLS = false;
emit configChanged();
}
QmlPositioned *LocationController::detail() const
{
return m_detailQml;
@ -378,6 +429,11 @@ QmlPositioned *LocationController::baseLocation() const
return m_baseQml;
}
QStringList LocationController::carrierParkings() const
{
return m_carrierParkings;
}
void LocationController::setOffsetRadial(QuantityValue offsetRadial)
{
if (m_offsetRadial == offsetRadial)
@ -436,27 +492,26 @@ void LocationController::setUseAvailableParking(bool useAvailableParking)
emit configChanged();
}
void LocationController::setUseCarrierFLOLS(bool useCarrierFLOLS)
{
if (!m_locationIsCarrier) {
qWarning() << "location is not a carrier";
return;
}
if (m_useCarrierFLOLS == useCarrierFLOLS)
return;
m_useCarrierFLOLS = useCarrierFLOLS;
m_carrierParking.clear();
emit configChanged();
}
void LocationController::restoreLocation(QVariantMap l)
{
try {
if (l.contains("location-lat")) {
m_locationIsLatLon = true;
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
l.value("location-lat").toDouble());
m_location.clear();
m_airportLocation.clear();
m_baseQml->setInner(nullptr);
} else if (l.contains("location-id")) {
m_location = NavDataCache::instance()->loadById(l.value("location-id").toULongLong());
m_locationIsLatLon = false;
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
} else {
m_airportLocation.clear();
}
m_baseQml->setInner(m_location);
}
clearLocation();
try {
m_altitudeEnabled = l.contains("altitude");
m_speedEnabled = l.contains("speed");
m_headingEnabled = l.contains("heading");
@ -470,10 +525,33 @@ void LocationController::restoreLocation(QVariantMap l)
m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
if (l.contains("location-lat")) {
m_locationIsLatLon = true;
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
l.value("location-lat").toDouble());
} else if (l.contains("carrier")) {
setCarrierLocation(l.value("carrier").toString());
if (l.contains("carrier-flols")) {
setUseCarrierFLOLS(l.value("carrier-flols").toBool());
// overwrite value form above, intentionally
m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
} else if (l.contains("carrier-parking")) {
setCarrierParking(l.value("carrier-parking").toString());
}
} else if (l.contains("location-id")) {
m_location = NavDataCache::instance()->loadById(l.value("location-id").toLongLong());
m_locationIsLatLon = false;
if (FGPositioned::isAirportType(m_location.ptr())) {
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
} else {
m_airportLocation.clear();
}
m_baseQml->setInner(m_location);
}
if (m_airportLocation) {
m_useActiveRunway = false;
m_useAvailableParking = false;
m_detailLocation.clear();
if (l.contains("location-apt-runway")) {
QString runway = l.value("location-apt-runway").toString().toUpper();
@ -502,10 +580,7 @@ void LocationController::restoreLocation(QVariantMap l)
} // of location is an airport
} catch (const sg_exception&) {
qWarning() << "Errors restoring saved location, clearing";
m_location.clear();
m_airportLocation.clear();
m_baseQml->setInner(nullptr);
m_offsetEnabled = false;
clearLocation();
}
baseLocationChanged();
@ -515,6 +590,10 @@ void LocationController::restoreLocation(QVariantMap l)
bool LocationController::shouldStartPaused() const
{
if (m_useCarrierFLOLS) {
return true;
}
if (!m_location) {
return false; // defaults to on-ground at the default airport
}
@ -535,6 +614,14 @@ QVariantMap LocationController::saveLocation() const
if (m_locationIsLatLon) {
locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg());
} else if (m_locationIsCarrier) {
locationSet.insert("carrier", m_carrierName);
if (m_useCarrierFLOLS) {
locationSet.insert("carrier-flols", true);
locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
} else if (!m_carrierParking.isEmpty()) {
locationSet.insert("carrier-parking", m_carrierParking);
}
} else if (m_location) {
locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid()));
@ -588,7 +675,7 @@ void LocationController::setLocationProperties()
"runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
"offset-distance-nm" << "glideslope-deg" <<
"speed-set" << "on-ground" << "airspeed-kt" <<
"airport-id" << "runway" << "parkpos";
"airport-id" << "runway" << "parkpos" << "carrier";
Q_FOREACH(QString s, props) {
SGPropertyNode* c = presets->getChild(s.toStdString());
@ -604,6 +691,8 @@ void LocationController::setLocationProperties()
fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
applyPositionOffset();
applyAltitude();
applyAirspeed();
return;
}
@ -612,6 +701,27 @@ void LocationController::setLocationProperties()
fgSetDouble("/sim/presets/altitude-ft", -9999.0);
fgSetDouble("/sim/presets/heading-deg", 9999.0);
if (m_locationIsCarrier) {
fgSetString("/sim/presets/carrier", m_carrierName.toStdString());
if (m_useCarrierFLOLS) {
fgSetBool("/sim/presets/runway-requested", true );
// treat the FLOLS as a runway, for the purposes of communication with position-init
fgSetString("/sim/presets/runway", "FLOLS");
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
applyAirspeed();
} else if (!m_carrierParking.isEmpty()) {
fgSetString("/sim/presets/parkpos", m_carrierParking.toStdString());
}
if (m_tuneNAV1) {
// tune TACAN to the carrier
qInfo() << "Implement TACAN tuning";
}
return;
}
if (!m_location) {
return;
}
@ -681,7 +791,7 @@ void LocationController::setLocationProperties()
break;
default:
break;
};
}
// set disambiguation property
globals->get_props()->setIntValue("/sim/presets/navaid-id",
@ -796,6 +906,21 @@ void LocationController::onCollectConfig()
return;
}
if (m_locationIsCarrier) {
m_config->setArg("carrier", m_carrierName);
if (!m_carrierParking.isEmpty()) {
m_config->setArg("parkpos", m_carrierParking);
} else if (m_useCarrierFLOLS) {
m_config->setArg("runway", QStringLiteral("FLOLS"));
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
m_config->setArg("offset-distance", QString::number(offsetNm));
applyAirspeed();
}
return;
}
if (!m_location) {
return;
}
@ -852,7 +977,7 @@ void LocationController::onCollectConfig()
break;
default:
break;
};
}
// set disambiguation property
m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
@ -912,6 +1037,11 @@ QString LocationController::description() const
return tr("at position %1").arg(QString::fromStdString(s));
}
if (m_locationIsCarrier) {
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
return tr("on carrier %1 (%2)").arg(m_carrierName).arg(pennant);
}
return tr("No location selected");
}
@ -952,7 +1082,7 @@ QString LocationController::description() const
if (m_offsetEnabled) {
offsetDesc = tr("%1nm %2 of").
arg(offsetNm, 0, 'f', 1).
arg(compassPointFromHeading(m_offsetRadial.value));
arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
}
QString navaidType;

View file

@ -32,6 +32,7 @@
#include "UnitsModel.hxx"
class NavaidSearchModel;
class CarriersLocationModel;
class LocationController : public QObject
{
@ -40,6 +41,7 @@ class LocationController : public QObject
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
Q_PROPERTY(NavaidSearchModel* searchModel MEMBER m_searchModel CONSTANT)
Q_PROPERTY(CarriersLocationModel* carriersModel MEMBER m_carriersModel CONSTANT)
Q_PROPERTY(QList<QObject*> airportRunways READ airportRunways NOTIFY baseLocationChanged)
Q_PROPERTY(QList<QObject*> airportParkings READ airportParkings NOTIFY baseLocationChanged)
@ -68,6 +70,13 @@ class LocationController : public QObject
Q_PROPERTY(QmlPositioned* detail READ detail CONSTANT)
Q_PROPERTY(bool isBaseLatLon READ isBaseLatLon NOTIFY baseLocationChanged)
Q_PROPERTY(bool isCarrier READ isCarrier NOTIFY baseLocationChanged)
Q_PROPERTY(QString carrier READ carrierName WRITE setCarrierLocation NOTIFY baseLocationChanged)
Q_PROPERTY(QStringList carrierParkings READ carrierParkings NOTIFY baseLocationChanged)
Q_PROPERTY(bool useCarrierFLOLS READ useCarrierFLOLS WRITE setUseCarrierFLOLS NOTIFY configChanged)
Q_PROPERTY(QString carrierParking READ carrierParking WRITE setCarrierParking NOTIFY configChanged)
// allow collecting the location properties to be disabled, if the
// user is setting conflicting ones
Q_PROPERTY(bool skipFromArgs MEMBER m_skipFromArgs NOTIFY skipFromArgsChanged)
@ -110,6 +119,9 @@ public:
QmlGeod baseGeod() const;
void setBaseGeod(QmlGeod geod);
QString carrierName() const;
void setCarrierLocation(QString name);
bool isAirportLocation() const;
bool offsetEnabled() const
@ -138,6 +150,9 @@ public:
Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const;
QString carrierParking() const;
void setCarrierParking(QString name);
bool tuneNAV1() const
{
return m_tuneNAV1;
@ -161,6 +176,19 @@ public:
{
return m_altitude;
}
bool isCarrier() const
{
return m_locationIsCarrier;
}
QStringList carrierParkings() const;
bool useCarrierFLOLS() const
{
return m_useCarrierFLOLS;
}
public slots:
void setOffsetRadial(QuantityValue offsetRadial);
@ -174,6 +202,8 @@ public slots:
void setUseAvailableParking(bool useAvailableParking);
void setUseCarrierFLOLS(bool useCarrierFLOLS);
Q_SIGNALS:
void descriptionChanged();
void offsetChanged();
@ -186,6 +216,7 @@ private Q_SLOTS:
void onRestoreCurrentLocation();
void onSaveCurrentLocation();
private:
void clearLocation();
void onSearchComplete();
void addToRecent(FGPositionedRef pos);
@ -197,12 +228,15 @@ private:
void applyOnFinal();
NavaidSearchModel* m_searchModel = nullptr;
CarriersLocationModel* m_carriersModel = nullptr;
FGPositionedRef m_location;
FGAirportRef m_airportLocation; // valid if m_location is an FGAirport
FGPositionedRef m_detailLocation; // parking stand or runway detail
bool m_locationIsLatLon = false;
SGGeod m_geodLocation;
bool m_locationIsCarrier = false;
QString m_carrierName;
FGPositionedList m_recentLocations;
LaunchConfig* m_config = nullptr;
@ -227,6 +261,10 @@ private:
bool m_speedEnabled = false;
bool m_altitudeEnabled = false;
bool m_skipFromArgs = false;
bool m_useCarrierFLOLS = false;
QString m_carrierParking;
QStringList m_carrierParkings;
};
#endif // LOCATION_CONTROLLER_HXX

View file

@ -24,35 +24,49 @@ QVariant ModelDataExtractor::data() const
return m_model->data(m, role);
}
if (m_value.isArray()) {
if (!m_stringsModel.empty()) {
if ((m_index < 0) || (m_index >= m_stringsModel.size()))
return {};
return m_stringsModel.at(m_index);
}
if (m_rawModel.isArray()) {
quint32 uIndex = static_cast<quint32>(m_index);
auto v = m_value.property(uIndex);
auto v = m_rawModel.property(uIndex);
if (v.isQObject()) {
// handle the QList<QObject*> case
auto obj = v.toQObject();
return obj->property(m_role.toUtf8().constData());
}
return m_value.property(uIndex).toVariant();
return m_rawModel.property(uIndex).toVariant();
}
qWarning() << "Unable to convert model data:" << m_rawModel.toString();
return {};
}
void ModelDataExtractor::setModel(QJSValue model)
void ModelDataExtractor::clear()
{
if (m_value.equals(model))
return;
if (m_model) {
// disconnect from everything
disconnect(m_model, nullptr, this, nullptr);
m_model = nullptr;
}
m_value = model;
if (m_value.isQObject()) {
m_model = qobject_cast<QAbstractItemModel*>(m_value.toQObject());
}
void ModelDataExtractor::setModel(QJSValue raw)
{
if (m_rawModel.strictlyEquals(raw))
return;
clear();
m_rawModel = raw;
if (raw.isQObject()) {
m_model = qobject_cast<QAbstractItemModel*>(raw.toQObject());
if (m_model) {
connect(m_model, &QAbstractItemModel::modelReset,
this, &ModelDataExtractor::dataChanged);
@ -61,10 +75,24 @@ void ModelDataExtractor::setModel(QJSValue model)
// ToDo: handle rows added / removed
} else {
qWarning() << "object but not a QAIM" << m_value.toQObject();
qWarning() << "object but not a QAIM" << raw.toQObject();
}
} else if (raw.isArray()) {
} else if (raw.isVariant() || raw.isObject()) {
// special case the QStringList case
// for reasons I don't understand yet, QStringList returned as a
// property value to JS, does not show up as a variant-in-JS-Value above
// (so ::isVariant returns false), but conversion to a variant
// works. Hence the 'raw.isObject' above
const auto var = raw.toVariant();
if (var.type() == QVariant::StringList) {
m_stringsModel = var.toStringList();
} else {
qWarning() << Q_FUNC_INFO << "variant but not a QStringList" << var;
}
} else {
// might be null, or an array
}
emit modelChanged();

View file

@ -20,7 +20,7 @@ public:
QJSValue model() const
{
return m_value;
return m_rawModel;
}
int index() const
@ -52,8 +52,12 @@ private slots:
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
void clear();
QAbstractItemModel* m_model = nullptr;
QJSValue m_value;
QJSValue m_rawModel;
QStringList m_stringsModel;
int m_index = 0;
QString m_role;
};

View file

@ -153,10 +153,17 @@ void NavaidSearchModel::clear()
qlonglong NavaidSearchModel::guidAtIndex(int index) const
{
if ((index < 0) || (index >= m_ids.size()))
const size_t uIndex = static_cast<size_t>(index);
if ((index < 0) || (uIndex >= m_ids.size()))
return 0;
return m_ids.at(index);
return m_ids.at(uIndex);
}
NavaidSearchModel::NavaidSearchModel(QObject *parent) :
QAbstractListModel(parent)
{
}
void NavaidSearchModel::setSearch(QString t, NavaidSearchModel::AircraftType aircraft)

View file

@ -51,7 +51,7 @@ class NavaidSearchModel : public QAbstractListModel
};
public:
NavaidSearchModel() { }
NavaidSearchModel(QObject* parent = nullptr);
enum AircraftType
{

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

View file

@ -0,0 +1,30 @@
import QtQuick 2.0
import "."
Rectangle {
id: root
radius: Style.roundRadius
border.width: 1
border.color: Style.themeColor
width: height
height: Style.baseFontPixelSize + Style.margin * 2
color: mouse.containsMouse ? Style.minorFrameColor : "white"
property alias icon: icon.source
signal clicked();
Image {
id: icon
width: parent.width - Style.margin
height: parent.height - Style.margin
anchors.centerIn: parent
}
MouseArea {
id: mouse
hoverEnabled: true
onClicked: root.clicked();
anchors.fill: parent
}
}

View file

@ -9,6 +9,9 @@ Item {
property bool __searchActive: false
property string lastSearch
property bool showCarriers: false
readonly property var locationModel: showCarriers ? _location.carriersModel : _location.searchModel
function backToSearch()
{
detailLoader.sourceComponent = null
@ -33,6 +36,13 @@ Item {
}
}
function selectCarrier(name)
{
selectedLocation.guid = 0;
_location.carrier = name;
detailLoader.sourceComponent = carrierDetails
}
Component.onCompleted: {
// important so we can leave the location page and return to it,
// preserving the state
@ -46,6 +56,8 @@ Item {
}
} else if (_location.isBaseLatLon) {
detailLoader.sourceComponent = navaidDetails
} else if (_location.isCarrier) {
detailLoader.sourceComponent = carrierDetails;
} else {
_location.showHistoryInSearchModel();
}
@ -69,6 +81,13 @@ Item {
}
}
Component {
id: carrierDetails
LocationCarrierView {
id: carrierView
}
}
Rectangle {
anchors.fill: parent
color: "white"
@ -129,7 +148,11 @@ Item {
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.selectLocation(model.guid, model.type);
if (root.showCarriers) {
root.selectCarrier(model.name);
} else {
root.selectLocation(model.guid, model.type);
}
}
}
}
@ -160,10 +183,12 @@ Item {
anchors.topMargin: Style.margin
}
SearchButton {
id: searchButton
anchors.right: parent.right
anchors.right: carriersButton.left
anchors.top: headerText.bottom
anchors.left: parent.left
anchors.margins: Style.margin
@ -172,7 +197,8 @@ Item {
placeholder: qsTr("Search for an airport or navaid");
onSearch: {
// when th search term is cleared, show the history
root.showCarriers = false;
// when the search term is cleared, show the history
if (term == "") {
_location.showHistoryInSearchModel();
return;
@ -191,6 +217,18 @@ Item {
}
}
IconButton {
id: carriersButton
anchors.top: headerText.bottom
anchors.right: parent.right
anchors.margins: Style.margin
icon: "qrc:///svg/icon-carrier"
onClicked: {
root.showCarriers = true;
}
}
StyledText {
id: searchHelpText
anchors.right: parent.right
@ -220,12 +258,12 @@ Item {
anchors.topMargin: Style.margin
width: parent.width
anchors.bottom: parent.bottom
model: _location.searchModel
model: root.locationModel
delegate: locationSearchDelegate
clip: true
header: Item {
visible: _location.searchModel.isSearchActive
visible: !root.showCarriers && _location.searchModel.isSearchActive
width: parent.width
height: visible ? 50 : 0
@ -246,7 +284,7 @@ Item {
footer: Item {
width: parent.width
height: noResultsText.height
visible: (parent.count === 0) && !_location.searchModel.isSearchActive
visible: !root.showCarriers && (parent.count === 0) && !_location.searchModel.isSearchActive
Text {
id: noResultsText
width: parent.width

View file

@ -0,0 +1,214 @@
import QtQuick 2.4
import FlightGear 1.0
import FlightGear.Launcher 1.0
import "."
Item {
property alias geod: diagram.geod
NavaidDiagram {
id: diagram
anchors.fill: parent
offsetEnabled: _location.offsetEnabled
offsetBearing: _location.offsetRadial
offsetDistance: _location.offsetDistance
heading: _location.heading
}
Component.onCompleted: {
syncUIFromController();
}
function syncUIFromController()
{
if (_location.useCarrierFLOLS) {
flolsRadio.select()
} else {
parkingRadio.select();
parkingChoice.syncCurrentIndex();
}
}
Rectangle {
id: panel
color: "transparent"
border.width: 1
border.color: Style.frameColor
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: Style.strutSize
}
height: selectionGrid.height + Style.margin * 2
// set opacity here only, so we don't make the whole summary pannel translucent
Rectangle {
id: background
anchors.fill: parent
z: -1
opacity: Style.panelOpacity
color: "white"
}
RadioButtonGroup {
id: radioGroup
}
Column {
id: selectionGrid
spacing: Style.margin
width: parent.width
StyledText { // heading text
id: headingText
anchors {
left: parent.left
right: parent.right
margins: Style.margin
}
text: qsTr("Carrier: %1").arg(_location.carrier);
font.pixelSize: Style.headingFontPixelSize
}
// on FLOLS offset
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
RadioButton {
id: flolsRadio
anchors.verticalCenter: parent.verticalCenter
group: radioGroup
onClicked: {
if (selected) _location.useCarrierFLOLS = selected
}
selected: _location.useCarrierFLOLS
}
StyledText {
text: qsTr("On final approach")
anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected
}
NumericalEdit {
id: offsetNmEdit
quantity: _location.offsetDistance
onCommit: _location.offsetDistance = newValue;
label: qsTr("at")
unitsMode: Units.Distance
live: true
anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected
}
StyledText {
text: qsTr(" from the FLOLS (aka the ball)")
anchors.verticalCenter: parent.verticalCenter
enabled: flolsRadio.selected
}
Item {
height: 1; width: Style.strutSize
}
ToggleSwitch {
id: airspeedToggle
enabled: flolsRadio.selected
checked: _location.speedEnabled
onCheckedChanged: _location.speedEnabled = checked;
anchors.verticalCenter: parent.verticalCenter
}
NumericalEdit {
id: airspeedSpinbox
label: qsTr("Airspeed:")
unitsMode: Units.SpeedWithoutMach
enabled: _location.speedEnabled && flolsRadio.selected
quantity: _location.airspeed
onCommit: _location.airspeed = newValue
anchors.verticalCenter: parent.verticalCenter
}
} // of FLOLS row
// parking row
Row {
anchors.left: parent.left
anchors.leftMargin: Style.margin
anchors.right: parent.right
anchors.rightMargin: Style.margin
spacing: Style.margin
// hide if there's no parking locations defined for this carrier
visible: _location.carrierParkings.length > 0
RadioButton {
id: parkingRadio
anchors.verticalCenter: parent.verticalCenter
group: radioGroup
onClicked: {
if (selected) parkingChoice.setLocation();
}
}
StyledText {
text: qsTr("Parking")
anchors.verticalCenter: parent.verticalCenter
enabled: parkingRadio.selected
}
PopupChoice {
id: parkingChoice
model: _location.carrierParkings
// displayRole: "modelData"
width: parent.width * 0.5
anchors.verticalCenter: parent.verticalCenter
enabled: parkingRadio.selected
onCurrentIndexChanged: {
setLocation();
}
function syncCurrentIndex()
{
for (var i=0; i < _location.carrierParkings.length; ++i) {
if (_location.carrierParkings[i] === _location.carrierParking) {
currentIndex = i;
return;
}
}
// not found, default to available
currentIndex = 0;
}
function setLocation()
{
_location.carrierParking = _location.carrierParkings[currentIndex]
//diagram.selection = _location.airportParkings[currentIndex]
}
}
}
ToggleSwitch {
anchors.left: parent.left
anchors.leftMargin: Style.margin
label: qsTr("Tune navigation radio (TACAN) to carrier")
checked: _location.tuneNAV1
onCheckedChanged: {
_location.tuneNAV1 = checked
}
}
} // main layout column
} // main panel rectangle
}

View file

@ -91,6 +91,7 @@
<file>qml/LineEdit.qml</file>
<file>qml/LocationAirportView.qml</file>
<file>qml/LocationNavaidView.qml</file>
<file>qml/LocationCarrierView.qml</file>
<file alias="linear-spinner">qml/icons8-linear-spinner.gif</file>
<file alias="ellipsis-icon">qml/icons8-ellipsis-filled-50.png</file>
<file>qml/RadioButton.qml</file>
@ -130,6 +131,7 @@
<file>qml/AircraftListView.qml</file>
<file>qml/GridToggleButton.qml</file>
<file>qml/EnableDisableButton.qml</file>
<file>qml/IconButton.qml</file>
</qresource>
<qresource prefix="/preview">
<file alias="close-icon">preview-close.png</file>
@ -149,5 +151,7 @@
<file alias="icon-grid-view">assets/icons8-grid-view.svg</file>
<file alias="icon-list-view">assets/icons8-menu.svg</file>
<file alias="icon-hide">assets/icons8-hide-50.png</file>
<file alias="icon-carrier">assets/icons8-cargo-ship-50.png</file>
<file alias="aircraft-carrier">assets/aircraft-carrier-icon.png</file>
</qresource>
</RCC>

View file

@ -1423,7 +1423,7 @@ fgOptScenario( const char *arg )
}
// create description node
auto n = FGAIManager::registerScenarioFile(path);
auto n = FGAIManager::registerScenarioFile(globals->get_props(), path);
if (!n) {
SG_LOG(SG_GENERAL, SG_WARN, "failed to read scenario file at:" << path);
return FG_OPTIONS_ERROR;

View file

@ -49,6 +49,7 @@
using std::endl;
using std::string;
namespace flightgear
{
@ -68,8 +69,11 @@ static bool global_callbackRegistered = false;
static void finalizePosition();
namespace { // annonymous namepsace to avoid warnings about inline classes
// Set current tower position lon/lat given an airport id
static bool fgSetTowerPosFromAirportID( const string& id) {
bool fgSetTowerPosFromAirportID( const string& id)
{
const FGAirport *a = fgFindAirportID( id);
if (a) {
SGGeod tower = a->getTowerLocation();
@ -80,12 +84,12 @@ static bool fgSetTowerPosFromAirportID( const string& id) {
} else {
return false;
}
}
class FGTowerLocationListener : public SGPropertyChangeListener {
void valueChanged(SGPropertyNode* node) {
void valueChanged(SGPropertyNode* node) override
{
string id(node->getStringValue());
if (fgGetBool("/sim/tower/auto-position",true))
{
@ -116,6 +120,9 @@ class FGClosestTowerLocationListener : public SGPropertyChangeListener
}
};
} // of anonymous namespace
void initTowerLocationListener() {
SGPropertyChangeListener* tll = new FGTowerLocationListener();
@ -160,10 +167,7 @@ static void fgApplyStartOffset(const SGGeod& aStartPos, double aHeading, double
aHeading = aTargetHeading;
}
SGGeod offset;
double az2; // dummy
SGGeodesy::direct(startPos, offsetAzimuth + 180, offsetDistance, offset, az2);
startPos = offset;
startPos = SGGeodesy::direct(startPos, offsetAzimuth + 180, offsetDistance);
}
setInitialPosition(startPos, aHeading);
@ -184,7 +188,7 @@ std::tuple<SGGeod, double> runwayStartPos(FGRunwayRef runway)
// add a margin, try to keep the entire aeroplane comfortable off the
// runway.
double margin = startOffset + (runway->widthM() * 1.5);
FGTaxiNodeRef taxiNode = groundNet ? groundNet->findNearestNodeOffRunway(pos, runway, margin) : 0;
FGTaxiNodeRef taxiNode = groundNet ? groundNet->findNearestNodeOffRunway(pos, runway, margin) : FGTaxiNodeRef{};
if (taxiNode) {
// set this so multiplayer.nas can inform the user
fgSetBool("/sim/presets/avoided-mp-runway", true);
@ -313,11 +317,10 @@ static bool fgSetPosFromAirportIDandRwy( const string& id, const string& rwy, bo
}
static void fgSetDistOrAltFromGlideSlope() {
// cout << "fgSetDistOrAltFromGlideSlope()" << endl;
static void fgSetDistOrAltFromGlideSlope()
{
string apt_id = fgGetString("/sim/presets/airport-id");
double gs = fgGetDouble("/sim/presets/glideslope-deg")
* SG_DEGREES_TO_RADIANS ;
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
double od = fgGetDouble("/sim/presets/offset-distance-nm");
double alt = fgGetDouble("/sim/presets/altitude-ft");
@ -363,9 +366,7 @@ static bool fgSetPosFromNAV( const string& id,
FGPositioned::Type type,
PositionedID guid)
{
FGNavRecord* nav = 0;
FGNavRecordRef nav;
if (guid != 0) {
nav = FGPositioned::loadById<FGNavRecord>(guid);
if (!nav)
@ -383,14 +384,14 @@ static bool fgSetPosFromNAV( const string& id,
if( navlist.size() > 1 ) {
std::ostringstream buf;
buf << "Ambigous NAV-ID: '" << id << "'. Specify id and frequency. Available stations:" << endl;
for( nav_list_type::const_iterator it = navlist.begin(); it != navlist.end(); ++it ) {
for( const auto& nav : navlist ) {
// NDB stored in kHz, VOR stored in MHz * 100 :-P
double factor = (*it)->type() == FGPositioned::NDB ? 1.0 : 1/100.0;
string unit = (*it)->type() == FGPositioned::NDB ? "kHz" : "MHz";
buf << (*it)->ident() << " "
<< std::setprecision(5) << (double)((*it)->get_freq() * factor) << " "
<< (*it)->get_lat() << "/" << (*it)->get_lon()
<< endl;
double factor = nav->type() == FGPositioned::NDB ? 1.0 : 1/100.0;
string unit = nav->type() == FGPositioned::NDB ? "kHz" : "MHz";
buf << nav->ident() << " "
<< std::setprecision(5) << static_cast<double>(nav->get_freq() * factor) << " "
<< nav->get_lat() << "/" << nav->get_lon()
<< endl;
}
SG_LOG( SG_GENERAL, SG_ALERT, buf.str() );
@ -413,7 +414,7 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
// so our PagedLOD is loaded
fgSetDouble("/sim/presets/longitude-deg", initialPos.second.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", initialPos.second.getLatitudeDeg());
SG_LOG( SG_GENERAL, SG_INFO, "Initial carrier pos = " << initialPos.second );
SG_LOG( SG_GENERAL, SG_DEBUG, "Initial carrier pos = " << initialPos.second );
return VicinityPosition;
}
@ -421,6 +422,31 @@ static InitPosResult setInitialPosFromCarrier( const string& carrier )
return Failure;
}
static InitPosResult checkCarrierSceneryLoaded(const SGSharedPtr<FGAICarrier> carrierRef)
{
SGVec3d cartPos = carrierRef->getCartPos();
auto framestamp = globals->get_renderer()->getViewer()->getFrameStamp();
simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(),
toOsg(cartPos),
100.0 /* range in metres */,
framestamp);
// currently the PagedLODs will not be loaded by the DatabasePager
// while the splashscreen is there, so CheckSceneryVisitor force-loads
// missing objects in the main thread
carrierRef->getSceneBranch()->accept(csnv);
if (!csnv.isLoaded()) {
return ContinueWaiting;
}
// and then wait for the load to actually be synced to the main thread
if (carrierRef->getSceneBranch()->getNumChildren() < 1) {
return ContinueWaiting;
}
return VicinityPosition;
}
// Set current_options lon/lat given an aircraft carrier id
static InitPosResult setFinalPosFromCarrier( const string& carrier, const string& posid )
{
@ -432,24 +458,9 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
return Failure;
}
SGVec3d cartPos = carrierRef->getCartPos();
auto framestamp = globals->get_renderer()->getViewer()->getFrameStamp();
simgear::CheckSceneryVisitor csnv(globals->get_scenery()->getPager(),
toOsg(cartPos),
100.0 /* range in metres */,
framestamp);
// currently the PagedLODs will not be loaded by the DatabasePager
// while the splashscreen is there, so CheckSceneryVisitor force-loads
// missing objects in the main thread
carrierRef->getSceneBranch()->accept(csnv);
if (!csnv.isLoaded()) {
return ContinueWaiting;
}
// and then wait for the load to actually be synced to the main thread
if (carrierRef->getSceneBranch()->getNumChildren() < 1) {
return ContinueWaiting;
auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) {
return res; // either failrue or keep waiting for scenery load
}
SGGeod geodPos;
@ -492,10 +503,55 @@ static InitPosResult setFinalPosFromCarrier( const string& carrier, const string
return Failure;
}
static InitPosResult setFinalPosFromCarrierFLOLS(const string& carrier)
{
SGSharedPtr<FGAICarrier> carrierRef = FGAICarrier::findCarrierByNameOrPennant(carrier);
if (!carrierRef) {
SG_LOG( SG_GENERAL, SG_ALERT, "Failed to locate aircraft carrier = "
<< carrier );
return Failure;
}
auto res = checkCarrierSceneryLoaded(carrierRef);
if (res != VicinityPosition) {
return res; // either failure or keep waiting for scenery load
}
SGGeod flolsPosition;
double headingToFLOLS;
if (!carrierRef->getFLOLSPositionHeading(flolsPosition, headingToFLOLS)) {
SG_LOG( SG_GENERAL, SG_ALERT, "Unable to compiute FLOLS position for carrier = "
<< carrier );
return Failure;
}
const auto flolsElevationFt = flolsPosition.getElevationFt();
double gs = SGMiscd::deg2rad(carrierRef->getFLOLFSGlidepathAngleDeg());
const double od = fgGetDouble("/sim/presets/offset-distance-nm");
// start position, but with altitude not set
SGGeod startPos = SGGeodesy::direct(flolsPosition, headingToFLOLS + 180, od * SG_NM_TO_METER);
const double offsetFt = od * SG_NM_TO_METER * SG_METER_TO_FEET;
startPos.setElevationFt(fabs(offsetFt*tan(gs)) + flolsElevationFt);
fgSetDouble("/sim/presets/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/sim/presets/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/sim/presets/altitude-ft", startPos.getElevationFt());
fgSetDouble("/sim/presets/heading-deg", headingToFLOLS);
fgSetDouble("/position/longitude-deg", startPos.getLongitudeDeg());
fgSetDouble("/position/latitude-deg", startPos.getLatitudeDeg());
fgSetDouble("/position/altitude-ft", startPos.getElevationFt());
fgSetDouble("/orientation/heading-deg", headingToFLOLS);
fgSetBool("/sim/presets/onground", false);
return ExactPosition;
}
// Set current_options lon/lat given a fix ident and GUID
static bool fgSetPosFromFix( const string& id, PositionedID guid )
{
FGPositioned* fix = NULL;
FGPositionedRef fix;
if (guid != 0) {
fix = FGPositioned::loadById<FGPositioned>(guid);
} else {
@ -521,8 +577,7 @@ bool initPosition()
global_callbackRegistered = true;
}
double gs = fgGetDouble("/sim/presets/glideslope-deg")
* SG_DEGREES_TO_RADIANS ;
double gs = SGMiscd::deg2rad(fgGetDouble("/sim/presets/glideslope-deg"));
double od = fgGetDouble("/sim/presets/offset-distance-nm");
double alt = fgGetDouble("/sim/presets/altitude-ft");
@ -762,20 +817,29 @@ void finalizePosition()
*/
std::string carrier = fgGetString("/sim/presets/carrier");
std::string parkpos = fgGetString("/sim/presets/parkpos");
std::string runway = fgGetString("/sim/presets/runway");
std::string apt = fgGetString("/sim/presets/airport-id");
const bool rwy_req = fgGetBool("/sim/presets/runway-requested");
if (!carrier.empty())
{
const auto res = setFinalPosFromCarrier(carrier, parkpos);
if (res == ExactPosition) {
const bool atFLOLS = rwy_req && (runway == "FLOLS");
InitPosResult carrierResult;
if (atFLOLS) {
carrierResult = setFinalPosFromCarrierFLOLS(carrier);
} else {
carrierResult = setFinalPosFromCarrier(carrier, parkpos);
}
if (carrierResult == ExactPosition) {
done = true;
} else if (res == Failure) {
} else if (carrierResult == Failure) {
SG_LOG(SG_GENERAL, SG_ALERT, "secondary carrier init failed");
done = true;
} else {
done = false;
// 60 second timeout on waiting for the carrier to load
if (global_finalizeTime.elapsedMSec() > 60000) {
SG_LOG(SG_GENERAL, SG_ALERT, "Timeout waiting for carrier scenery to load, will start on the water.");
done = true;
}
}