From 64cee7fba8cffb500e32167a571fc140d76f8e47 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Sat, 9 Jul 2016 18:29:37 +0200 Subject: [PATCH] refs #706, callsign to airline plus testing resulting in multiple smaller improvements (refs #707) * added functions to callsign / callsign list * used in aircraft matcher * also resolve std.livery in matcher * also allow to find aircraft ICAO designator ending with string (e.g. 737 for B737) * renamed CAircraftMatcher::reverseLookup -> CAircraftMatcher::reverselLookupModel * threadsafe isInRange (CAIrspaceMonitor) --- src/blackcore/aircraftmatcher.cpp | 36 +++++-- src/blackcore/aircraftmatcher.h | 6 +- src/blackcore/airspacemonitor.cpp | 99 ++++++++++++++----- src/blackcore/airspacemonitor.h | 7 ++ src/blackcore/matchingutils.h | 1 - src/blackcore/webdataservices.cpp | 7 ++ src/blackcore/webdataservices.h | 4 + .../components/modelmatchercomponent.cpp | 4 +- .../aviation/aircrafticaocodelist.cpp | 24 +++++ src/blackmisc/aviation/aircrafticaocodelist.h | 4 + .../aviation/airlineicaocodelist.cpp | 13 ++- src/blackmisc/aviation/airlineicaocodelist.h | 5 +- src/blackmisc/aviation/callsign.cpp | 20 +++- src/blackmisc/aviation/callsign.h | 3 + src/blackmisc/aviation/liverylist.cpp | 8 +- src/blackmisc/simulation/aircraftmodel.h | 2 +- 16 files changed, 196 insertions(+), 47 deletions(-) diff --git a/src/blackcore/aircraftmatcher.cpp b/src/blackcore/aircraftmatcher.cpp index 29f8bf020..78cccb115 100644 --- a/src/blackcore/aircraftmatcher.cpp +++ b/src/blackcore/aircraftmatcher.cpp @@ -167,7 +167,7 @@ namespace BlackCore return matchedModel; } - CAircraftModel CAircraftMatcher::reverseLookup(const CAircraftModel &modelToLookup, const QString &networkLiveryInfo, CStatusMessageList *log) + CAircraftModel CAircraftMatcher::reverselLookupModel(const CAircraftModel &modelToLookup, const QString &networkLiveryInfo, CStatusMessageList *log) { Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing sApp"); Q_ASSERT_X(sApp->getWebDataServices(), Q_FUNC_INFO, "No web services"); @@ -279,8 +279,8 @@ namespace BlackCore const CAircraftIcaoCode icao = sApp->getWebDataServices()->smartAircraftIcaoSelector(designator); if (log) { - if (icao.hasValidDbKey()) { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup for ICAO '%1' found '%2'").arg(designator).arg(icao.getDesignator())); } - else { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of ICAO '%1'', nothing found").arg(designator)); } + if (icao.hasValidDbKey()) { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup for ICAO '%1' found '%2'").arg(designator).arg(icao.getDesignator()), CAircraftMatcher::getLogCategories()); } + else { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of ICAO '%1'', nothing found").arg(designator), CAircraftMatcher::getLogCategories()); } } return icao; } @@ -291,20 +291,40 @@ namespace BlackCore Q_ASSERT_X(sApp->getWebDataServices(), Q_FUNC_INFO, "No web services"); const QString designator(icaoDesignator.trimmed().toUpper()); - const CAirlineIcaoCode icao = sApp->getWebDataServices()->smartAirlineIcaoSelector(designator); + const CAirlineIcaoCode icao = sApp->getWebDataServices()->smartAirlineIcaoSelector(designator, callsign); if (log) { - if (icao.hasValidDbKey()) { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of airline '%1' found '%2'").arg(designator).arg(icao.getDesignator())); } - else { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of airline '%1', nothing found").arg(designator)); } + if (icao.hasValidDbKey()) { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of airline '%1' and callsign '%2' found '%3'").arg(designator).arg(callsign.asString()).arg(icao.getDesignator()), CAircraftMatcher::getLogCategories()); } + else { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of airline '%1' and callsign '%2', nothing found").arg(designator).arg(callsign.asString()), CAircraftMatcher::getLogCategories()); } } return icao; } + CLivery CAircraftMatcher::reverseLookupStandardLivery(const CAirlineIcaoCode &airline, const CCallsign &callsign, CStatusMessageList *log) + { + Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing sApp"); + Q_ASSERT_X(sApp->getWebDataServices(), Q_FUNC_INFO, "No web services"); + + if (!airline.hasValidDesignator()) + { + if (log) { CMatchingUtils::addLogDetailsToList(log, callsign, "Reverse lookup of standard livery skipped, no airline designator", CAircraftMatcher::getLogCategories(), CStatusMessage::SeverityWarning); } + return CLivery(); + } + + const CLivery livery = sApp->getWebDataServices()->getStdLiveryForAirlineCode(airline); + if (log) + { + if (livery.hasValidDbKey()) { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Reverse lookup of standard livery for '%1' found '%2'").arg(airline.getDesignator()).arg(livery.getCombinedCode()), CAircraftMatcher::getLogCategories()); } + else { CMatchingUtils::addLogDetailsToList(log, callsign, QString("Not standard livery for airline '%1'").arg(airline.getDesignator()), CAircraftMatcher::getLogCategories()); } + } + return livery; + } + int CAircraftMatcher::setModelSet(const CAircraftModelList &models) { CAircraftModelList modelsCleaned(models); - int r1 = modelsCleaned.removeAllWithoutModelString(); - int r2 = modelsCleaned.removeIfExcluded(); + const int r1 = modelsCleaned.removeAllWithoutModelString(); + const int r2 = modelsCleaned.removeIfExcluded(); if ((r1 + r2) > 0) { CLogMessage(this).warning("Removed models for matcher, without string %1, excluded %2") << r1 << r2; diff --git a/src/blackcore/aircraftmatcher.h b/src/blackcore/aircraftmatcher.h index 18386a8e2..5367bae9d 100644 --- a/src/blackcore/aircraftmatcher.h +++ b/src/blackcore/aircraftmatcher.h @@ -74,7 +74,7 @@ namespace BlackCore //! Try to find the corresponding data in DB and get best information for following matching //! \threadsafe - static BlackMisc::Simulation::CAircraftModel reverseLookup(const BlackMisc::Simulation::CAircraftModel &modelToLookup, const QString &networkLiveryInfo, BlackMisc::CStatusMessageList *log = nullptr); + static BlackMisc::Simulation::CAircraftModel reverselLookupModel(const BlackMisc::Simulation::CAircraftModel &modelToLookup, const QString &networkLiveryInfo, BlackMisc::CStatusMessageList *log = nullptr); //! Try to find the DB corresponding ICAO code //! \threadsafe @@ -84,6 +84,10 @@ namespace BlackCore //! \threadsafe static BlackMisc::Aviation::CAirlineIcaoCode reverseLookupAirlineIcao(const QString &icaoDesignator, const BlackMisc::Aviation::CCallsign &callsign = BlackMisc::Aviation::CCallsign(), BlackMisc::CStatusMessageList *log = nullptr); + //! Lookup of standard livery + //! \threadsafe + static BlackMisc::Aviation::CLivery reverseLookupStandardLivery(const BlackMisc::Aviation::CAirlineIcaoCode &airline, const BlackMisc::Aviation::CCallsign &callsign, BlackMisc::CStatusMessageList *log = nullptr); + //! Get the models BlackMisc::Simulation::CAircraftModelList getModelSet() const { return m_modelSet; } diff --git a/src/blackcore/airspacemonitor.cpp b/src/blackcore/airspacemonitor.cpp index 4ae03026d..127c7f06b 100644 --- a/src/blackcore/airspacemonitor.cpp +++ b/src/blackcore/airspacemonitor.cpp @@ -376,6 +376,13 @@ namespace BlackCore return this->getOtherClients().containsCallsign(callsign); } + bool CAirspaceMonitor::isInRange(const CCallsign &callsign) const + { + if (callsign.isEmpty()) { return false; } + QReadLocker l(&m_lockAircraft); + return m_aircraftInRange.containsCallsign(callsign); + } + CClientList CAirspaceMonitor::getOtherClients() const { QReadLocker l(&m_lockClient); @@ -653,7 +660,7 @@ namespace BlackCore // check if the name and ICAO query went properly through, those usually mean the aircraft are in range and can be used // this here is part of the reverse lookup process, where we turn FSD data into a model we assume the other user is flying - const bool maxTrialsReached = trial >= 3; + const bool maxTrialsReached = trial >= 2; const bool inRange = remoteAircraft.hasValidCallsign(); // found? otherwise we need to wait for some data bool validData = inRange && remoteAircraft.hasAircraftDesignator(); if (!maxTrialsReached && validData && !remoteAircraft.getModel().hasModelString()) @@ -852,7 +859,7 @@ namespace BlackCore } else { - CPropertyIndexVariantMap vm(CClient::IndexModelString, modelString); + const CPropertyIndexVariantMap vm(CClient::IndexModelString, modelString); this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); // update client info } } @@ -873,14 +880,18 @@ namespace BlackCore void CAirspaceMonitor::icaoOrFsdDataReceived(const CCallsign &callsign, const QString &aircraftIcaoDesignator, const QString &airlineIcaoDesignator, const QString &livery, const QString &modelString, CAircraftModel::ModelType type) { Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "not in main thread"); + Q_ASSERT_X(type == CAircraftModel::TypeFsdData || type == CAircraftModel::TypeQueriedFromNetwork, Q_FUNC_INFO, "Wrong type"); BLACK_VERIFY_X(callsign.isValid(), Q_FUNC_INFO, "invalid callsign"); if (!callsign.isValid()) { return; } if (!this->m_connected) { return; } - if (aircraftIcaoDesignator.isEmpty() && airlineIcaoDesignator.isEmpty() && livery.isEmpty()) { return; } + if (aircraftIcaoDesignator.isEmpty() && airlineIcaoDesignator.isEmpty() && livery.isEmpty() && modelString.isEmpty()) { return; } CStatusMessageList reverseLookupMessages; CStatusMessageList *pReverseLookupMessages = this->isReverseLookupMessagesEnabled() ? &reverseLookupMessages : nullptr; - CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("Data from network: aircraft '%1', airline '%2', livery '%3', model '%4'").arg(aircraftIcaoDesignator).arg(airlineIcaoDesignator).arg(livery).arg(modelString)); + CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("Data from network (%1): aircraft '%2', airline '%3', livery '%4', model '%5'"). + arg(CAircraftModel::modelTypeToString(type)). + arg(aircraftIcaoDesignator).arg(airlineIcaoDesignator). + arg(livery).arg(modelString)); const CSimulatedAircraft remoteAircraft(this->getAircraftInRangeForCallsign(callsign)); const bool existingAircraft = !remoteAircraft.getCallsign().isEmpty(); @@ -900,10 +911,10 @@ namespace BlackCore // already matched with DB? Means we already have DB data in cache or existing model if (!model.canInitializeFromFsd()) { return; } - // update model string if not yet existing - if (model.getModelType() == CAircraftModel::TypeUnknown || model.getModelType() == CAircraftModel::TypeQueriedFromNetwork) + // update type to FSD if applicable + if (model.getModelType() == CAircraftModel::TypeFsdData) { - model.setModelType(type); // update type if no type yet + model.setModelType(CAircraftModel::TypeFsdData); // update type if no type yet } // @@ -931,23 +942,50 @@ namespace BlackCore CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("No DB data for aircraft %1").arg(aircraftIcaoDesignator), getLogCategories()); } } - if (!model.hasAirlineDesignator() && !airlineIcaoDesignator.isEmpty()) + + // Derive airline from callsign + CAirlineIcaoCode airlineIcaoDesignatorReviewed(airlineIcaoDesignator); + if (type == CAircraftModel::TypeQueriedFromNetwork && airlineIcaoDesignator.isEmpty()) { - const CAirlineIcaoCode reverseIcao = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcaoDesignator, callsign, pReverseLookupMessages); - CLivery defaultLivery = model.getLivery(); - if (reverseIcao.hasValidDbKey()) + // try to conclude from callsign + airlineIcaoDesignatorReviewed = CAirspaceMonitor::callsignToAirline(callsign); + if (airlineIcaoDesignatorReviewed.hasValidDesignator()) { - defaultLivery.setAirlineIcaoCode(reverseIcao); + CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("Turned callsign %1 into airline %2").arg(callsign.asString()).arg(airlineIcaoDesignatorReviewed.getDesignator()), getLogCategories()); } else { - defaultLivery.setAirlineIcaoCode(airlineIcaoDesignator); - CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("No DB data for airline %1").arg(aircraftIcaoDesignator), getLogCategories()); + CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("Cannot turn callsign %1 into airline").arg(callsign.asString()), getLogCategories()); + } + } + + // Set livery / airline + if (!model.getLivery().hasValidDbKey()) + { + if (!model.hasAirlineDesignator() && airlineIcaoDesignatorReviewed.hasValidDesignator()) + { + const CAirlineIcaoCode reverseIcao = CAircraftMatcher::reverseLookupAirlineIcao(airlineIcaoDesignatorReviewed.getDesignator(), callsign, pReverseLookupMessages); + CLivery defaultLivery = CAircraftMatcher::reverseLookupStandardLivery(reverseIcao, callsign, pReverseLookupMessages); + if (!defaultLivery.hasValidDbKey()) + { + // No DB livery, create one + if (reverseIcao.hasValidDbKey()) + { + defaultLivery.setAirlineIcaoCode(reverseIcao); + } + else + { + defaultLivery.setAirlineIcaoCode(airlineIcaoDesignatorReviewed); + CMatchingUtils::addLogDetailsToList(pReverseLookupMessages, callsign, QString("No DB data for airline %1").arg(aircraftIcaoDesignator), getLogCategories()); + } + } } } // now try resolution to model from DB - model = CAircraftMatcher::reverseLookup(model, livery, &reverseLookupMessages); + model = CAircraftMatcher::reverselLookupModel(model, livery, &reverseLookupMessages); + + // messages if (this->m_enableReverseLookupMsgs && !reverseLookupMessages.isEmpty()) { this->addReverseLookupMessages(callsign, reverseLookupMessages); @@ -1000,6 +1038,13 @@ namespace BlackCore this->addReverseLookupMessage(callsign, m); } + CAirlineIcaoCode CAirspaceMonitor::callsignToAirline(const CCallsign &callsign) + { + if (callsign.isEmpty() || !sApp || !sApp->getWebDataServices()) { return CAirlineIcaoCode(); } + const CAirlineIcaoCode icao = sApp->getWebDataServices()->findBestMatchByCallsign(callsign); + return icao; + } + void CAirspaceMonitor::ps_aircraftUpdateReceived(const CAircraftSituation &situation, const CTransponder &transponder) { Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "Called in different thread"); @@ -1012,7 +1057,7 @@ namespace BlackCore this->storeAircraftSituation(situation); emit this->addedAircraftSituation(situation); - bool existsInRange = this->m_aircraftInRange.containsCallsign(callsign); + const bool existsInRange = this->isInRange(callsign); if (!existsInRange) { // new aircraft @@ -1048,7 +1093,7 @@ namespace BlackCore QWriteLocker l(&m_lockClient); if (!this->m_otherClients.containsCallsign(callsign)) { - CClient c(callsign); + const CClient c(callsign); this->m_otherClients.push_back(c); // initial, will be filled by data later } } @@ -1112,30 +1157,32 @@ namespace BlackCore Q_ASSERT_X(CThreadUtils::isCurrentThreadObjectThread(this), Q_FUNC_INFO, "Called in different thread"); if (!this->m_connected) { return; } - CCallsign callsign(situation.getCallsign()); + const CCallsign callsign(situation.getCallsign()); Q_ASSERT_X(!callsign.isEmpty(), Q_FUNC_INFO, "Empty callsign"); - // todo: Check if the timestamp is copied here as well. - // Interim packets do not have groundspeed, hence set the last known value. // If there is no full position available yet, throw this interim position away. CAircraftSituation interimSituation(situation); + CAircraftSituationList history; { QReadLocker l(&m_lockSituations); - auto history = this->m_situationsByCallsign[callsign]; - if (history.empty()) { return; } // we need one full situation - interimSituation.setCurrentUtcTime(); - interimSituation.setGroundSpeed(history.latestObject().getGroundSpeed()); + history = this->m_situationsByCallsign[callsign]; } + if (history.empty()) { return; } // we need one full situation at least + const CAircraftSituation lastSituation = history.latestObject(); + if (lastSituation.getPosition() == interimSituation.getPosition()) { return; } // save position, ignore + + // changed position, continue and copy values + interimSituation.setCurrentUtcTime(); + interimSituation.setGroundSpeed(lastSituation.getGroundSpeed()); // store situation history this->storeAircraftSituation(interimSituation); emit this->addedAircraftSituation(interimSituation); // update aircraft - //! \todo skip aircraft updates for interim positions as for performance reasons CLength distance = getOwnAircraft().calculateGreatCircleDistance(interimSituation.getPosition()); - distance.switchUnit(CLengthUnit::NM()); // lloks nicer + distance.switchUnit(CLengthUnit::NM()); // looks nicer CPropertyIndexVariantMap vm; vm.addValue(CSimulatedAircraft::IndexSituation, interimSituation); vm.addValue(CSimulatedAircraft::IndexDistanceToOwnAircraft, distance); diff --git a/src/blackcore/airspacemonitor.h b/src/blackcore/airspacemonitor.h index b8e236fd1..3795de17d 100644 --- a/src/blackcore/airspacemonitor.h +++ b/src/blackcore/airspacemonitor.h @@ -127,6 +127,10 @@ namespace BlackCore //! \threadsafe bool hasClientInfo(const BlackMisc::Aviation::CCallsign &callsign) const; + //! Is aircraft in range? + //! \threadsafe + bool isInRange(const BlackMisc::Aviation::CCallsign &callsign) const; + //! Returns the current online ATC stations BlackMisc::Aviation::CAtcStationList getAtcStationsOnline() const { return m_atcStationsOnline; } @@ -268,6 +272,9 @@ namespace BlackCore //! \threadsafe void addReverseLookupMessage(const BlackMisc::Aviation::CCallsign &callsign, const QString &message, BlackMisc::CStatusMessage::StatusSeverity severity = BlackMisc::CStatusMessage::SeverityInfo); + //! Turn callsign into airline + static BlackMisc::Aviation::CAirlineIcaoCode callsignToAirline(const BlackMisc::Aviation::CCallsign &callsign); + private slots: //! Create aircraft in range, this is the only place where a new aircraft should be added void ps_aircraftUpdateReceived(const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); diff --git a/src/blackcore/matchingutils.h b/src/blackcore/matchingutils.h index 214bb3de3..a9766bedb 100644 --- a/src/blackcore/matchingutils.h +++ b/src/blackcore/matchingutils.h @@ -24,7 +24,6 @@ namespace BlackCore class BLACKCORE_EXPORT CMatchingUtils { public: - //! Specialized log for matching / reverse lookup //! \threadsafe static void addLogDetailsToList( diff --git a/src/blackcore/webdataservices.cpp b/src/blackcore/webdataservices.cpp index 1f53f7928..3cf01d3c7 100644 --- a/src/blackcore/webdataservices.cpp +++ b/src/blackcore/webdataservices.cpp @@ -351,6 +351,13 @@ namespace BlackCore return CAirlineIcaoCode(); } + CAirlineIcaoCode CWebDataServices::findBestMatchByCallsign(const CCallsign &callsign) const + { + if (callsign.isEmpty()) { return CAirlineIcaoCode(); } + const CAirlineIcaoCodeList icaos(this->getAirlineIcaoCodes()); + return icaos.findBestMatchByCallsign(callsign); + } + CAirlineIcaoCode CWebDataServices::getAirlineIcaoCodeForDbKey(int key) const { if (m_icaoDataReader) { return m_icaoDataReader->getAirlineIcaoCodeForDbKey(key); } diff --git a/src/blackcore/webdataservices.h b/src/blackcore/webdataservices.h index 00ed5d069..a5008cfc4 100644 --- a/src/blackcore/webdataservices.h +++ b/src/blackcore/webdataservices.h @@ -246,6 +246,10 @@ namespace BlackCore //! \threadsafe BlackMisc::Aviation::CAirlineIcaoCode smartAirlineIcaoSelector(const BlackMisc::Aviation::CAirlineIcaoCode &code) const; + //! ICAO code for callsign (e.g. DLH123 -> DLH) + //! \threadsafe + BlackMisc::Aviation::CAirlineIcaoCode findBestMatchByCallsign(const BlackMisc::Aviation::CCallsign &callsign) const; + //! Countries //! \threadsafe BlackMisc::CCountryList getCountries() const; diff --git a/src/blackgui/components/modelmatchercomponent.cpp b/src/blackgui/components/modelmatchercomponent.cpp index 49b523a45..fd48edd99 100644 --- a/src/blackgui/components/modelmatchercomponent.cpp +++ b/src/blackgui/components/modelmatchercomponent.cpp @@ -112,7 +112,7 @@ namespace BlackGui if (this->ui->cb_withReverseLookup->isChecked()) { const QString liveryString(ui->comp_LiverySelector->getRawCombinedCode()); - const CAircraftModel reverseModel = CAircraftMatcher::reverseLookup(remoteAircraft.getModel(), liveryString, &msgs); + const CAircraftModel reverseModel = CAircraftMatcher::reverselLookupModel(remoteAircraft.getModel(), liveryString, &msgs); remoteAircraft.setModel(reverseModel); } @@ -129,7 +129,7 @@ namespace BlackGui this->m_matcher.setDefaultModel(CModelMatcherComponent::defaultModel()); const CSimulatedAircraft remoteAircraft(createAircraft()); const QString livery(ui->comp_LiverySelector->getRawCombinedCode()); - const CAircraftModel matched = CAircraftMatcher::reverseLookup(remoteAircraft.getModel(), livery, &msgs); + const CAircraftModel matched = CAircraftMatcher::reverselLookupModel(remoteAircraft.getModel(), livery, &msgs); ui->te_Results->setText(matched.toQString(true)); ui->tvp_ResultMessages->updateContainer(msgs); } diff --git a/src/blackmisc/aviation/aircrafticaocodelist.cpp b/src/blackmisc/aviation/aircrafticaocodelist.cpp index 45bf79dc8..07bf24e04 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.cpp +++ b/src/blackmisc/aviation/aircrafticaocodelist.cpp @@ -52,6 +52,26 @@ namespace BlackMisc }); } + CAircraftIcaoCodeList CAircraftIcaoCodeList::findEndingWith(const QString &icaoEnding) const + { + QString ends = icaoEnding.trimmed().toUpper(); + if (ends.isEmpty()) { return CAircraftIcaoCodeList(); } + CAircraftIcaoCodeList icaosDesignator; + CAircraftIcaoCodeList icaosFamily; + for (const CAircraftIcaoCode &icao : *this) + { + if (icao.getDesignator().endsWith(ends)) + { + icaosDesignator.push_back(icao); + } + else if (icao.getFamily().endsWith(ends)) + { + icaosFamily.push_back(icao); + } + } + return icaosDesignator.isEmpty() ? icaosFamily : icaosDesignator; + } + CAircraftIcaoCodeList CAircraftIcaoCodeList::findByIataCode(const QString &iata) const { if (iata.isEmpty()) { return CAircraftIcaoCodeList(); } @@ -221,6 +241,10 @@ namespace BlackMisc // we have one exact match if (codes.size() == 1) { return codes.front(); } + // now try to find as ending + codes = this->findEndingWith(d); + if (codes.size() == 1) { return codes.front(); } + // still empty, hopeless if (codes.isEmpty()) { return icaoPattern; } diff --git a/src/blackmisc/aviation/aircrafticaocodelist.h b/src/blackmisc/aviation/aircrafticaocodelist.h index 5d27be714..f19a41e1e 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.h +++ b/src/blackmisc/aviation/aircrafticaocodelist.h @@ -60,6 +60,10 @@ namespace BlackMisc //! Find by ICAO/IATA code or family CAircraftIcaoCodeList findByDesignatorIataOrFamily(const QString &icaoIataOrFamily) const; + //! Find code ending with string, e.g. "738" finds "B738" + //! \remark many users use wrong ICAO designators, one typical mistake is "738" for "B737" + CAircraftIcaoCodeList findEndingWith(const QString &icaoEnding) const; + //! Find by manufacturer CAircraftIcaoCodeList findByManufacturer(const QString &manufacturer) const; diff --git a/src/blackmisc/aviation/airlineicaocodelist.cpp b/src/blackmisc/aviation/airlineicaocodelist.cpp index 770e7b7ae..aee37f27a 100644 --- a/src/blackmisc/aviation/airlineicaocodelist.cpp +++ b/src/blackmisc/aviation/airlineicaocodelist.cpp @@ -119,12 +119,23 @@ namespace BlackMisc return icaoPattern; } + CAirlineIcaoCode CAirlineIcaoCodeList::findBestMatchByCallsign(const CCallsign &callsign) const + { + if (this->isEmpty() || callsign.isEmpty()) { return CAirlineIcaoCode(); } + const QString airline = callsign.getAirlineSuffix().toUpper(); + if (airline.isEmpty()) { return CAirlineIcaoCode(); } + const CAirlineIcaoCode airlineCode = (airline.length() == 3) ? + this->findFirstByOrDefault(&CAirlineIcaoCode::getDesignator, airline) : + this->findFirstByOrDefault(&CAirlineIcaoCode::getVDesignator, airline); + return airlineCode; + } + CAirlineIcaoCodeList CAirlineIcaoCodeList::fromDatabaseJson(const QJsonArray &array, bool ignoreIncomplete) { CAirlineIcaoCodeList codes; for (const QJsonValue &value : array) { - CAirlineIcaoCode icao(CAirlineIcaoCode::fromDatabaseJson(value.toObject())); + const CAirlineIcaoCode icao(CAirlineIcaoCode::fromDatabaseJson(value.toObject())); if (ignoreIncomplete && !icao.hasCompleteData()) { continue; } codes.push_back(icao); } diff --git a/src/blackmisc/aviation/airlineicaocodelist.h b/src/blackmisc/aviation/airlineicaocodelist.h index a2756e83c..4230c9534 100644 --- a/src/blackmisc/aviation/airlineicaocodelist.h +++ b/src/blackmisc/aviation/airlineicaocodelist.h @@ -13,7 +13,7 @@ #define BLACKMISC_AVIATION_AIRLINEICAOCODELIST_H #include "airlineicaocode.h" -#include "blackmisc/aviation/airlineicaocode.h" +#include "callsign.h" #include "blackmisc/blackmiscexport.h" #include "blackmisc/collection.h" #include "blackmisc/db/datastoreobjectlist.h" @@ -72,6 +72,9 @@ namespace BlackMisc //! Best selection by given pattern CAirlineIcaoCode smartAirlineIcaoSelector(const CAirlineIcaoCode &icaoPattern) const; + //! Use callsign to conclude airline + CAirlineIcaoCode findBestMatchByCallsign(const CCallsign &callsign) const; + //! String list for completion by ICAO designator QStringList toIcaoDesignatorCompleterStrings(bool combinedString = true, bool sort = true) const; diff --git a/src/blackmisc/aviation/callsign.cpp b/src/blackmisc/aviation/callsign.cpp index aca8f7121..2fe7ade55 100644 --- a/src/blackmisc/aviation/callsign.cpp +++ b/src/blackmisc/aviation/callsign.cpp @@ -61,6 +61,7 @@ namespace BlackMisc if ("CTR" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleCenter); } if ("SUP" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleSup); } if ("OBS" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleObs); } + if ("INS" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleMnt); } if ("FSS" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleFss); } if ("ATIS" == sfx) { return CIconList::iconByIndex(CIcons::AviationAtis); } if ("EXAM" == sfx) { return CIconList::iconByIndex(CIcons::NetworkRoleMnt); } @@ -125,6 +126,20 @@ namespace BlackMisc return s; } + QString CCallsign::getAirlineSuffix() const + { + if (this->m_callsign.length() < 3) { return ""; } + if (this->isAtcCallsign()) { return ""; } + + static const QRegExp regExp("^[A-Z]{3,}"); + const int pos = regExp.indexIn(this->m_callsign); + if (pos < 0) { return ""; } + const QString airline = regExp.cap(0); + if (airline.length() == 3) { return airline; } // we allow 3 letters + if (airline.length() == 4 && airline.startsWith('V')) { return airline; } // we allow virtual 4 letter codes, e.g. VDLD + return ""; // invalid + } + bool CCallsign::hasSuffix() const { return this->getStringAsSet().contains('_'); @@ -187,6 +202,8 @@ namespace BlackMisc return this->m_callsignAsSet.compare(compareValue.m_callsignAsSet, Qt::CaseInsensitive); case IndexTelephonyDesignator: return this->m_telephonyDesignator.compare(compareValue.m_telephonyDesignator, Qt::CaseInsensitive); + case IndexSuffix: + return this->getSuffix().compare(compareValue.getSuffix(), Qt::CaseInsensitive); default: break; } @@ -247,9 +264,8 @@ namespace BlackMisc const QStringList &CCallsign::atcAlikeCallsignSuffixes() { - static const QStringList a({ "ATIS", "APP", "GND", "OBS", "TWR", "DEL", "CTR", "SUP", "FSS" }); + static const QStringList a({ "ATIS", "APP", "GND", "OBS", "TWR", "DEL", "CTR", "SUP", "FSS", "INS" }); return a; } - } // namespace } // namespace diff --git a/src/blackmisc/aviation/callsign.h b/src/blackmisc/aviation/callsign.h index 2b378b568..70264e6ca 100644 --- a/src/blackmisc/aviation/callsign.h +++ b/src/blackmisc/aviation/callsign.h @@ -110,6 +110,9 @@ namespace BlackMisc //! Get the callsign suffix ("TWR", "ATIS" ...) if any ("_" is removed) QString getSuffix() const; + //! Airline suffix (e.g. DLH1234 -> DLH) if applicable + QString getAirlineSuffix() const; + //! Suffix such as "_TWR"? bool hasSuffix() const; diff --git a/src/blackmisc/aviation/liverylist.cpp b/src/blackmisc/aviation/liverylist.cpp index 23c6c0bb2..a5e376987 100644 --- a/src/blackmisc/aviation/liverylist.cpp +++ b/src/blackmisc/aviation/liverylist.cpp @@ -85,7 +85,7 @@ namespace BlackMisc if (liveryPattern.hasValidDbKey()) { int k = liveryPattern.getDbKey(); - CLivery l(this->findByKey(k)); + const CLivery l(this->findByKey(k)); if (l.hasCompleteData()) { return l; } } @@ -93,14 +93,14 @@ namespace BlackMisc if (liveryPattern.hasCombinedCode()) { QString cc(liveryPattern.getCombinedCode()); - CLivery l(this->findByCombinedCode(cc)); + const CLivery l(this->findByCombinedCode(cc)); if (l.hasCompleteData()) { return l; } } if (liveryPattern.hasValidAirlineDesignator()) { - QString icao(liveryPattern.getAirlineIcaoCodeDesignator()); - CLivery l(this->findStdLiveryByAirlineIcaoDesignator(icao)); + const QString icao(liveryPattern.getAirlineIcaoCodeDesignator()); + const CLivery l(this->findStdLiveryByAirlineIcaoDesignator(icao)); if (l.hasCompleteData()) { return l; } } return CLivery(); diff --git a/src/blackmisc/simulation/aircraftmodel.h b/src/blackmisc/simulation/aircraftmodel.h index e483cf530..aaac2821e 100644 --- a/src/blackmisc/simulation/aircraftmodel.h +++ b/src/blackmisc/simulation/aircraftmodel.h @@ -334,7 +334,7 @@ namespace BlackMisc //! From swift DB JSON static CAircraftModel fromDatabaseJson(const QJsonObject &json, const QString prefix = QString("mod_")); - //! Split swift network string "DLH._STD" [modelname]" + //! Split swift network string "DLH._STD [modelname]" //! \return QStringList [0] livery code , [1] model string //! \sa getSwiftLiveryString static QStringList splitNetworkLiveryString(const QString &liveryString);