From ddb02608e0a7feb4834502024e3775b686f2a135 Mon Sep 17 00:00:00 2001 From: Roland Winklmeier Date: Sun, 3 May 2015 13:34:50 +0200 Subject: [PATCH] refs #409 aircraft model matcher --- .../simulation/fscommon/aircraftmatcher.cpp | 258 ++++++++++++++++++ .../simulation/fscommon/aircraftmatcher.h | 149 ++++++++++ 2 files changed, 407 insertions(+) create mode 100644 src/blackmisc/simulation/fscommon/aircraftmatcher.cpp create mode 100644 src/blackmisc/simulation/fscommon/aircraftmatcher.h diff --git a/src/blackmisc/simulation/fscommon/aircraftmatcher.cpp b/src/blackmisc/simulation/fscommon/aircraftmatcher.cpp new file mode 100644 index 000000000..90bd9e31e --- /dev/null +++ b/src/blackmisc/simulation/fscommon/aircraftmatcher.cpp @@ -0,0 +1,258 @@ +/* Copyright (C) 2013 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "aircraftmatcher.h" +#include "blackmisc/logmessage.h" +#include "blackmisc/worker.h" +#include + +using namespace BlackMisc; +using namespace BlackMisc::Simulation; +using namespace BlackMisc::Network; +using namespace BlackMisc::Aviation; + +namespace BlackMisc +{ + namespace Simulation + { + namespace FsCommon + { + + CAircraftMatcher::CAircraftMatcher(MatchingMode matchingMode, QObject *parent) : + QObject(parent), + m_matchingMode(matchingMode) + { } + + CAircraftMatcher::~CAircraftMatcher() + { + gracefulShutdown(); + } + + void CAircraftMatcher::init() + { + if (m_initWorker) { return; } + m_initWorker = BlackMisc::CWorker::fromTask(this, "CAircraftMatcher::initImpl", [this]() + { + initImpl(); + }); + } + + bool CAircraftMatcher::isInitialized() const + { + return m_initialized; + } + + void CAircraftMatcher::setModelMappingProvider(std::unique_ptr mappings) + { + m_mappingsProvider = std::move(mappings); + if (m_matchingMode.testFlag(ModelMapping)) initMappings(); + } + + void CAircraftMatcher::setMatchingModes(MatchingMode matchingModes) + { + m_matchingMode = matchingModes; + if (m_matchingMode.testFlag(ModelMapping) && m_modelMappings.isEmpty()) initMappings(); + } + + CAircraftModel CAircraftMatcher::getClosestMatch(const CSimulatedAircraft &remoteAircraft) + { + CAircraftModel aircraftModel(remoteAircraft); // set defaults + + // Manually set string? + if (remoteAircraft.getModel().hasManuallySetString()) + { + // manual set model, maybe update missing parts + aircraftModel.updateMissingParts(remoteAircraft.getModel()); + aircraftModel.setCallsign(remoteAircraft.getCallsign()); + reverseLookupIcaoData(aircraftModel); + return aircraftModel; + } + + // mapper ready? + if (!isInitialized()) + { + // will be removed later, just for experimental version + aircraftModel = getDefaultModel(); + aircraftModel.setCallsign(remoteAircraft.getCallsign()); + CLogMessage(static_cast(nullptr)).warning("Matcher not initialized, set to default model"); + return aircraftModel; + } + + aircraftModel = matchByExactModelName(remoteAircraft); + + if (!aircraftModel.hasModelString()) + { + aircraftModel = matchByMapping(remoteAircraft); + } + + if (!aircraftModel.hasModelString()) + { + aircraftModel = matchByAlgorithm(remoteAircraft); + } + + if (!aircraftModel.hasModelString()) + { + aircraftModel = getDefaultModel(); + } + aircraftModel.setCallsign(remoteAircraft.getCallsign()); + + Q_ASSERT(!aircraftModel.getCallsign().isEmpty()); + Q_ASSERT(aircraftModel.hasModelString()); + Q_ASSERT(aircraftModel.getModelType() != CAircraftModel::TypeUnknown); + + return aircraftModel; + } + + CAircraftIcaoData CAircraftMatcher::getIcaoForModelString(const QString &modelString) const + { + if (modelString.isEmpty() || !isInitialized()) { return CAircraftIcaoData(); } + CAircraftMappingList mappings = m_mappingsProvider->getMappingList().findByModelString(modelString); + if (mappings.isEmpty()) { return CAircraftIcaoData(); } + return mappings.front().getIcao(); + } + + int CAircraftMatcher::synchronize() + { + return synchronizeWithExistingModels(m_installedModels.getSortedModelStrings()); + } + + void CAircraftMatcher::gracefulShutdown() + { + // when running, force re-init + this->m_initInProgress = false; + this->m_initialized = false; + } + + void CAircraftMatcher::markUninitialized() + { + this->m_initialized = false; + } + + const CAircraftModel &CAircraftMatcher::getDefaultModel() + { + return m_defaultModel; + } + + void CAircraftMatcher::setDefaultModel(const BlackMisc::Simulation::CAircraftModel &defaultModel) + { + m_defaultModel = defaultModel; + } + + void CAircraftMatcher::initImpl() + { + if (m_initialized) { return; } + if (m_initInProgress) { return; } + m_initInProgress = true; + + // sync + this->synchronize(); + CLogMessage(this).debug() << "Mapping definitions after sync" << m_modelMappings.size(); + + // finish + CLogMessage(this).info("Mapping system: %1 definitions for %2 installed models") << m_modelMappings.size() + << m_installedModels.size(); + emit initializationFinished(); + m_initInProgress = false; + m_initialized = true; + } + + void CAircraftMatcher::initMappings() + { + Q_ASSERT(m_mappingsProvider); + int mappingsSize = m_mappingsProvider->getMappingList().size(); + if (mappingsSize < 1) + { + m_mappingsProvider->read(); + m_modelMappings = m_mappingsProvider->getMappingList(); + mappingsSize = m_modelMappings.size(); + if (mappingsSize < 1) + { + CLogMessage(this).error("Reading mapping rules failed or empty!"); + // Turn off the model mapping mode + m_matchingMode &= ~ModelMapping; + return; + } + } + m_modelMappings = m_mappingsProvider->getMappingList(); + CLogMessage(this).debug() << "Mapping definitions" << mappingsSize; + } + + CAircraftModel CAircraftMatcher::matchByExactModelName(const CSimulatedAircraft &remoteAircraft) + { + CAircraftModel aircraftModel(remoteAircraft); + // Model by queried string + const CClient remoteClient = remoteAircraft.getClient(); + if (remoteClient.getAircraftModel().hasQueriedModelString()) + { + QString directModelString = remoteClient.getAircraftModel().getModelString(); + if (!directModelString.isEmpty() && m_installedModels.containsModelString(directModelString)) + { + aircraftModel = m_installedModels.findFirstByModelString(directModelString); + aircraftModel.setModelType(CAircraftModel::TypeQueriedFromNetwork); + } + } + + return aircraftModel; + } + + CAircraftModel CAircraftMatcher::matchByMapping(const CSimulatedAircraft &remoteAircraft) + { + CAircraftModel aircraftModel; + CAircraftIcaoData icao = remoteAircraft.getIcaoInfo(); + BlackMisc::Network::CAircraftMappingList mappingList = m_modelMappings.findByIcaoAircraftAndAirlineDesignator(icao, true); + if (!mappingList.isEmpty()) + { + CAircraftModel modelFromMappings = mappingList.front().getModel(); + // now turn the model from the mapping rules into a model from the simulator which has more metadata + aircraftModel = m_installedModels.findFirstByModelString(modelFromMappings.getModelString()); + Q_ASSERT(aircraftModel.getModelString() == modelFromMappings.getModelString()); + aircraftModel.updateMissingParts(modelFromMappings); // update ICAO + aircraftModel.setModelType(CAircraftModel::TypeModelMatching); + } + + return aircraftModel; + } + + CAircraftModel CAircraftMatcher::matchByAlgorithm(const CSimulatedAircraft & /** remoteAircraft **/) + { + // Use an algorithm to find the best match + return CAircraftModel(); + } + + int CAircraftMatcher::synchronizeWithExistingModels(const QStringList &modelNames, Qt::CaseSensitivity cs) + { + if (modelNames.isEmpty() || m_modelMappings.isEmpty()) { return 0; } + CAircraftMappingList newList; + for (const CAircraftMapping &mapping : m_modelMappings) + { + QString modelString = mapping.getModel().getModelString(); + if (modelString.isEmpty()) { continue; } + if (modelNames.contains(modelString, cs)) + { + newList.push_back(mapping); + } + } + this->m_modelMappings = newList; + return this->m_modelMappings.size(); + } + + void CAircraftMatcher::reverseLookupIcaoData(CAircraftModel &model) + { + if (isInitialized()) + { + // reverse lookup of ICAO + CAircraftIcaoData icao = getIcaoForModelString(model.getModelString()); + icao.updateMissingParts(icao); + model.setIcao(icao); + } + } + + } // namespace + } // namespace +} // namespace diff --git a/src/blackmisc/simulation/fscommon/aircraftmatcher.h b/src/blackmisc/simulation/fscommon/aircraftmatcher.h new file mode 100644 index 000000000..a93f4cca9 --- /dev/null +++ b/src/blackmisc/simulation/fscommon/aircraftmatcher.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2015 + * swift project Community / Contributors + * + * This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of swift project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +//! \file + +#ifndef BLACKMISC_SIMULATION_FSCOMMON_AIRCRAFTMATCHER_H +#define BLACKMISC_SIMULATION_FSCOMMON_AIRCRAFTMATCHER_H + +#include "aircraftcfgentrieslist.h" +#include "blackmisc/blackmiscexport.h" +#include "blackmisc/simulation/modelmappingsprovider.h" +#include "blackmisc/simulation/simulatedaircraft.h" +#include "blackmisc/worker.h" +#include +#include +#include +#include +#include +#include +#include + +namespace BlackMisc +{ + namespace Simulation + { + namespace FsCommon + { + //! Mapper for all models (works for FS9/FSX). + //! \details Reads all the mapping rules and all the available flight simulator models. + //! Then all rules for models not existing are eliminated ( \sa synchronize ). + //! Thereafter all existing models and mappings can be obtained from here. + //! \sa CAircraftCfgEntries + //! \sa CAircraftCfgEntriesList + class BLACKMISC_EXPORT CAircraftMatcher : public QObject + { + Q_OBJECT + + signals: + + //! Full init completed + void initializationFinished(); + + public: + + //! Enabled matching mode flags + enum MatchingModeFlag + { + ExactMatch = 1 << 0, + ModelMapping = 1 << 1, + ModelMatching = 1 << 2, + AllModes = ExactMatch | ModelMapping | ModelMatching + }; + Q_DECLARE_FLAGS(MatchingMode, MatchingModeFlag) + + //! Constructor + CAircraftMatcher(MatchingMode matchingMode = ModelMatching, QObject *parent = nullptr); + + //! Destructor + ~CAircraftMatcher(); + + //! Initialize + void init(); + + //! Init completed? + bool isInitialized() const; + + //! Set the list of installed models + void setInstalledModels(const CAircraftModelList &models) { m_installedModels = models; } + + //! Set the model mapping provider. The CAircraftMatcher will move the object and take over ownership + void setModelMappingProvider(std::unique_ptr mappings); + + //! Set the enabled matching modes + void setMatchingModes(MatchingMode matchingModes); + + //! Get the closest matching aircraft model. + //! Result depends on enabled modes. + //! \sa MatchingModeFlag + CAircraftModel getClosestMatch(const CSimulatedAircraft &remoteAircraft); + + //! Get all mappings + const BlackMisc::Network::CAircraftMappingList &getAircraftMappingList() const { return m_mappingsProvider->getMappingList(); } + + //! Inverse lookup + BlackMisc::Aviation::CAircraftIcaoData getIcaoForModelString(const QString &modelString) const; + + //! Number of mapping definitions + int countMappingRules() const { return m_modelMappings.size(); } + + //! Synchronize models and mappings + //! \remarks after this step, we only have mappings for which we have models + int synchronize(); + + //! Shutdown + void gracefulShutdown(); + + //! To force reload + void markUninitialized(); + + //! default model + const BlackMisc::Simulation::CAircraftModel &getDefaultModel(); + + //! Set default model + void setDefaultModel(const BlackMisc::Simulation::CAircraftModel &defaultModel); + + private slots: + + void ps_setModelMappingRules(const BlackMisc::Network::CAircraftMappingList &mappings) + { + m_modelMappings = mappings; + } + + private: + + void initImpl(); + void initMappings(); + + CAircraftModel matchByExactModelName(const CSimulatedAircraft &remoteAircraft); + CAircraftModel matchByMapping(const CSimulatedAircraft &remoteAircraft); + CAircraftModel matchByAlgorithm(const CSimulatedAircraft &remoteAircraft); + + //! Synchronize with existing model names, remove unneeded models + int synchronizeWithExistingModels(const QStringList &modelNames, Qt::CaseSensitivity cs = Qt::CaseInsensitive); + + //! Reverse lookup + void reverseLookupIcaoData(BlackMisc::Simulation::CAircraftModel &model); + + std::unique_ptr m_mappingsProvider; //!< Provides all mapping definitions + std::atomic m_initialized = { false }; + std::atomic m_initInProgress = { false }; + BlackMisc::CWorker *m_initWorker = { nullptr }; + MatchingMode m_matchingMode = ModelMatching; + CAircraftModelList m_installedModels; + BlackMisc::Network::CAircraftMappingList m_modelMappings; + BlackMisc::Simulation::CAircraftModel m_defaultModel; + }; + } // namespace + } // namespace +} // namespace + +Q_DECLARE_OPERATORS_FOR_FLAGS(BlackMisc::Simulation::FsCommon::CAircraftMatcher::MatchingMode) + +#endif // guard