FlightGear next
LocationController.cxx
Go to the documentation of this file.
1// LocationController.cxx - GUI launcher dialog using Qt5
2//
3// Written by James Turner, started October 2015.
4//
5// Copyright (C) 2015 James Turner <zakalawe@mac.com>
6//
7// This program is free software; you can redistribute it and/or
8// modify it under the terms of the GNU General Public License as
9// published by the Free Software Foundation; either version 2 of the
10// License, or (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful, but
13// WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15// General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License
18// along with this program; if not, write to the Free Software
19// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
22
23#include <QSettings>
24#include <QAbstractListModel>
25#include <QTimer>
26#include <QDebug>
27#include <QQmlComponent>
28#include <QQmlEngine>
29
30#include <simgear/misc/strutils.hxx>
31#include <simgear/structure/exception.hxx>
32
33#include "AirportDiagram.hxx"
34#include "CarrierDiagram.hxx"
37#include "LaunchConfig.hxx"
38#include "NavaidDiagram.hxx"
39#include "NavaidSearchModel.hxx"
41
42#include <Airports/airport.hxx>
44
45#include <Main/globals.hxx>
47#include <Navaids/navrecord.hxx>
48#include <Main/options.hxx>
49#include <Main/fg_init.hxx>
50#include <Main/fg_props.hxx> // for fgSetDouble
51
52using namespace flightgear;
53
54const unsigned int MAX_RECENT_LOCATIONS = 20;
55
56QVariant savePositionList(const FGPositionedList& posList)
57{
58 QVariantList vl;
59 for (const auto& pos : posList) {
60 QVariantMap vm;
61 vm.insert("ident", QString::fromStdString(pos->ident()));
62 vm.insert("type", pos->type());
63 vm.insert("lat", pos->geod().getLatitudeDeg());
64 vm.insert("lon", pos->geod().getLongitudeDeg());
65 vl.append(vm);
66 }
67 return vl;
68}
69
71{
72 QVariantList vl = v.toList();
73 FGPositionedList result;
74 result.reserve(static_cast<size_t>(vl.size()));
76
77 Q_FOREACH(QVariant v, vl) {
78 QVariantMap vm = v.toMap();
79 std::string ident(vm.value("ident").toString().toStdString());
80 double lat = vm.value("lat").toDouble();
81 double lon = vm.value("lon").toDouble();
82 FGPositioned::Type ty(static_cast<FGPositioned::Type>(vm.value("type").toInt()));
83 FGPositioned::TypeFilter filter(ty);
84 FGPositionedRef pos = cache->findClosestWithIdent(ident,
85 SGGeod::fromDeg(lon, lat),
86 &filter);
87 if (pos)
88 result.push_back(pos);
89 }
90
91 return result;
92}
93
95 QObject(parent)
96{
97 qmlRegisterUncreatableType<QmlPositionedModel>("FlightGear.Launcher", 1, 0, "QmlPositionedModel", "no");
98
99 m_searchModel = new NavaidSearchModel(this);
100 m_detailQml = new QmlPositioned(this);
101 m_baseQml = new QmlPositioned(this);
102 m_carriersModel = new CarriersLocationModel(this);
103 m_runwaysModel = new QmlPositionedModel(this);
104 m_helipadsModel = new QmlPositionedModel(this);
105 m_parkingsModel = new QmlPositionedModel(this);
106
107 m_defaultAltitude = QuantityValue{Units::FeetMSL, 6000};
108 m_defaultAirspeed = QuantityValue{Units::Knots, 120};
109 m_defaultHeading = QuantityValue{Units::DegreesTrue, 0};
110 m_defaultOffsetDistance = QuantityValue{Units::NauticalMiles, 1.0};
111 m_defaultOffsetRadial = QuantityValue{Units::DegreesTrue, 90};
112
113 // chain location and offset updated to description
120}
121
123
125{
126 m_config = config;
127 connect(m_config, &LaunchConfig::collect, this, &LocationController::onCollectConfig);
128 connect(m_config, &LaunchConfig::save, this, &LocationController::onSaveCurrentLocation);
129 connect(m_config, &LaunchConfig::restore, this, &LocationController::onRestoreCurrentLocation);
130}
131
133{
134 QSettings settings;
135 m_recentLocations = loadPositionedList(settings.value("recent-locations"));
136}
137
138void LocationController::onRestoreCurrentLocation()
139{
140 QVariantMap vm = m_config->getValueForKey("", "current-location", QVariantMap()).toMap();
141 if (vm.empty())
142 return;
143
144 restoreLocation(vm);
145}
146
147void LocationController::onSaveCurrentLocation()
148{
149 m_config->setValueForKey("", "current-location", saveLocation());
150}
151
153{
154 if (m_airportLocation) {
155 if (m_useAvailableParking)
156 return true;
157
158 if (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING)) {
159 return true;
160 }
161 }
162
163 // treat all other ground starts as taxi or on runway, i.e engines
164 // running if possible
165 return false;
166}
167
169{
170 const bool altIsPositive = (m_altitude.value > 0);
171
172 if (m_locationIsLatLon) {
173 return m_altitudeEnabled && altIsPositive;
174 }
175
176 if (m_airportLocation) {
177 const bool onRunway =
178 (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY)) ||
179 m_useActiveRunway;
180
181 if (onRunway && m_onFinal) {
182 // in this case no altitude might be set, but we assume
183 // it's still an airborne position
184 return true;
185 }
186
187 return false;
188 }
189
190 // relative to a navaid or fix - base off altitude.
191 return m_altitudeEnabled && altIsPositive;
192}
193
195{
196 return m_offsetRadial;
197}
198
200{
201 if (m_locationIsLatLon && (m_geodLocation == geod.geod()))
202 return;
203
204 clearLocation();
205 m_locationIsLatLon = true;
206 m_geodLocation = geod.geod();
207 emit baseLocationChanged();
208}
209
211{
212 return m_carrierName;
213}
214
216{
217 const auto cIndex = m_carriersModel->indexOf(name);
218 clearLocation();
219 if (cIndex < 0) {
220 qWarning() << "invalid carrier name:" << name;
221 return;
222 }
223
224 m_locationIsCarrier = true;
225 m_carrierName = name;
226 m_geodLocation = m_carriersModel->geodForIndex(cIndex);
227 m_carrierParkings = m_carriersModel->parkingsForIndex(cIndex);
228
229 emit baseLocationChanged();
230}
231
232void LocationController::clearLocation()
233{
234 m_locationIsLatLon = false;
235 m_locationIsCarrier = false;
236 m_abeam = false;
237 m_location.clear();
238 m_carrierName.clear();
239 m_airportLocation.clear();
240 m_detailLocation.clear();
241 m_detailQml->setGuid(0);
242 m_baseQml->setGuid(0);
243 m_carrierParkings.clear();
244 m_carrierParking.clear();
245 m_runwaysModel->clear();
246 m_helipadsModel->clear();
247 m_parkingsModel->clear();
248 emit baseLocationChanged();
249}
250
252{
253 if (!pos) {
254 clearLocation();
255 return;
256 }
257
258 if (pos->inner() == m_location)
259 return;
260
261 clearLocation();
262 m_location = pos->inner();
263 m_baseQml->setGuid(pos->guid());
264
265 if (FGPositioned::isAirportType(m_location.ptr())) {
266 m_airportLocation = static_cast<FGAirport*>(m_location.ptr());
267 // disable offset when selecting a heliport
268 if (m_airportLocation->isHeliport()) {
269 m_onFinal = false;
270 m_useActiveRunway = false;
271 }
272
273 updateAirportModels();
274 } else {
275 m_airportLocation.clear();
276 }
277
278 emit offsetChanged();
279 emit baseLocationChanged();
280}
281
282void LocationController::updateAirportModels()
283{
284 m_runwaysModel->setValues(m_airportLocation->getRunways());
285
286 FGPositionedList helipads;
287 for (unsigned int r = 0; r < m_airportLocation->numHelipads(); ++r) {
288 helipads.push_back(m_airportLocation->getHelipadByIndex(r));
289 }
290
291 m_helipadsModel->setValues(helipads);
292 m_parkingsModel->setValues(m_airportLocation->groundNetwork()->allParkings());
293}
294
296{
297 if (pos && (pos->inner() == m_detailLocation))
298 return;
299
300 if (!pos) {
301 m_detailLocation.clear();
302 m_detailQml->setInner({});
303 } else {
304 m_detailLocation = pos->inner();
305 m_useActiveRunway = false;
306 m_useAvailableParking = false;
307 m_detailQml->setInner(pos->inner());
308 }
309
310 emit configChanged();
311}
312
314{
315 if (m_locationIsLatLon || m_locationIsCarrier)
316 return m_geodLocation;
317
318 if (m_location)
319 return QmlGeod(m_location->geod());
320
321 return {};
322}
323
325{
326 return m_airportLocation;
327}
328
330{
331 if (b == m_useActiveRunway)
332 return;
333
334 m_useActiveRunway = b;
335 if (m_useActiveRunway) {
336 m_detailLocation.clear(); // clear any specific runway
337 m_useAvailableParking = false;
338 }
339 emit configChanged();
340}
341
346
348{
349 // prepend the default location and tutorial airport
350
351 FGPositionedList locs = m_recentLocations;
352 const std::string defaultICAO = flightgear::defaultAirportICAO();
353 const std::string tutorialICAO = "PHTO"; // C172P tutorial aiurport
354
355 // remove them from the recent locations
356 auto it = std::remove_if(locs.begin(), locs.end(),
357 [defaultICAO, tutorialICAO](FGPositionedRef pos)
358 {
359 return (pos->ident() == defaultICAO) || (pos->ident() == tutorialICAO);
360 });
361 locs.erase(it, locs.end());
362
363 // prepend them
364 FGAirportRef apt = FGAirport::findByIdent(tutorialICAO);
365 if (apt) {
366 locs.insert(locs.begin(), apt);
367 } else {
368 qWarning() << Q_FUNC_INFO << "couldn't find tutorial airport for:" << QString::fromStdString(tutorialICAO);
369 }
370
371 apt = FGAirport::findByIdent(defaultICAO);
372 if (apt) {
373 locs.insert(locs.begin(), apt);
374 } else {
375 qWarning() << Q_FUNC_INFO << "couldn't find default airport for:" << QString::fromStdString(defaultICAO);
376 }
377
378 // Sentry FLIGHTGEAR-1BM indicates we are somtimes passing null pointers
379 // in this list; checks above are an attempt to diagnose this.
380 m_searchModel->setItems(locs);
381}
382
384{
385 SGGeod g;
386 if (!simgear::strutils::parseStringAsGeod(string.toStdString(), &g)) {
387 return {};
388 }
389
390 return QmlGeod(g);
391}
392
394{
395 if (!m_locationIsCarrier)
396 return {};
397 return m_carrierParking;
398}
399
401{
402 if (!m_locationIsCarrier) {
403 qWarning() << "active location is not a carrier";
404 return;
405 }
406
407 if (m_carrierParking == name)
408 return;
409
410 if (!m_carrierParkings.contains(name)) {
411 qWarning() << "parking '" << name << "' not found in carrier parking list";
412 return;
413 }
414
415 m_carrierParking = name;
416 m_useCarrierFLOLS = false;
417 emit configChanged();
418}
419
421{
422 return m_detailQml;
423}
424
426{
427 return m_baseQml;
428}
429
431{
432 return m_carrierParkings;
433}
434
436{
437 if (m_offsetRadial == offsetRadial)
438 return;
439
440 m_offsetRadial = offsetRadial;
441 emit offsetChanged();
442}
443
445{
446 if (m_offsetDistance == d)
447 return;
448
449 m_offsetDistance = d;
450 emit offsetChanged();
451}
452
454{
455 if (m_offsetEnabled == offsetEnabled)
456 return;
457
458 m_offsetEnabled = offsetEnabled;
459 emit offsetChanged();
460}
461
463{
464 if (m_onFinal == onFinal)
465 return;
466
467 m_onFinal = onFinal;
468 emit configChanged();
469}
470
472{
473 if (m_tuneNAV1 == tuneNAV1)
474 return;
475
476 m_tuneNAV1 = tuneNAV1;
477 emit configChanged();
478}
479
481{
482 if (m_useAvailableParking == useAvailableParking)
483 return;
484
485 m_useAvailableParking = useAvailableParking;
486 if (m_useAvailableParking) {
487 m_detailLocation.clear(); // clear any specific runway
488 m_useActiveRunway = false;
489 }
490 emit configChanged();
491}
492
494{
495 if (!m_locationIsCarrier) {
496 qWarning() << "location is not a carrier";
497 return;
498 }
499
500 if (m_useCarrierFLOLS == useCarrierFLOLS)
501 return;
502
503 m_useCarrierFLOLS = useCarrierFLOLS;
504 m_carrierParking.clear();
505 emit configChanged();
506}
507
509{
510 m_abeam = abeam;
511 emit configChanged();
512}
513
515{
516 clearLocation();
517
518 try {
519 m_altitudeEnabled = l.contains("altitude");
520 m_speedEnabled = l.contains("speed");
521 m_headingEnabled = l.contains("heading");
522
523 m_altitude = l.value("altitude", QVariant::fromValue(m_defaultAltitude)).value<QuantityValue>();
524 m_airspeed = l.value("speed", QVariant::fromValue(m_defaultAirspeed)).value<QuantityValue>();
525 m_heading = l.value("heading", QVariant::fromValue(m_defaultHeading)).value<QuantityValue>();
526
527 m_offsetEnabled = l.value("offset-enabled").toBool();
528 m_offsetRadial = l.value("offset-bearing", QVariant::fromValue(m_defaultOffsetRadial)).value<QuantityValue>();
529 m_offsetDistance = l.value("offset-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
530 m_tuneNAV1 = l.value("tune-nav1-radio").toBool();
531
532 if (l.contains("carrier")) {
533 setCarrierLocation(l.value("carrier").toString());
534 if (l.contains("carrier-flols")) {
535 setUseCarrierFLOLS(l.value("carrier-flols").toBool());
536 setAbeam(l.value("abeam").toBool());
537 // overwrite value form above, intentionally
538 m_offsetDistance = l.value("location-carrier-flols-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
539 } else if (l.contains("carrier-parking")) {
540 setCarrierParking(l.value("carrier-parking").toString());
541 }
542 } else if (l.contains("location-apt")) {
543 const auto icao = l.value("location-apt").toString().toStdString();
544 m_airportLocation = FGAirport::findByIdent(icao);
545 m_location = m_airportLocation;
546 m_baseQml->setInner(m_location);
547 } else if (l.contains("location-navaid")) {
548 const auto ident = l.value("location-navaid").toString().toStdString();
549
550 // we need lat/lon to disambiguate globally
551 const SGGeod vicinity = SGGeod::fromDeg(l.value("vicinity-lon").toDouble(),
552 l.value("vicinity-lat").toDouble());
553
554 // special-case TACANs so we don't get an ambiguity on VORTACs, where
555 // there would be a double hit on any ident
556 const auto isTacan = l.value("location-is-tacan").toBool();
557 if (isTacan) {
559 m_location = FGPositioned::findClosestWithIdent(ident, vicinity, &filter);
560 } else {
563 m_location = FGPositioned::findClosestWithIdent(ident, vicinity, &filter);
564 }
565
566 m_baseQml->setInner(m_location);
567 } else if (l.contains("location-lat")) {
568 m_locationIsLatLon = true;
569 m_geodLocation = SGGeod::fromDeg(l.value("location-lon").toDouble(),
570 l.value("location-lat").toDouble());
571 }
572
573 if (m_airportLocation) {
574 m_useActiveRunway = false;
575 m_useAvailableParking = false;
576
577 if (l.contains("location-apt-runway")) {
578 QString runway = l.value("location-apt-runway").toString().toUpper();
579 const auto runwayStr = runway.toStdString();
580 if (runway == QStringLiteral("ACTIVE")) {
581 m_useActiveRunway = true;
582 } else if (m_airportLocation->isHeliport()) {
583 m_detailLocation = m_airportLocation->getHelipadByIdent(runwayStr);
584 } else {
585 // could be a helipad at a regular airport
586 if (m_airportLocation->hasHelipadWithIdent(runwayStr)) {
587 m_detailLocation = m_airportLocation->getHelipadByIdent(runwayStr);
588 } else {
589 m_detailLocation = m_airportLocation->getRunwayByIdent(runwayStr);
590 }
591 }
592 } else if (l.contains("location-apt-parking")) {
593 QString parking = l.value("location-apt-parking").toString();
594 if (parking == QStringLiteral("AVAILABLE")) {
595 m_useAvailableParking = true;
596 } else {
597 m_detailLocation = m_airportLocation->groundNetwork()->findParkingByName(parking.toStdString());
598 }
599 }
600
601 if (m_detailLocation) {
602 m_detailQml->setInner(m_detailLocation);
603 }
604
605 m_onFinal = l.value("location-on-final").toBool();
606 setAbeam(l.value("abeam").toBool());
607 m_offsetDistance = l.value("location-apt-final-distance", QVariant::fromValue(m_defaultOffsetDistance)).value<QuantityValue>();
608 updateAirportModels();
609
610 } // of location is an airport
611 } catch (const sg_exception&) {
612 qWarning() << "Errors restoring saved location, clearing";
613 clearLocation();
614 }
615
619}
620
622{
623 if (m_useCarrierFLOLS) {
624 return true;
625 }
626
627 if (!m_location) {
628 return false; // defaults to on-ground at the default airport
629 }
630
631 if (m_airportLocation) {
632 return m_onFinal;
633 } else {
634 // navaid, start paused
635 return true;
636 }
637}
638
640{
641 QVariantMap locationSet;
642 if (m_locationIsLatLon) {
643 locationSet.insert("location-lat", m_geodLocation.getLatitudeDeg());
644 locationSet.insert("location-lon", m_geodLocation.getLongitudeDeg());
645 } else if (m_locationIsCarrier) {
646 locationSet.insert("carrier", m_carrierName);
647 if (m_useCarrierFLOLS) {
648 locationSet.insert("carrier-flols", true);
649 locationSet.insert("location-carrier-flols-distance", QVariant::fromValue(m_offsetDistance));
650 locationSet.insert("abeam", m_abeam);
651 } else if (!m_carrierParking.isEmpty()) {
652 locationSet.insert("carrier-parking", m_carrierParking);
653 }
654 } else if (m_location) {
655 if (m_airportLocation) {
656 locationSet.insert("location-apt", QString::fromStdString(m_airportLocation->ident()));
657 locationSet.insert("location-on-final", m_onFinal);
658 locationSet.insert("location-apt-final-distance", QVariant::fromValue(m_offsetDistance));
659 locationSet.insert("abeam", m_abeam);
660 if (m_useActiveRunway) {
661 locationSet.insert("location-apt-runway", "ACTIVE");
662 } else if (m_useAvailableParking) {
663 locationSet.insert("location-apt-parking", "AVAILABLE");
664 } else if (m_detailLocation) {
665 const auto detailType = m_detailLocation->type();
666 if ((detailType == FGPositioned::RUNWAY) || (detailType == FGPositioned::HELIPAD)) {
667 locationSet.insert("location-apt-runway", QString::fromStdString(m_detailLocation->ident()));
668 } else if (detailType == FGPositioned::PARKING) {
669 locationSet.insert("location-apt-parking", QString::fromStdString(m_detailLocation->ident()));
670 }
671 }
672
673 } else { // not an aiport, must be a navaid
674 locationSet.insert("location-navaid", QString::fromStdString(m_location->ident()));
675 if (m_location->type() == FGPositioned::DME) {
676 // so we don't get ambiguous on VORTACs, explicity mark TACANs
677 // otherwise every VORTAC would be ambiguous
678 locationSet.insert("location-is-tacan", true);
679 }
680 locationSet.insert("vicinity-lat", m_location->geod().getLatitudeDeg());
681 locationSet.insert("vicinity-lon", m_location->geod().getLongitudeDeg());
682
683 } // of m_location is valid
684 }
685
686 if (m_altitudeEnabled) {
687 locationSet.insert("altitude", QVariant::fromValue(m_altitude));
688 }
689
690 if (m_speedEnabled) {
691 locationSet.insert("speed", QVariant::fromValue(m_airspeed));
692 }
693
694 if (m_headingEnabled) {
695 locationSet.insert("heading", QVariant::fromValue(m_heading));
696 }
697
698 if (m_offsetEnabled) {
699 locationSet.insert("offset-enabled", m_offsetEnabled);
700 locationSet.insert("offset-bearing", QVariant::fromValue(m_offsetRadial));
701 locationSet.insert("offset-distance", QVariant::fromValue(m_offsetDistance));
702 }
703
704 locationSet.insert("text", description());
705 locationSet.insert("tune-nav1-radio", m_tuneNAV1);
706
707 return locationSet;
708}
709
711{
712 SGPropertyNode_ptr presets = fgGetNode("/sim/presets", true);
713
714 QStringList props = QStringList() << "vor-id"
715 << "fix"
716 << "ndb-id"
717 << "tacan-id"
718 << "runway-requested"
719 << "navaid-id"
720 << "offset-azimuth-deg"
721 << "offset-distance-nm"
722 << "glideslope-deg"
723 << "speed-set"
724 << "on-ground"
725 << "airspeed-kt"
726 << "airport-id"
727 << "runway"
728 << "parkpos"
729 << "carrier"
730 << "carrier-position";
731
732 Q_FOREACH(QString s, props) {
733 SGPropertyNode* c = presets->getChild(s.toStdString());
734 if (c) {
735 c->clearValue();
736 }
737 }
738
739 if (m_locationIsLatLon) {
740 fgSetDouble("/sim/presets/latitude-deg", m_geodLocation.getLatitudeDeg());
741 fgSetDouble("/position/latitude-deg", m_geodLocation.getLatitudeDeg());
742 fgSetDouble("/sim/presets/longitude-deg", m_geodLocation.getLongitudeDeg());
743 fgSetDouble("/position/longitude-deg", m_geodLocation.getLongitudeDeg());
744
745 applyPositionOffset();
746 applyAltitude();
747 applyAirspeed();
748 return;
749 }
750
751 fgSetDouble("/sim/presets/latitude-deg", 9999.0);
752 fgSetDouble("/sim/presets/longitude-deg", 9999.0);
753 fgSetDouble("/sim/presets/altitude-ft", -9999.0);
754 fgSetDouble("/sim/presets/heading-deg", 9999.0);
755
756 if (m_locationIsCarrier) {
757 fgSetString("/sim/presets/carrier", m_carrierName.toStdString());
758
759 if (m_useCarrierFLOLS) {
760 if (m_abeam) {
761 fgSetString("/sim/presets/carrier-position", "abeam");
762 } else {
763 fgSetString("/sim/presets/carrier-position", "FLOLS");
764 }
765 fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
766 applyAltitude();
767 applyAirspeed();
768 } else if (!m_carrierParking.isEmpty()) {
769 fgSetString("/sim/presets/carrier-position", m_carrierParking.toStdString());
770 }
771
772 if (m_tuneNAV1) {
773 // tune TACAN to the carrier
774 qInfo() << "Implement TACAN tuning";
775 }
776 return;
777 }
778
779 if (!m_location) {
780 return;
781 }
782
783 if (m_airportLocation) {
784 fgSetString("/sim/presets/airport-id", m_airportLocation->ident());
785 fgSetBool("/sim/presets/on-ground", true);
786 fgSetBool("/sim/presets/airport-requested", true);
787 fgSetBool("/sim/presets/abeam", m_abeam);
788
789 const bool onRunway = (m_detailLocation && (m_detailLocation->type() == FGPositioned::RUNWAY));
790 const bool atParking = (m_detailLocation && (m_detailLocation->type() == FGPositioned::PARKING));
791 if (m_useActiveRunway) {
792 // automatic runway choice
793 // we can't set navaid here
794 } else if (m_useAvailableParking) {
795 fgSetString("/sim/presets/parkpos", "AVAILABLE");
796 } else if (onRunway) {
797 if (m_airportLocation->type() == FGPositioned::AIRPORT) {
798 // explicit runway choice (this also works for helipads)
799 fgSetString("/sim/presets/runway", m_detailLocation->ident() );
800 fgSetBool("/sim/presets/runway-requested", true );
801
802 // set nav-radio 1 based on selected runway
803 FGRunway* runway = static_cast<FGRunway*>(m_detailLocation.ptr());
804 if (m_tuneNAV1 && runway->ILS()) {
805 double mhz = runway->ILS()->get_freq() / 100.0;
806 fgSetDouble("/instrumentation/nav[0]/radials/selected-deg", runway->headingDeg());
807 fgSetDouble("/instrumentation/nav[0]/frequencies/selected-mhz", mhz);
808 }
809
810 if (m_onFinal) {
811 fgSetDouble("/sim/presets/glideslope-deg", 3.0);
812 fgSetDouble("/sim/presets/offset-distance-nm", m_offsetDistance.convertToUnit(Units::NauticalMiles).value);
813 fgSetBool("/sim/presets/on-ground", false);
814 }
815 } else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
816 // explicit pad choice
817 fgSetString("/sim/presets/runway", m_detailLocation->ident() );
818 fgSetBool("/sim/presets/runway-requested", true );
819 }
820 } else if (atParking) {
821 // parking selection
822 fgSetString("/sim/presets/parkpos", m_detailLocation->ident());
823 }
824
825 updateAirportModels();
826 // of location is an airport
827 } else {
828 fgSetString("/sim/presets/airport-id", "");
829
830 // location is a navaid
831 // note setting the ident here is ambigious, we really only need and
832 // want the 'navaid-id' property. However setting the 'real' option
833 // gives a better UI experience (eg existing Position in Air dialog)
834 FGPositioned::Type ty = m_location->type();
835 switch (ty) {
837 fgSetString("/sim/presets/vor-id", m_location->ident());
838 setNavRadioOption();
839 break;
840
842 fgSetString("/sim/presets/ndb-id", m_location->ident());
843 setNavRadioOption();
844 break;
845
847 fgSetString("/sim/presets/fix", m_location->ident());
848 break;
849
850 // assume if a DME was selected, it was actually a TACAN
852 fgSetString("/sim/presets/tacan-id", m_location->ident());
853 break;
854
855 default:
856 break;
857 }
858
859 // set disambiguation property
860 globals->get_props()->setIntValue("/sim/presets/navaid-id",
861 static_cast<int>(m_location->guid()));
862
863 applyPositionOffset();
864 applyAltitude();
865 applyAirspeed();
866 } // of navaid location
867}
868
869void LocationController::applyAirspeed()
870{
871 if (m_speedEnabled && (m_airspeed.unit != Units::NoUnits)) {
872 if (m_airspeed.unit == Units::Knots) {
873 m_config->setArg("vc", QString::number(m_airspeed.value));
874 } else if (m_airspeed.unit == Units::KilometersPerHour) {
875 const double vc = m_airspeed.convertToUnit(Units::Knots).value;
876 m_config->setArg("vc", QString::number(vc));
877 } else if (m_airspeed.unit == Units::Mach) {
878 m_config->setArg("mach", QString::number(m_airspeed.value));
879 } else {
880 qWarning() << Q_FUNC_INFO << "unsupported airpseed unit" << m_airspeed.unit;
881 }
882 }
883}
884
885void LocationController::applyPositionOffset()
886{
887 if (m_headingEnabled && (m_heading.unit != Units::NoUnits)) {
888 if (m_heading.unit == Units::DegreesTrue) {
889 m_config->setArg("heading", QString::number(m_heading.value));
890 } else {
891 qWarning() << Q_FUNC_INFO << "unsupported heading unit" << m_heading.unit;
892 }
893 }
894
895 if (m_offsetEnabled) {
896 // flip direction of azimuth to balance the flip done in fgApplyStartOffset
897 // I don't know why that flip exists but changing it there will break
898 // command-line compatability so compensating here instead
899 int offsetAzimuth = static_cast<int>(m_offsetRadial.value) - 180;
900 m_config->setArg("offset-azimuth", QString::number(offsetAzimuth));
901 const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
902 m_config->setArg("offset-distance", QString::number(offsetNm));
903 }
904}
905
906void LocationController::applyAltitude()
907{
908 if (!m_altitudeEnabled)
909 return;
910
911 switch (m_altitude.unit) {
912 default:
913 qWarning() << Q_FUNC_INFO << "unsupported altitude unit";
914 break;
915 case Units::FeetMSL:
916 m_config->setArg("altitude", QString::number(m_altitude.value));
917 break;
918
919 case Units::FeetAGL:
920 // fixme - allow the sim to accpet AGL start position
921 m_config->setArg("altitude", QString::number(m_altitude.value));
922 break;
923
925 // FIXME - allow the sim to accept real FlightLevel arguments
926 m_config->setArg("altitude", QString::number(m_altitude.value * 100));
927 break;
928
930 m_config->setArg("altitude", QString::number(m_altitude.value));
931 break;
932
933 case Units::MetersMSL:
934 const double ftMSL = m_altitude.convertToUnit(Units::FeetMSL).value;
935 m_config->setArg("altitude", QString::number(ftMSL));
936 break;
937 }
938}
939
940void LocationController::applyOnFinal()
941{
942 if (m_onFinal) {
943 if (!m_altitudeEnabled) {
944 m_config->setArg("glideslope", std::string("3.0"));
945 }
946
947 const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
948 m_config->setArg("offset-distance", QString::number(offsetNm));
949 m_config->setArg("in-air", std::string(""));
950
951 applyAirspeed();
952 applyAltitude();
953 }
954}
955
956void LocationController::onCollectConfig()
957{
958 if (m_skipFromArgs) {
959 qWarning() << Q_FUNC_INFO << "skipping argument collection";
960 return;
961 }
962
963 if (m_locationIsLatLon) {
964 m_config->setArg("lat", QString::number(m_geodLocation.getLatitudeDeg(), 'f', 8));
965 m_config->setArg("lon", QString::number(m_geodLocation.getLongitudeDeg(), 'f', 8));
966 applyPositionOffset();
967 applyAltitude();
968 applyAirspeed();
969 return;
970 }
971
972 if (m_locationIsCarrier) {
973 m_config->setArg("carrier", m_carrierName);
974
975 if (!m_carrierParking.isEmpty()) {
976 m_config->setArg("carrier-position", m_carrierParking);
977 } else if (m_useCarrierFLOLS) {
978 if (m_abeam) {
979 m_config->setArg("carrier-position", QStringLiteral("abeam"));
980 } else {
981 m_config->setArg("carrier-position", QStringLiteral("FLOLS"));
982 }
983 const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
984 m_config->setArg("offset-distance", QString::number(offsetNm));
985
986 applyAltitude();
987 applyAirspeed();
988 }
989
990 return;
991 }
992
993 if (!m_location) {
994 return;
995 }
996
997 if (m_airportLocation) {
998 m_config->setArg("airport", QString::fromStdString(m_airportLocation->ident()));
999 const auto ty = m_detailLocation ? m_detailLocation->type() : FGPositioned::INVALID;
1000 const bool onRunway = (ty == FGPositioned::RUNWAY) || (ty == FGPositioned::HELIPAD);
1001 const bool atParking = ty == FGPositioned::PARKING;
1002
1003 if (m_useActiveRunway) {
1004 // pick by default
1005 applyOnFinal();
1006 } else if (m_useAvailableParking) {
1007 m_config->setArg("parking-id", QStringLiteral("AVAILABLE"));
1008 } else if (onRunway) {
1009 if (m_airportLocation->type() == FGPositioned::AIRPORT) {
1010 m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
1011
1012 if (ty == FGPositioned::RUNWAY) {
1013 // set nav-radio 1 based on selected runway
1014 auto runway = fgpositioned_cast<FGRunway>(m_detailLocation);
1015 if (runway->ILS()) {
1016 double mhz = runway->ILS()->get_freq() / 100.0;
1017 m_config->setArg("nav1", QString("%1:%2").arg(runway->headingDeg()).arg(mhz));
1018 }
1019
1020 applyOnFinal();
1021 }
1022
1023 } else if (m_airportLocation->type() == FGPositioned::HELIPORT) {
1024 m_config->setArg("runway", QString::fromStdString(m_detailLocation->ident()));
1025 }
1026 } else if (atParking) {
1027 // parking selection
1028 m_config->setArg("parking-id", QString::fromStdString(m_detailLocation->ident()));
1029 }
1030 // of location is an airport
1031 } else {
1032 // location is a navaid
1033 // note setting the ident here is ambigious, we really only need and
1034 // want the 'navaid-id' property. However setting the 'real' option
1035 // gives a better UI experience (eg existing Position in Air dialog)
1036 FGPositioned::Type ty = m_location->type();
1037 switch (ty) {
1038 case FGPositioned::VOR:
1039 m_config->setArg("vor", m_location->ident());
1040 setNavRadioOption();
1041 break;
1042
1043 case FGPositioned::NDB:
1044 m_config->setArg("ndb", m_location->ident());
1045 setNavRadioOption();
1046 break;
1047
1048 case FGPositioned::FIX:
1049 m_config->setArg("fix", m_location->ident());
1050 break;
1051
1052 case FGPositioned::DME:
1053 m_config->setArg("tacan", m_location->ident());
1054 break;
1055
1056 default:
1057 break;
1058 }
1059
1060 // set disambiguation property
1061 m_config->setProperty("/sim/presets/navaid-id", QString::number(m_location->guid()));
1062 applyPositionOffset();
1063 applyAltitude();
1064 applyAirspeed();
1065 } // of navaid location
1066}
1067
1068void LocationController::setNavRadioOption()
1069{
1070 if (!m_tuneNAV1)
1071 return;
1072
1073 if (m_location->type() == FGPositioned::VOR) {
1074 FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
1075 double mhz = nav->get_freq() / 100.0;
1076 int heading = 0; // add heading support
1077 QString navOpt = QString("%1:%2").arg(heading).arg(mhz);
1078 m_config->setArg("nav1", navOpt);
1079 } else {
1080 FGNavRecordRef nav(static_cast<FGNavRecord*>(m_location.ptr()));
1081 int khz = nav->get_freq() / 100;
1082 int heading = 0;
1083 QString adfOpt = QString("%1:%2").arg(heading).arg(khz);
1084 m_config->setArg("adf1", adfOpt);
1085 }
1086}
1087
1088QString LocationController::compassPointFromHeading(int heading) const
1089{
1090 const int labelArc = 360 / 8;
1091 heading += (labelArc >> 1);
1092 SG_NORMALIZE_RANGE(heading, 0, 359);
1093
1094 //
1095 switch (heading / labelArc) {
1096 case 0: return tr("N");
1097 case 1: return tr("NE");
1098 case 2: return tr("E");
1099 case 3: return tr("SE");
1100 case 4: return tr("S");
1101 case 5: return tr("SW");
1102 case 6: return tr("W");
1103 case 7: return tr("NW");
1104 }
1105
1106 return {};
1107}
1108
1110{
1111 const double offsetNm = m_offsetDistance.convertToUnit(Units::NauticalMiles).value;
1112
1113 if (!m_location) {
1114 if (m_locationIsLatLon) {
1115 const auto s = simgear::strutils::formatGeodAsString(m_geodLocation,
1116 simgear::strutils::LatLonFormat::DECIMAL_DEGREES,
1117 simgear::strutils::DegreeSymbol::UTF8_DEGREE);
1118 return tr("at position %1").arg(QString::fromStdString(s));
1119 }
1120
1121 if (m_locationIsCarrier) {
1122 QString pennant = m_carriersModel->pennantForIndex(m_carriersModel->indexOf(m_carrierName));
1123 QString locationToCarrier;
1124 if (m_abeam) {
1125 locationToCarrier = tr("%1nm abeam").arg(offsetNm);
1126 } else if (m_useCarrierFLOLS) {
1127 locationToCarrier = tr("on %1nm final to").arg(offsetNm);
1128 } else {
1129 locationToCarrier = tr("on deck at %1 on").arg(m_carrierParking);
1130 }
1131 return tr("%1 carrier %2 (%3)").arg(locationToCarrier).arg(m_carrierName).arg(pennant);
1132 }
1133
1134 return tr("No location selected");
1135 }
1136
1137 QString ident = QString::fromStdString(m_location->ident()),
1138 name = QString::fromStdString(m_location->name());
1139
1141
1142 if (m_airportLocation) {
1143 const auto ty = m_detailLocation ? m_detailLocation->type() : FGPositioned::INVALID;
1144 const bool onRunway = ty == FGPositioned::RUNWAY;
1145 const bool onPad = ty == FGPositioned::HELIPAD;
1146 const bool atParking = ty == FGPositioned::PARKING;
1147
1148 QString locationOnAirport;
1149
1150 if (m_useActiveRunway) {
1151 if (m_onFinal) {
1152 locationOnAirport = tr("on %1-mile final to active runway").arg(offsetNm);
1153 } else {
1154 locationOnAirport = tr("on active runway");
1155 }
1156 } else if (m_useAvailableParking) {
1157 locationOnAirport = tr("at an available parking position");
1158 } else if (onRunway) {
1159 QString runwayName = tr("runway %1").arg(QString::fromStdString(m_detailLocation->ident()));
1160
1161 if (m_onFinal) {
1162 locationOnAirport = tr("on %2-mile final to %1").arg(runwayName).arg(offsetNm);
1163 } else {
1164 locationOnAirport = tr("on %1").arg(runwayName);
1165 }
1166 } else if (onPad) {
1167 locationOnAirport = tr("on pad %1").arg(QString::fromStdString(m_detailLocation->ident()));
1168 } else if (atParking) {
1169 locationOnAirport = tr("at parking position %1").arg(QString::fromStdString(m_detailLocation->ident()));
1170 }
1171
1172 return tr("%2 (%1): %3").arg(ident).arg(name).arg(locationOnAirport);
1173 } else {
1174 QString offsetDesc = tr("at");
1175 if (m_offsetEnabled) {
1176 offsetDesc = tr("%1nm %2 of").
1177 arg(offsetNm, 0, 'f', 1).
1178 arg(compassPointFromHeading(static_cast<int>(m_offsetRadial.value)));
1179 }
1180
1181 QString navaidType;
1182 switch (m_location->type()) {
1183 case FGPositioned::VOR:
1184 navaidType = QString("VOR"); break;
1185 case FGPositioned::NDB:
1186 navaidType = QString("NDB"); break;
1187 case FGPositioned::DME:
1188 navaidType = QString("TACAN");
1189 break;
1190 case FGPositioned::FIX:
1191 return tr("%2 waypoint %1").arg(ident).arg(offsetDesc);
1192 default:
1193 // unsupported type
1194 break;
1195 }
1196
1197 return tr("%4 %1 %2 (%3)").arg(navaidType).arg(ident).arg(name).arg(offsetDesc);
1198 }
1199}
1200
1201
1203{
1204 if (!pos) {
1205 // ensure null positiones don't get added, could cause
1206 // Sentry FLIGHTGEAR-1BM
1207 return;
1208 }
1209
1210 auto it = std::find(m_recentLocations.begin(),
1211 m_recentLocations.end(), pos);
1212 if (it != m_recentLocations.end()) {
1213 m_recentLocations.erase(it);
1214 }
1215
1216 if (m_recentLocations.size() >= MAX_RECENT_LOCATIONS) {
1217 m_recentLocations.pop_back();
1218 }
1219
1220 m_recentLocations.insert(m_recentLocations.begin(), pos);
1221 QSettings settings;
1222 settings.setValue("recent-locations", savePositionList(m_recentLocations));
1223}
FGPositionedList loadPositionedList(QVariant v)
QVariant savePositionList(const FGPositionedList &posList)
const unsigned int MAX_RECENT_LOCATIONS
QString fixNavaidName(QString s)
SGSharedPtr< FGAirport > FGAirportRef
static FGAirportRef findByIdent(const std::string &aIdent)
Helper to look up an FGAirport instance by unique ident.
Definition airport.cxx:489
int get_freq() const
Definition navrecord.hxx:76
static FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, Filter *aFilter=NULL)
static bool isAirportType(FGPositioned *pos)
@ PARKING
parking position - might be a gate, or stand
@ DME
important that DME & TACAN are adjacent to keep the TacanFilter efficient - DMEs are proxies for TACA...
double headingDeg() const
Runway heading in degrees.
FGNavRecord * ILS() const
Definition runways.cxx:120
void restore()
void collect()
Q_INVOKABLE void setArg(QString name, QString value=QString(), Origin origin=Launcher)
Q_INVOKABLE QVariant getValueForKey(QString group, QString key, QVariant defaultValue=QVariant()) const
void setLaunchConfig(LaunchConfig *config)
void restoreLocation(QVariantMap l)
void setUseCarrierFLOLS(bool useCarrierFLOLS)
void setCarrierParking(QString name)
bool isAirborneLocation() const
used to automatically select aircraft state
void setAbeam(bool abeam)
void setOnFinal(bool onFinal)
QmlPositioned * baseLocation() const
Q_INVOKABLE void showHistoryInSearchModel()
Q_INVOKABLE void addToRecent(QmlPositioned *pos)
void setOffsetRadial(QuantityValue offsetRadial)
void setUseAvailableParking(bool useAvailableParking)
void setBaseLocation(FGPositionedRef ref)
void setBaseGeod(QmlGeod geod)
void setOffsetEnabled(bool offsetEnabled)
QVariantMap saveLocation() const
Q_INVOKABLE QmlGeod parseStringAsGeod(QString string) const
void setOffsetDistance(QuantityValue offsetNm)
bool isParkedLocation() const
used to automatically select aircraft state
void baseLocationChanged()
LocationController(QObject *parent=nullptr)
void setTuneNAV1(bool tuneNAV1)
QString carrierName() const
Q_INVOKABLE void setDetailLocation(QmlPositioned *pos)
void setCarrierLocation(QString name)
Expose an SGGeod as Qml-friendly class.
SGGeod geod() const
void setValues(const FGPositionedList &posItems)
FGPositionedRef inner() const
void setGuid(qlonglong guid)
Units::Type unit
@ KilometersPerHour
@ DegreesTrue
@ FlightLevel
@ MetersMSL
@ FeetAboveFieldElevation
@ NauticalMiles
FGPositionedRef findClosestWithIdent(const std::string &aIdent, const SGGeod &aPos, FGPositioned::Filter *aFilter)
static NavDataCache * instance()
const char * name
FGGlobals * globals
Definition globals.cxx:142
FlightPlan.hxx - defines a full flight-plan object, including departure, cruise, arrival information ...
Definition Addon.cxx:53
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.cxx:49
std::string defaultAirportICAO()
SGSharedPtr< FGNavRecord > FGNavRecordRef
T * fgpositioned_cast(FGPositioned *p)
std::vector< FGPositionedRef > FGPositionedList
bool fgSetDouble(const char *name, double defaultValue)
Set a double value for a property.
Definition proptest.cpp:31
bool fgSetBool(char const *name, bool val)
Set a bool value for a property.
Definition proptest.cpp:24
bool fgSetString(char const *name, char const *str)
Set a string value for a property.
Definition proptest.cpp:26
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27