2018-05-07 15:41:10 +00:00
|
|
|
// LocationController.cxx - GUI launcher dialog using Qt5
|
|
|
|
//
|
|
|
|
// Written by James Turner, started October 2015.
|
|
|
|
//
|
|
|
|
// Copyright (C) 2015 James Turner <zakalawe@mac.com>
|
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program; if not, write to the Free Software
|
|
|
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
#include "LocationController.hxx"
|
|
|
|
|
|
|
|
#include <QSettings>
|
|
|
|
#include <QAbstractListModel>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QQmlComponent>
|
|
|
|
#include <QQmlEngine>
|
|
|
|
|
2018-06-25 17:12:43 +00:00
|
|
|
#include <simgear/misc/strutils.hxx>
|
2018-06-28 15:06:34 +00:00
|
|
|
#include <simgear/structure/exception.hxx>
|
2018-06-25 17:12:43 +00:00
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
#include "AirportDiagram.hxx"
|
2020-04-05 22:13:32 +00:00
|
|
|
#include "CarrierDiagram.hxx"
|
2018-05-07 15:41:10 +00:00
|
|
|
#include "NavaidDiagram.hxx"
|
|
|
|
#include "LaunchConfig.hxx"
|
|
|
|
#include "DefaultAircraftLocator.hxx"
|
2018-07-18 10:32:47 +00:00
|
|
|
#include "NavaidSearchModel.hxx"
|
2020-03-18 16:34:02 +00:00
|
|
|
#include "CarriersLocationModel.hxx"
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
#include <Airports/airport.hxx>
|
|
|
|
#include <Airports/groundnetwork.hxx>
|
|
|
|
|
|
|
|
#include <Main/globals.hxx>
|
|
|
|
#include <Navaids/NavDataCache.hxx>
|
|
|
|
#include <Navaids/navrecord.hxx>
|
|
|
|
#include <Main/options.hxx>
|
|
|
|
#include <Main/fg_init.hxx>
|
|
|
|
#include <Main/fg_props.hxx> // for fgSetDouble
|
|
|
|
|
|
|
|
using namespace flightgear;
|
|
|
|
|
|
|
|
const unsigned int MAX_RECENT_LOCATIONS = 64;
|
|
|
|
|
|
|
|
QVariant savePositionList(const FGPositionedList& posList)
|
|
|
|
{
|
|
|
|
QVariantList vl;
|
2020-03-18 16:34:02 +00:00
|
|
|
for (const auto& pos : posList) {
|
2018-05-07 15:41:10 +00:00
|
|
|
QVariantMap vm;
|
|
|
|
vm.insert("ident", QString::fromStdString(pos->ident()));
|
|
|
|
vm.insert("type", pos->type());
|
|
|
|
vm.insert("lat", pos->geod().getLatitudeDeg());
|
|
|
|
vm.insert("lon", pos->geod().getLongitudeDeg());
|
|
|
|
vl.append(vm);
|
|
|
|
}
|
|
|
|
return vl;
|
|
|
|
}
|
|
|
|
|
|
|
|
FGPositionedList loadPositionedList(QVariant v)
|
|
|
|
{
|
|
|
|
QVariantList vl = v.toList();
|
|
|
|
FGPositionedList result;
|
2020-03-18 16:34:02 +00:00
|
|
|
result.reserve(static_cast<size_t>(vl.size()));
|
2018-05-07 15:41:10 +00:00
|
|
|
NavDataCache* cache = NavDataCache::instance();
|
|
|
|
|
|
|
|
Q_FOREACH(QVariant v, vl) {
|
|
|
|
QVariantMap vm = v.toMap();
|
|
|
|
std::string ident(vm.value("ident").toString().toStdString());
|
|
|
|
double lat = vm.value("lat").toDouble();
|
|
|
|
double lon = vm.value("lon").toDouble();
|
|
|
|
FGPositioned::Type ty(static_cast<FGPositioned::Type>(vm.value("type").toInt()));
|
|
|
|
FGPositioned::TypeFilter filter(ty);
|
|
|
|
FGPositionedRef pos = cache->findClosestWithIdent(ident,
|
|
|
|
SGGeod::fromDeg(lon, lat),
|
|
|
|
&filter);
|
|
|
|
if (pos)
|
|
|
|
result.push_back(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
LocationController::LocationController(QObject *parent) :
|
|
|
|
QObject(parent)
|
|
|
|
{
|
2020-03-18 16:34:02 +00:00
|
|
|
m_searchModel = new NavaidSearchModel(this);
|
2018-05-07 15:41:10 +00:00
|
|
|
m_detailQml = new QmlPositioned(this);
|
2018-06-22 11:09:35 +00:00
|
|
|
m_baseQml = new QmlPositioned(this);
|
2020-03-18 16:34:02 +00:00
|
|
|
m_carriersModel = new CarriersLocationModel(this);
|
2018-06-22 11:09:35 +00:00
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
|
|
|
|
m_defaultAirspeed = QuantityValue{Units::Knots, 120};
|
|
|
|
m_defaultHeading = QuantityValue{Units::DegreesTrue, 0};
|
|
|
|
m_defaultOffsetDistance = QuantityValue{Units::NauticalMiles, 1.0};
|
|
|
|
m_defaultOffsetRadial = QuantityValue{Units::DegreesTrue, 90};
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
// chain location and offset updated to description
|
|
|
|
connect(this, &LocationController::baseLocationChanged,
|
|
|
|
this, &LocationController::descriptionChanged);
|
|
|
|
connect(this, &LocationController::configChanged,
|
|
|
|
this, &LocationController::descriptionChanged);
|
|
|
|
connect(this, &LocationController::offsetChanged,
|
|
|
|
this, &LocationController::descriptionChanged);
|
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
LocationController::~LocationController() = default;
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
void LocationController::setLaunchConfig(LaunchConfig *config)
|
|
|
|
{
|
|
|
|
m_config = config;
|
|
|
|
connect(m_config, &LaunchConfig::collect, this, &LocationController::onCollectConfig);
|
2018-06-27 22:06:39 +00:00
|
|
|
connect(m_config, &LaunchConfig::save, this, &LocationController::onSaveCurrentLocation);
|
|
|
|
connect(m_config, &LaunchConfig::restore, this, &LocationController::onRestoreCurrentLocation);
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2018-06-27 22:06:39 +00:00
|
|
|
void LocationController::restoreSearchHistory()
|
2018-05-07 15:41:10 +00:00
|
|
|
{
|
|
|
|
QSettings settings;
|
|
|
|
m_recentLocations = loadPositionedList(settings.value("recent-locations"));
|
|
|
|
}
|
|
|
|
|
2018-06-27 22:06:39 +00:00
|
|
|
void LocationController::onRestoreCurrentLocation()
|
|
|
|
{
|
|
|
|
QVariantMap vm = m_config->getValueForKey("", "current-location", QVariantMap()).toMap();
|
|
|
|
if (vm.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
restoreLocation(vm);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::onSaveCurrentLocation()
|
|
|
|
{
|
|
|
|
m_config->setValueForKey("", "current-location", saveLocation());
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
bool LocationController::isParkedLocation() const
|
|
|
|
{
|
|
|
|
if (m_airportLocation) {
|
2019-01-08 23:21:28 +00:00
|
|
|
if (m_useAvailableParking)
|
|
|
|
return true;
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// treat all other ground starts as taxi or on runway, i.e engines
|
|
|
|
// running if possible
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LocationController::isAirborneLocation() const
|
|
|
|
{
|
2018-07-17 10:55:25 +00:00
|
|
|
const bool altIsPositive = (m_altitude.value > 0);
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
if (m_locationIsLatLon) {
|
2018-07-17 10:55:25 +00:00
|
|
|
return m_altitudeEnabled && altIsPositive;
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
2018-06-27 22:06:39 +00:00
|
|
|
const bool onRunway =
|
|
|
|
(m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)) ||
|
|
|
|
m_useActiveRunway;
|
|
|
|
|
|
|
|
if (onRunway && m_onFinal) {
|
2018-05-07 15:41:10 +00:00
|
|
|
// in this case no altitude might be set, but we assume
|
2018-06-27 22:06:39 +00:00
|
|
|
// it's still an airborne position
|
2018-05-07 15:41:10 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-06-27 22:06:39 +00:00
|
|
|
return false;
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// relative to a navaid or fix - base off altitude.
|
2018-07-17 10:55:25 +00:00
|
|
|
return m_altitudeEnabled && altIsPositive;
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
QuantityValue LocationController::offsetRadial() const
|
2018-05-07 15:41:10 +00:00
|
|
|
{
|
|
|
|
return m_offsetRadial;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setBaseGeod(QmlGeod geod)
|
|
|
|
{
|
|
|
|
if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
|
|
|
|
return;
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
clearLocation();
|
2018-05-07 15:41:10 +00:00
|
|
|
m_locationIsLatLon = true;
|
|
|
|
m_geodLocation = geod.geod();
|
2020-03-18 16:34:02 +00:00
|
|
|
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;
|
2020-04-05 22:13:32 +00:00
|
|
|
m_abeam = false;
|
2018-05-07 15:41:10 +00:00
|
|
|
m_location.clear();
|
2020-03-18 16:34:02 +00:00
|
|
|
m_carrierName.clear();
|
2018-05-07 15:41:10 +00:00
|
|
|
m_airportLocation.clear();
|
|
|
|
m_detailLocation.clear();
|
2020-03-18 16:34:02 +00:00
|
|
|
m_detailQml->setGuid(0);
|
|
|
|
m_baseQml->setGuid(0);
|
|
|
|
m_carrierParkings.clear();
|
|
|
|
m_carrierParking.clear();
|
2018-05-07 15:41:10 +00:00
|
|
|
emit baseLocationChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setBaseLocation(QmlPositioned* pos)
|
|
|
|
{
|
2018-06-22 11:09:35 +00:00
|
|
|
if (!pos) {
|
2020-03-18 16:34:02 +00:00
|
|
|
clearLocation();
|
2018-06-22 11:09:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (pos->inner() == m_location)
|
|
|
|
return;
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
clearLocation();
|
2018-05-07 15:41:10 +00:00
|
|
|
m_location = pos->inner();
|
2018-06-22 11:09:35 +00:00
|
|
|
m_baseQml->setGuid(pos->guid());
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
if (FGPositioned::isAirportType(m_location.ptr())) {
|
|
|
|
m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
|
2018-06-28 15:22:55 +00:00
|
|
|
// disable offset when selecting a heliport
|
|
|
|
if (m_airportLocation->isHeliport()) {
|
|
|
|
m_onFinal = false;
|
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
} else {
|
|
|
|
m_airportLocation.clear();
|
|
|
|
}
|
2018-06-28 15:22:55 +00:00
|
|
|
|
|
|
|
emit offsetChanged();
|
2018-05-07 15:41:10 +00:00
|
|
|
emit baseLocationChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setDetailLocation(QmlPositioned* pos)
|
|
|
|
{
|
|
|
|
if (pos && (pos->inner() == m_detailLocation))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!pos) {
|
|
|
|
m_detailLocation.clear();
|
|
|
|
m_detailQml->setInner({});
|
|
|
|
} else {
|
|
|
|
m_detailLocation = pos->inner();
|
|
|
|
m_useActiveRunway = false;
|
2019-01-08 23:21:28 +00:00
|
|
|
m_useAvailableParking = false;
|
2018-05-07 15:41:10 +00:00
|
|
|
m_detailQml->setInner(pos->inner());
|
|
|
|
}
|
|
|
|
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
QmlGeod LocationController::baseGeod() const
|
|
|
|
{
|
2020-03-18 16:34:02 +00:00
|
|
|
if (m_locationIsLatLon || m_locationIsCarrier)
|
2018-05-07 15:41:10 +00:00
|
|
|
return m_geodLocation;
|
|
|
|
|
|
|
|
if (m_location)
|
|
|
|
return QmlGeod(m_location->geod());
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LocationController::isAirportLocation() const
|
|
|
|
{
|
|
|
|
return m_airportLocation;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setUseActiveRunway(bool b)
|
|
|
|
{
|
|
|
|
if (b == m_useActiveRunway)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_useActiveRunway = b;
|
|
|
|
if (m_useActiveRunway) {
|
|
|
|
m_detailLocation.clear(); // clear any specific runway
|
2019-01-08 23:21:28 +00:00
|
|
|
m_useAvailableParking = false;
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::addToRecent(QmlPositioned* pos)
|
|
|
|
{
|
|
|
|
addToRecent(pos->inner());
|
|
|
|
}
|
|
|
|
|
|
|
|
QObjectList LocationController::airportRunways() const
|
|
|
|
{
|
|
|
|
if (!m_airportLocation)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
QObjectList result;
|
|
|
|
if (m_airportLocation->isHeliport()) {
|
|
|
|
// helipads
|
|
|
|
for (unsigned int r=0; r<m_airportLocation->numHelipads(); ++r) {
|
|
|
|
auto p = new QmlPositioned(m_airportLocation->getHelipadByIndex(r).ptr());
|
|
|
|
QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership);
|
|
|
|
result.push_back(p);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// regular runways
|
2018-06-16 12:03:36 +00:00
|
|
|
for (unsigned int r=0; r<m_airportLocation->numRunways(); ++r) {
|
2018-05-07 15:41:10 +00:00
|
|
|
auto p = new QmlPositioned(m_airportLocation->getRunwayByIndex(r).ptr());
|
|
|
|
QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership);
|
|
|
|
result.push_back(p);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
QObjectList LocationController::airportParkings() const
|
|
|
|
{
|
|
|
|
if (!m_airportLocation)
|
|
|
|
return {};
|
|
|
|
|
|
|
|
QObjectList result;
|
|
|
|
for (auto park : m_airportLocation->groundNetwork()->allParkings()) {
|
|
|
|
auto p = new QmlPositioned(park);
|
|
|
|
QQmlEngine::setObjectOwnership(p, QQmlEngine::JavaScriptOwnership);
|
|
|
|
result.push_back(p);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::showHistoryInSearchModel()
|
|
|
|
{
|
2018-07-01 08:55:37 +00:00
|
|
|
// prepend the default location and tutorial airport
|
|
|
|
|
|
|
|
FGPositionedList locs = m_recentLocations;
|
2018-05-07 15:41:10 +00:00
|
|
|
const std::string defaultICAO = flightgear::defaultAirportICAO();
|
2018-07-01 08:55:37 +00:00
|
|
|
const std::string tutorialICAO = "PHTO"; // C172P tutorial aiurport
|
2018-05-07 15:41:10 +00:00
|
|
|
|
2018-07-01 08:55:37 +00:00
|
|
|
// remove them from the recent locations
|
2020-04-03 19:37:37 +00:00
|
|
|
auto it = std::remove_if(locs.begin(), locs.end(),
|
|
|
|
[defaultICAO, tutorialICAO](FGPositionedRef pos)
|
2018-07-01 08:55:37 +00:00
|
|
|
{
|
|
|
|
return (pos->ident() == defaultICAO) || (pos->ident() == tutorialICAO);
|
2018-05-07 15:41:10 +00:00
|
|
|
});
|
2018-07-01 08:55:37 +00:00
|
|
|
locs.erase(it, locs.end());
|
2018-05-07 15:41:10 +00:00
|
|
|
|
2018-07-01 08:55:37 +00:00
|
|
|
// prepend them
|
|
|
|
FGAirportRef apt = FGAirport::findByIdent(tutorialICAO);
|
|
|
|
locs.insert(locs.begin(), apt);
|
|
|
|
|
|
|
|
apt = FGAirport::findByIdent(defaultICAO);
|
|
|
|
locs.insert(locs.begin(), apt);
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
m_searchModel->setItems(locs);
|
|
|
|
}
|
|
|
|
|
|
|
|
QmlGeod LocationController::parseStringAsGeod(QString string) const
|
|
|
|
{
|
|
|
|
SGGeod g;
|
2018-06-25 17:12:43 +00:00
|
|
|
if (!simgear::strutils::parseStringAsGeod(string.toStdString(), &g)) {
|
2018-05-07 15:41:10 +00:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return QmlGeod(g);
|
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
QmlPositioned *LocationController::detail() const
|
|
|
|
{
|
|
|
|
return m_detailQml;
|
|
|
|
}
|
|
|
|
|
2018-06-22 11:09:35 +00:00
|
|
|
QmlPositioned *LocationController::baseLocation() const
|
|
|
|
{
|
|
|
|
return m_baseQml;
|
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
QStringList LocationController::carrierParkings() const
|
|
|
|
{
|
|
|
|
return m_carrierParkings;
|
|
|
|
}
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
void LocationController::setOffsetRadial(QuantityValue offsetRadial)
|
2018-05-07 15:41:10 +00:00
|
|
|
{
|
|
|
|
if (m_offsetRadial == offsetRadial)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_offsetRadial = offsetRadial;
|
|
|
|
emit offsetChanged();
|
|
|
|
}
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
void LocationController::setOffsetDistance(QuantityValue d)
|
2018-05-07 15:41:10 +00:00
|
|
|
{
|
2018-07-17 10:55:25 +00:00
|
|
|
if (m_offsetDistance == d)
|
2018-05-07 15:41:10 +00:00
|
|
|
return;
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
m_offsetDistance = d;
|
2018-05-07 15:41:10 +00:00
|
|
|
emit offsetChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setOffsetEnabled(bool offsetEnabled)
|
|
|
|
{
|
|
|
|
if (m_offsetEnabled == offsetEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_offsetEnabled = offsetEnabled;
|
|
|
|
emit offsetChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setOnFinal(bool onFinal)
|
|
|
|
{
|
|
|
|
if (m_onFinal == onFinal)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_onFinal = onFinal;
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setTuneNAV1(bool tuneNAV1)
|
|
|
|
{
|
|
|
|
if (m_tuneNAV1 == tuneNAV1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_tuneNAV1 = tuneNAV1;
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setUseAvailableParking(bool useAvailableParking)
|
|
|
|
{
|
|
|
|
if (m_useAvailableParking == useAvailableParking)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_useAvailableParking = useAvailableParking;
|
|
|
|
if (m_useAvailableParking) {
|
|
|
|
m_detailLocation.clear(); // clear any specific runway
|
2019-01-08 23:21:28 +00:00
|
|
|
m_useActiveRunway = false;
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2020-04-05 22:13:32 +00:00
|
|
|
void LocationController::setAbeam(bool abeam)
|
|
|
|
{
|
|
|
|
m_abeam = abeam;
|
|
|
|
emit configChanged();
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
void LocationController::restoreLocation(QVariantMap l)
|
|
|
|
{
|
2020-03-18 16:34:02 +00:00
|
|
|
clearLocation();
|
|
|
|
|
2018-06-28 15:06:34 +00:00
|
|
|
try {
|
2020-03-18 16:34:02 +00:00
|
|
|
m_altitudeEnabled = l.contains("altitude");
|
|
|
|
m_speedEnabled = l.contains("speed");
|
|
|
|
m_headingEnabled = l.contains("heading");
|
|
|
|
|
|
|
|
m_altitude = l.value("altitude", QVariant::fromValue(m_defaultAltitude)).value<QuantityValue>();
|
|
|
|
m_airspeed = l.value("speed", QVariant::fromValue(m_defaultAirspeed)).value<QuantityValue>();
|
|
|
|
m_heading = l.value("heading", QVariant::fromValue(m_defaultHeading)).value<QuantityValue>();
|
|
|
|
|
|
|
|
m_offsetEnabled = l.value("offset-enabled").toBool();
|
|
|
|
m_offsetRadial = l.value("offset-bearing", QVariant::fromValue(m_defaultOffsetRadial)).value<QuantityValue>();
|
|
|
|
m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
|
|
|
|
m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
|
|
|
|
|
2018-06-28 15:06:34 +00:00
|
|
|
if (l.contains("location-lat")) {
|
|
|
|
m_locationIsLatLon = true;
|
|
|
|
m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
|
|
|
|
l.value("location-lat").toDouble());
|
2020-03-18 16:34:02 +00:00
|
|
|
} else if (l.contains("carrier")) {
|
|
|
|
setCarrierLocation(l.value("carrier").toString());
|
|
|
|
if (l.contains("carrier-flols")) {
|
|
|
|
setUseCarrierFLOLS(l.value("carrier-flols").toBool());
|
2020-04-05 22:13:32 +00:00
|
|
|
setAbeam(l.value("abeam").toBool());
|
2020-03-18 16:34:02 +00:00
|
|
|
// 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());
|
|
|
|
}
|
2018-06-28 15:06:34 +00:00
|
|
|
} else if (l.contains("location-id")) {
|
2020-03-18 16:34:02 +00:00
|
|
|
m_location = NavDataCache::instance()->loadById(l.value("location-id").toLongLong());
|
2018-06-28 15:06:34 +00:00
|
|
|
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);
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2018-06-28 15:06:34 +00:00
|
|
|
if (m_airportLocation) {
|
|
|
|
m_useActiveRunway = false;
|
2019-01-08 23:21:28 +00:00
|
|
|
m_useAvailableParking = false;
|
2018-06-28 15:06:34 +00:00
|
|
|
|
|
|
|
if (l.contains("location-apt-runway")) {
|
|
|
|
QString runway = l.value("location-apt-runway").toString().toUpper();
|
|
|
|
if (runway == QStringLiteral("ACTIVE")) {
|
|
|
|
m_useActiveRunway = true;
|
|
|
|
} else if (m_airportLocation->isHeliport()) {
|
|
|
|
m_detailLocation = m_airportLocation->getHelipadByIdent(runway.toStdString());
|
|
|
|
} else {
|
|
|
|
m_detailLocation = m_airportLocation->getRunwayByIdent(runway.toStdString());
|
|
|
|
}
|
|
|
|
} else if (l.contains("location-apt-parking")) {
|
|
|
|
QString parking = l.value("location-apt-parking").toString();
|
2019-01-08 23:21:28 +00:00
|
|
|
if (parking == QStringLiteral("AVAILABLE")) {
|
|
|
|
m_useAvailableParking = true;
|
|
|
|
} else {
|
|
|
|
m_detailLocation = m_airportLocation->groundNetwork()->findParkingByName(parking.toStdString());
|
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2018-06-28 15:06:34 +00:00
|
|
|
if (m_detailLocation) {
|
|
|
|
m_detailQml->setInner(m_detailLocation);
|
|
|
|
}
|
2018-06-22 11:09:35 +00:00
|
|
|
|
2018-06-28 15:06:34 +00:00
|
|
|
m_onFinal = l.value("location-on-final").toBool();
|
2020-04-05 22:13:32 +00:00
|
|
|
setAbeam(l.value("abeam").toBool());
|
2018-07-17 10:55:25 +00:00
|
|
|
m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
|
2018-06-28 15:06:34 +00:00
|
|
|
} // of location is an airport
|
|
|
|
} catch (const sg_exception&) {
|
|
|
|
qWarning() << "Errors restoring saved location, clearing";
|
2020-03-18 16:34:02 +00:00
|
|
|
clearLocation();
|
2018-06-28 15:06:34 +00:00
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
baseLocationChanged();
|
|
|
|
configChanged();
|
|
|
|
offsetChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool LocationController::shouldStartPaused() const
|
|
|
|
{
|
2020-03-18 16:34:02 +00:00
|
|
|
if (m_useCarrierFLOLS) {
|
|
|
|
return true;
|
|
|
|
}
|
2020-04-03 19:37:37 +00:00
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (!m_location) {
|
|
|
|
return false; // defaults to on-ground at the default airport
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
|
|
|
return m_onFinal;
|
|
|
|
} else {
|
|
|
|
// navaid, start paused
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVariantMap LocationController::saveLocation() const
|
|
|
|
{
|
|
|
|
QVariantMap locationSet;
|
|
|
|
if (m_locationIsLatLon) {
|
|
|
|
locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
|
|
|
|
locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg());
|
2020-03-18 16:34:02 +00:00
|
|
|
} 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));
|
2020-04-05 22:13:32 +00:00
|
|
|
locationSet.insert("abeam", m_abeam);
|
2020-03-18 16:34:02 +00:00
|
|
|
} else if (!m_carrierParking.isEmpty()) {
|
|
|
|
locationSet.insert("carrier-parking", m_carrierParking);
|
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
} else if (m_location) {
|
|
|
|
locationSet.insert("location-id", static_cast<qlonglong>(m_location->guid()));
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
|
|
|
locationSet.insert("location-on-final", m_onFinal);
|
2018-07-17 10:55:25 +00:00
|
|
|
locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance));
|
2020-04-05 22:13:32 +00:00
|
|
|
locationSet.insert("abeam", m_abeam);
|
2018-05-07 15:41:10 +00:00
|
|
|
if (m_useActiveRunway) {
|
|
|
|
locationSet.insert("location-apt-runway", "ACTIVE");
|
2019-01-08 23:21:28 +00:00
|
|
|
} else if (m_useAvailableParking) {
|
|
|
|
locationSet.insert("location-apt-parking", "AVAILABLE");
|
2018-06-25 15:40:00 +00:00
|
|
|
} else if (m_detailLocation) {
|
|
|
|
const auto detailType = m_detailLocation->type();
|
|
|
|
if (detailType == FGPositioned::RUNWAY) {
|
|
|
|
locationSet.insert("location-apt-runway", QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
} else if (detailType == FGPositioned::PARKING) {
|
|
|
|
locationSet.insert("location-apt-parking", QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
} // of location is an airport
|
|
|
|
} // of m_location is valid
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
if (m_altitudeEnabled) {
|
|
|
|
locationSet.insert("altitude", QVariant::fromValue(m_altitude));
|
2018-06-21 14:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_speedEnabled) {
|
2018-07-17 10:55:25 +00:00
|
|
|
locationSet.insert("speed", QVariant::fromValue(m_airspeed));
|
2018-06-21 14:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_headingEnabled) {
|
2018-07-17 10:55:25 +00:00
|
|
|
locationSet.insert("heading", QVariant::fromValue(m_heading));
|
2018-06-21 14:27:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_offsetEnabled) {
|
|
|
|
locationSet.insert("offset-enabled", m_offsetEnabled);
|
2018-07-17 10:55:25 +00:00
|
|
|
locationSet.insert("offset-bearing", QVariant::fromValue(m_offsetRadial));
|
|
|
|
locationSet.insert("offset-distance", QVariant::fromValue(m_offsetDistance));
|
2018-06-21 14:27:48 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
locationSet.insert("text", description());
|
|
|
|
locationSet.insert("tune-nav1-radio", m_tuneNAV1);
|
|
|
|
|
|
|
|
return locationSet;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setLocationProperties()
|
|
|
|
{
|
|
|
|
SGPropertyNode_ptr presets = fgGetNode("/sim/presets", true);
|
|
|
|
|
|
|
|
QStringList props = QStringList() << "vor-id" << "fix" << "ndb-id" <<
|
|
|
|
"runway-requested" << "navaid-id" << "offset-azimuth-deg" <<
|
|
|
|
"offset-distance-nm" << "glideslope-deg" <<
|
|
|
|
"speed-set" << "on-ground" << "airspeed-kt" <<
|
2020-04-05 22:13:32 +00:00
|
|
|
"airport-id" << "runway" << "parkpos" << "carrier" << "abeam";
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
Q_FOREACH(QString s, props) {
|
|
|
|
SGPropertyNode* c = presets->getChild(s.toStdString());
|
|
|
|
if (c) {
|
|
|
|
c->clearValue();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_locationIsLatLon) {
|
|
|
|
fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg());
|
|
|
|
fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg());
|
|
|
|
fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg());
|
|
|
|
fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
|
|
|
|
|
|
|
|
applyPositionOffset();
|
2020-03-18 16:34:02 +00:00
|
|
|
applyAltitude();
|
|
|
|
applyAirspeed();
|
2018-05-07 15:41:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fgSetDouble("/sim/presets/latitude-deg", 9999.0);
|
|
|
|
fgSetDouble("/sim/presets/longitude-deg", 9999.0);
|
|
|
|
fgSetDouble("/sim/presets/altitude-ft", -9999.0);
|
|
|
|
fgSetDouble("/sim/presets/heading-deg", 9999.0);
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
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);
|
2020-04-05 22:13:32 +00:00
|
|
|
fgSetBool("/sim/presets/abeam", m_abeam);
|
2020-04-03 19:37:37 +00:00
|
|
|
applyAltitude();
|
2020-03-18 16:34:02 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (!m_location) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
|
|
|
fgSetString("/sim/presets/airport-id", m_airportLocation->ident());
|
|
|
|
fgSetBool("/sim/presets/on-ground", true);
|
|
|
|
fgSetBool("/sim/presets/airport-requested", true);
|
2020-04-05 22:13:32 +00:00
|
|
|
fgSetBool("/sim/presets/abeam", m_abeam);
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
|
|
|
|
const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
|
|
|
|
if (m_useActiveRunway) {
|
|
|
|
// automatic runway choice
|
|
|
|
// we can't set navaid here
|
2019-01-08 23:21:28 +00:00
|
|
|
} else if (m_useAvailableParking) {
|
|
|
|
fgSetString("/sim/presets/parkpos", "AVAILABLE");
|
2018-05-07 15:41:10 +00:00
|
|
|
} else if (onRunway) {
|
|
|
|
if (m_airportLocation->type() == FGPositioned::AIRPORT) {
|
|
|
|
// explicit runway choice
|
|
|
|
fgSetString("/sim/presets/runway", m_detailLocation->ident() );
|
|
|
|
fgSetBool("/sim/presets/runway-requested", true );
|
|
|
|
|
|
|
|
// set nav-radio 1 based on selected runway
|
|
|
|
FGRunway* runway = static_cast<FGRunway*>(m_detailLocation.ptr());
|
|
|
|
if (m_tuneNAV1 && runway->ILS()) {
|
|
|
|
double mhz = runway->ILS()->get_freq() / 100.0;
|
|
|
|
fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", runway->headingDeg());
|
|
|
|
fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", mhz);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_onFinal) {
|
|
|
|
fgSetDouble("/sim/presets/glideslope-deg", 3.0);
|
2018-07-17 10:55:25 +00:00
|
|
|
fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
|
2018-05-07 15:41:10 +00:00
|
|
|
fgSetBool("/sim/presets/on-ground", false);
|
|
|
|
}
|
|
|
|
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
|
|
|
|
// explicit pad choice
|
|
|
|
fgSetString("/sim/presets/runway", m_detailLocation->ident() );
|
|
|
|
fgSetBool("/sim/presets/runway-requested", true );
|
|
|
|
}
|
|
|
|
} else if (atParking) {
|
|
|
|
// parking selection
|
|
|
|
fgSetString("/sim/presets/parkpos", m_detailLocation->ident());
|
|
|
|
}
|
|
|
|
// of location is an airport
|
|
|
|
} else {
|
|
|
|
fgSetString("/sim/presets/airport-id", "");
|
|
|
|
|
|
|
|
// location is a navaid
|
|
|
|
// note setting the ident here is ambigious, we really only need and
|
|
|
|
// want the 'navaid-id' property. However setting the 'real' option
|
|
|
|
// gives a better UI experience (eg existing Position in Air dialog)
|
|
|
|
FGPositioned::Type ty = m_location->type();
|
|
|
|
switch (ty) {
|
|
|
|
case FGPositioned::VOR:
|
|
|
|
fgSetString("/sim/presets/vor-id", m_location->ident());
|
|
|
|
setNavRadioOption();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FGPositioned::NDB:
|
|
|
|
fgSetString("/sim/presets/ndb-id", m_location->ident());
|
|
|
|
setNavRadioOption();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FGPositioned::FIX:
|
|
|
|
fgSetString("/sim/presets/fix", m_location->ident());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-03-18 16:34:02 +00:00
|
|
|
}
|
2020-04-03 19:37:37 +00:00
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
// set disambiguation property
|
|
|
|
globals->get_props()->setIntValue("/sim/presets/navaid-id",
|
|
|
|
static_cast<int>(m_location->guid()));
|
2020-04-03 19:37:37 +00:00
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
applyPositionOffset();
|
2018-07-02 15:45:17 +00:00
|
|
|
applyAltitude();
|
2018-07-17 10:55:25 +00:00
|
|
|
applyAirspeed();
|
2018-05-07 15:41:10 +00:00
|
|
|
} // of navaid location
|
|
|
|
}
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
void LocationController::applyAirspeed()
|
2018-07-02 15:45:17 +00:00
|
|
|
{
|
2018-07-17 10:55:25 +00:00
|
|
|
if (m_speedEnabled && (m_airspeed.unit != Units::NoUnits)) {
|
|
|
|
if (m_airspeed.unit == Units::Knots) {
|
|
|
|
m_config->setArg("vc", QString::number(m_airspeed.value));
|
2018-11-07 08:22:50 +00:00
|
|
|
} else if (m_airspeed.unit == Units::KilometersPerHour) {
|
|
|
|
const double vc = m_airspeed.convertToUnit(Units::Knots).value;
|
|
|
|
m_config->setArg("vc", QString::number(vc));
|
2018-07-17 10:55:25 +00:00
|
|
|
} else if (m_airspeed.unit == Units::Mach) {
|
|
|
|
m_config->setArg("mach", QString::number(m_airspeed.value));
|
|
|
|
} else {
|
|
|
|
qWarning() << Q_FUNC_INFO << "unsupported airpseed unit" << m_airspeed.unit;
|
|
|
|
}
|
2018-07-02 15:45:17 +00:00
|
|
|
}
|
2018-07-17 10:55:25 +00:00
|
|
|
}
|
2018-07-02 15:45:17 +00:00
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
void LocationController::applyPositionOffset()
|
|
|
|
{
|
|
|
|
if (m_headingEnabled && (m_heading.unit != Units::NoUnits)) {
|
|
|
|
if (m_heading.unit == Units::DegreesTrue) {
|
|
|
|
m_config->setArg("heading", QString::number(m_heading.value));
|
|
|
|
} else {
|
|
|
|
qWarning() << Q_FUNC_INFO << "unsupported heading unit" << m_heading.unit;
|
|
|
|
}
|
2018-07-02 15:45:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (m_offsetEnabled) {
|
|
|
|
// flip direction of azimuth to balance the flip done in fgApplyStartOffset
|
|
|
|
// I don't know why that flip exists but changing it there will break
|
|
|
|
// command-line compatability so compensating here instead
|
2018-11-07 08:22:50 +00:00
|
|
|
int offsetAzimuth = static_cast<int>(m_offsetRadial.value) - 180;
|
2018-07-02 15:45:17 +00:00
|
|
|
m_config->setArg("offset-azimuth", QString::number(offsetAzimuth));
|
2018-07-17 10:55:25 +00:00
|
|
|
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
|
|
|
|
m_config->setArg("offset-distance", QString::number(offsetNm));
|
2018-07-02 15:45:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::applyAltitude()
|
2018-05-07 15:41:10 +00:00
|
|
|
{
|
2018-07-17 10:55:25 +00:00
|
|
|
if (!m_altitudeEnabled)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (m_altitude.unit) {
|
|
|
|
default:
|
2020-04-03 19:37:37 +00:00
|
|
|
qWarning() << Q_FUNC_INFO << "unsupported altitude unit";
|
2018-06-21 14:27:48 +00:00
|
|
|
break;
|
2018-07-17 10:55:25 +00:00
|
|
|
case Units::FeetMSL:
|
|
|
|
m_config->setArg("altitude", QString::number(m_altitude.value));
|
2018-06-21 14:27:48 +00:00
|
|
|
break;
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
case Units::FeetAGL:
|
2018-06-21 14:27:48 +00:00
|
|
|
// fixme - allow the sim to accpet AGL start position
|
2018-07-17 10:55:25 +00:00
|
|
|
m_config->setArg("altitude", QString::number(m_altitude.value));
|
2018-06-21 14:27:48 +00:00
|
|
|
break;
|
|
|
|
|
2018-07-17 10:55:25 +00:00
|
|
|
case Units::FlightLevel:
|
2018-06-21 14:27:48 +00:00
|
|
|
// FIXME - allow the sim to accept real FlightLevel arguments
|
2018-07-17 10:55:25 +00:00
|
|
|
m_config->setArg("altitude", QString::number(m_altitude.value * 100));
|
2018-06-21 14:27:48 +00:00
|
|
|
break;
|
2018-11-07 08:22:50 +00:00
|
|
|
|
|
|
|
case Units::FeetAboveFieldElevation:
|
|
|
|
m_config->setArg("altitude", QString::number(m_altitude.value));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Units::MetersMSL:
|
|
|
|
const double ftMSL = m_altitude.convertToUnit(Units::FeetMSL).value;
|
|
|
|
m_config->setArg("altitude", QString::number(ftMSL));
|
|
|
|
break;
|
2018-06-21 14:27:48 +00:00
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2018-07-18 10:32:47 +00:00
|
|
|
void LocationController::applyOnFinal()
|
|
|
|
{
|
|
|
|
if (m_onFinal) {
|
|
|
|
if (!m_altitudeEnabled) {
|
|
|
|
m_config->setArg("glideslope", std::string("3.0"));
|
|
|
|
}
|
|
|
|
|
|
|
|
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
|
|
|
|
m_config->setArg("offset-distance", QString::number(offsetNm));
|
|
|
|
m_config->setArg("on-ground", std::string("false"));
|
|
|
|
|
|
|
|
applyAirspeed();
|
|
|
|
applyAltitude();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
void LocationController::onCollectConfig()
|
|
|
|
{
|
2018-06-25 22:06:20 +00:00
|
|
|
if (m_skipFromArgs) {
|
2018-07-03 08:35:26 +00:00
|
|
|
qWarning() << Q_FUNC_INFO << "skipping argument collection";
|
2018-06-25 22:06:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (m_locationIsLatLon) {
|
2018-06-28 21:51:53 +00:00
|
|
|
m_config->setArg("lat", QString::number(m_geodLocation.getLatitudeDeg(), 'f', 8));
|
|
|
|
m_config->setArg("lon", QString::number(m_geodLocation.getLongitudeDeg(), 'f', 8));
|
2018-05-07 15:41:10 +00:00
|
|
|
applyPositionOffset();
|
2018-07-04 22:17:09 +00:00
|
|
|
applyAltitude();
|
2018-07-17 10:55:25 +00:00
|
|
|
applyAirspeed();
|
2018-05-07 15:41:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
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));
|
2020-04-05 22:13:32 +00:00
|
|
|
|
|
|
|
if (m_abeam) m_config->setArg("carrier-abeam", QStringLiteral("true"));
|
2020-04-03 19:37:37 +00:00
|
|
|
applyAltitude();
|
2020-03-18 16:34:02 +00:00
|
|
|
applyAirspeed();
|
|
|
|
}
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (!m_location) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
|
|
|
m_config->setArg("airport", QString::fromStdString(m_airportLocation->ident()));
|
|
|
|
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
|
|
|
|
const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
|
|
|
|
|
|
|
|
if (m_useActiveRunway) {
|
|
|
|
// pick by default
|
2018-07-18 10:32:47 +00:00
|
|
|
applyOnFinal();
|
2019-01-08 23:21:28 +00:00
|
|
|
} else if (m_useAvailableParking) {
|
|
|
|
m_config->setArg("parkpos", QStringLiteral("AVAILABLE"));
|
2018-05-07 15:41:10 +00:00
|
|
|
} else if (onRunway) {
|
|
|
|
if (m_airportLocation->type() == FGPositioned::AIRPORT) {
|
|
|
|
m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
|
|
|
|
// set nav-radio 1 based on selected runway
|
|
|
|
FGRunway* runway = static_cast<FGRunway*>(m_detailLocation.ptr());
|
|
|
|
if (runway->ILS()) {
|
|
|
|
double mhz = runway->ILS()->get_freq() / 100.0;
|
|
|
|
m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz));
|
|
|
|
}
|
|
|
|
|
2018-07-18 10:32:47 +00:00
|
|
|
applyOnFinal();
|
2018-05-07 15:41:10 +00:00
|
|
|
} else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
|
|
|
|
m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
}
|
|
|
|
} else if (atParking) {
|
|
|
|
// parking selection
|
|
|
|
m_config->setArg("parkpos", QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
}
|
|
|
|
// of location is an airport
|
|
|
|
} else {
|
|
|
|
// location is a navaid
|
|
|
|
// note setting the ident here is ambigious, we really only need and
|
|
|
|
// want the 'navaid-id' property. However setting the 'real' option
|
|
|
|
// gives a better UI experience (eg existing Position in Air dialog)
|
|
|
|
FGPositioned::Type ty = m_location->type();
|
|
|
|
switch (ty) {
|
|
|
|
case FGPositioned::VOR:
|
|
|
|
m_config->setArg("vor", m_location->ident());
|
|
|
|
setNavRadioOption();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FGPositioned::NDB:
|
|
|
|
m_config->setArg("ndb", m_location->ident());
|
|
|
|
setNavRadioOption();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case FGPositioned::FIX:
|
|
|
|
m_config->setArg("fix", m_location->ident());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2020-03-18 16:34:02 +00:00
|
|
|
}
|
2018-05-07 15:41:10 +00:00
|
|
|
|
|
|
|
// set disambiguation property
|
|
|
|
m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
|
|
|
|
applyPositionOffset();
|
2018-07-02 15:45:17 +00:00
|
|
|
applyAltitude();
|
2018-07-17 10:55:25 +00:00
|
|
|
applyAirspeed();
|
2018-05-07 15:41:10 +00:00
|
|
|
} // of navaid location
|
|
|
|
}
|
|
|
|
|
|
|
|
void LocationController::setNavRadioOption()
|
|
|
|
{
|
|
|
|
if (!m_tuneNAV1)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (m_location->type() == FGPositioned::VOR) {
|
|
|
|
FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
|
|
|
|
double mhz = nav->get_freq() / 100.0;
|
|
|
|
int heading = 0; // add heading support
|
|
|
|
QString navOpt = QString("%1:%2").arg(heading).arg(mhz);
|
|
|
|
m_config->setArg("nav1", navOpt);
|
|
|
|
} else {
|
|
|
|
FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
|
|
|
|
int khz = nav->get_freq() / 100;
|
|
|
|
int heading = 0;
|
|
|
|
QString adfOpt = QString("%1:%2").arg(heading).arg(khz);
|
|
|
|
m_config->setArg("adf1", adfOpt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString compassPointFromHeading(int heading)
|
|
|
|
{
|
|
|
|
const int labelArc = 360 / 8;
|
|
|
|
heading += (labelArc >> 1);
|
|
|
|
SG_NORMALIZE_RANGE(heading, 0, 359);
|
|
|
|
|
|
|
|
switch (heading / labelArc) {
|
|
|
|
case 0: return "N";
|
|
|
|
case 1: return "NE";
|
|
|
|
case 2: return "E";
|
|
|
|
case 3: return "SE";
|
|
|
|
case 4: return "S";
|
|
|
|
case 5: return "SW";
|
|
|
|
case 6: return "W";
|
|
|
|
case 7: return "NW";
|
|
|
|
}
|
|
|
|
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString LocationController::description() const
|
|
|
|
{
|
2020-04-05 22:13:32 +00:00
|
|
|
const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
if (!m_location) {
|
|
|
|
if (m_locationIsLatLon) {
|
2018-06-25 17:12:43 +00:00
|
|
|
const auto s = simgear::strutils::formatGeodAsString(m_geodLocation,
|
|
|
|
simgear::strutils::LatLonFormat::DECIMAL_DEGREES,
|
|
|
|
simgear::strutils::DegreeSymbol::UTF8_DEGREE);
|
|
|
|
return tr("at position %1").arg(QString::fromStdString(s));
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
2020-03-18 16:34:02 +00:00
|
|
|
if (m_locationIsCarrier) {
|
|
|
|
QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
|
2020-04-05 22:13:32 +00:00
|
|
|
QString locationToCarrier;
|
|
|
|
if (m_abeam) {
|
|
|
|
locationToCarrier = tr("%1nm abeam").arg(offsetNm);
|
|
|
|
} else if (m_useCarrierFLOLS) {
|
|
|
|
locationToCarrier = tr("on %1nm final to").arg(offsetNm);
|
|
|
|
} else {
|
|
|
|
locationToCarrier = tr("on deck at %1 on").arg(m_carrierParking);
|
|
|
|
}
|
|
|
|
return tr("%1 carrier %2 (%3)").arg(locationToCarrier).arg(m_carrierName).arg(pennant);
|
2020-03-18 16:34:02 +00:00
|
|
|
}
|
|
|
|
|
2018-05-07 15:41:10 +00:00
|
|
|
return tr("No location selected");
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ident = QString::fromStdString(m_location->ident()),
|
|
|
|
name = QString::fromStdString(m_location->name());
|
|
|
|
|
|
|
|
name = fixNavaidName(name);
|
|
|
|
|
|
|
|
if (m_airportLocation) {
|
|
|
|
const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
|
|
|
|
const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
|
|
|
|
QString locationOnAirport;
|
|
|
|
|
|
|
|
if (m_useActiveRunway) {
|
|
|
|
if (m_onFinal) {
|
2018-07-17 10:55:25 +00:00
|
|
|
locationOnAirport = tr("on %1-mile final to active runway").arg(offsetNm);
|
2018-05-07 15:41:10 +00:00
|
|
|
} else {
|
|
|
|
locationOnAirport = tr("on active runway");
|
|
|
|
}
|
2019-01-08 23:21:28 +00:00
|
|
|
} else if (m_useAvailableParking) {
|
|
|
|
locationOnAirport = tr("at an available parking position");
|
2018-05-07 15:41:10 +00:00
|
|
|
} if (onRunway) {
|
|
|
|
QString runwayName = QString("runway %1").arg(QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
|
|
|
|
if (m_onFinal) {
|
2018-07-17 10:55:25 +00:00
|
|
|
locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(offsetNm);
|
2018-05-07 15:41:10 +00:00
|
|
|
} else {
|
|
|
|
locationOnAirport = tr("on %1").arg(runwayName);
|
|
|
|
}
|
|
|
|
} else if (atParking) {
|
|
|
|
locationOnAirport = tr("at parking position %1").arg(QString::fromStdString(m_detailLocation->ident()));
|
|
|
|
}
|
|
|
|
|
|
|
|
return tr("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport);
|
|
|
|
} else {
|
|
|
|
QString offsetDesc = tr("at");
|
|
|
|
if (m_offsetEnabled) {
|
|
|
|
offsetDesc = tr("%1nm %2 of").
|
2018-07-17 10:55:25 +00:00
|
|
|
arg(offsetNm, 0, 'f', 1).
|
2020-03-18 16:34:02 +00:00
|
|
|
arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
|
2018-05-07 15:41:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString navaidType;
|
|
|
|
switch (m_location->type()) {
|
|
|
|
case FGPositioned::VOR:
|
|
|
|
navaidType = QString("VOR"); break;
|
|
|
|
case FGPositioned::NDB:
|
|
|
|
navaidType = QString("NDB"); break;
|
|
|
|
case FGPositioned::FIX:
|
|
|
|
return tr("%2 waypoint %1").arg(ident).arg(offsetDesc);
|
|
|
|
default:
|
|
|
|
// unsupported type
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return tr("%4 %1 %2 (%3)").arg(navaidType).arg(ident).arg(name).arg(offsetDesc);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tr("No location selected");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void LocationController::addToRecent(FGPositionedRef pos)
|
|
|
|
{
|
|
|
|
auto it = std::find(m_recentLocations.begin(),
|
|
|
|
m_recentLocations.end(), pos);
|
|
|
|
if (it != m_recentLocations.end()) {
|
|
|
|
m_recentLocations.erase(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_recentLocations.size() >= MAX_RECENT_LOCATIONS) {
|
|
|
|
m_recentLocations.pop_back();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_recentLocations.insert(m_recentLocations.begin(), pos);
|
|
|
|
QSettings settings;
|
|
|
|
settings.setValue("recent-locations", savePositionList(m_recentLocations));
|
|
|
|
}
|