refs #720, improved distributor and airline/livery detection

* mark data read from DB (req. for string key where existing key value is not reliable indicator for DB data)
* detect distributors by part of model string
* use a simplified name (no spaces, no special characters) to find a match
* allow to obtain model strings (=keys) as set and list
This commit is contained in:
Klaus Basan
2016-08-09 01:24:47 +02:00
committed by Mathew Sutcliffe
parent 39dae7ed45
commit f9922353c4
21 changed files with 220 additions and 40 deletions

View File

@@ -9,8 +9,8 @@
#include "airlineicaocode.h"
#include "callsign.h"
#include "blackmisc/comparefunctions.h"
#include "blackmisc/db/datastoreutility.h"
#include "blackmisc/comparefunctions.h"
#include "blackmisc/icons.h"
#include "blackmisc/logcategory.h"
#include "blackmisc/logcategorylist.h"
@@ -75,6 +75,11 @@ namespace BlackMisc
return s.trimmed();
}
QString CAirlineIcaoCode::getSimplifiedName() const
{
return BlackMisc::simplifyNameForSearch(this->getName());
}
bool CAirlineIcaoCode::hasValidCountry() const
{
return this->m_country.isValid();
@@ -120,6 +125,14 @@ namespace BlackMisc
return this->matchesVDesignator(candidate) || this->matchesIataCode(candidate);
}
bool CAirlineIcaoCode::isContainedInSimplifiedName(const QString &candidate) const
{
if (candidate.isEmpty() || !this->hasName()) { return false; }
// try unaltered name first (should be faster)
if (this->getName().contains(candidate, Qt::CaseInsensitive)) { return true; }
return this->getSimplifiedName().contains(candidate, Qt::CaseInsensitive);
}
bool CAirlineIcaoCode::hasCompleteData() const
{
return this->hasValidDesignator() && this->hasValidCountry() && this->hasName();
@@ -361,15 +374,15 @@ namespace BlackMisc
return CAirlineIcaoCode();
}
QString designator(json.value(prefix + "designator").toString());
QString iata(json.value(prefix + "iata").toString());
QString telephony(json.value(prefix + "callsign").toString());
QString name(json.value(prefix + "name").toString());
QString countryIso(json.value(prefix + "country").toString());
QString countryName(json.value(prefix + "countryname").toString());
bool va = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "va").toString());
bool operating = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "operating").toString());
bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "military").toString());
const QString designator(json.value(prefix + "designator").toString());
const QString iata(json.value(prefix + "iata").toString());
const QString telephony(json.value(prefix + "callsign").toString());
const QString name(json.value(prefix + "name").toString());
const QString countryIso(json.value(prefix + "country").toString());
const QString countryName(json.value(prefix + "countryname").toString());
const bool va = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "va").toString());
const bool operating = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "operating").toString());
const bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "military").toString());
CAirlineIcaoCode code(
designator, name,

View File

@@ -92,6 +92,9 @@ namespace BlackMisc
//! Get name, e.g. "Lufthansa"
const QString &getName() const { return this->m_name; }
//! \copydoc BlackMisc::simplifyNameForSearch
QString getSimplifiedName() const;
//! Name plus key, e.g. "Lufthansa (3421)"
QString getNameWithKey() const;
@@ -146,6 +149,9 @@ namespace BlackMisc
//! Matches IATA code or v-designator?
bool matchesVDesignatorOrIataCode(const QString &candidate) const;
//! Does simplified name contain the candidate
bool isContainedInSimplifiedName(const QString &candidate) const;
//! Telephony designator?
bool hasTelephonyDesignator() const { return !this->m_telephonyDesignator.isEmpty(); }
@@ -158,7 +164,7 @@ namespace BlackMisc
//! Comined string with key
QString getCombinedStringWithKey() const;
//! What is better, the callsign airline code or this code
//! What is better, the callsign airline code or this code. Return the better one.
CAirlineIcaoCode thisOrCallsignCode(const CCallsign &callsign) const;
//! \copydoc BlackMisc::Mixin::Icon::toIcon

View File

@@ -80,6 +80,15 @@ namespace BlackMisc
});
}
CAirlineIcaoCodeList CAirlineIcaoCodeList::findBySimplifiedNameContaining(const QString &containedString) const
{
if (containedString.isEmpty()) { return CAirlineIcaoCodeList(); }
return this->findBy([&](const CAirlineIcaoCode & code)
{
return code.isContainedInSimplifiedName(containedString);
});
}
CAirlineIcaoCodeList CAirlineIcaoCodeList::findByMilitary(bool military) const
{
return this->findBy([&](const CAirlineIcaoCode & code)

View File

@@ -66,6 +66,9 @@ namespace BlackMisc
//! Find by country code
CAirlineIcaoCodeList findByCountryIsoCode(const QString &isoCode) const;
//! Find if simplified name contains search string
CAirlineIcaoCodeList findBySimplifiedNameContaining(const QString &containedString) const;
//! Find by military flag
CAirlineIcaoCodeList findByMilitary(bool military) const;
@@ -87,7 +90,6 @@ namespace BlackMisc
//! From our DB JSON
static CAirlineIcaoCodeList fromDatabaseJson(const QJsonArray &array, bool ignoreIncomplete = true);
};
} //namespace
} // namespace

View File

@@ -74,6 +74,11 @@ namespace BlackMisc
return s;
}
bool CLivery::isContainedInSimplifiedAirlineName(const QString &candidate) const
{
return this->getAirlineIcaoCode().isContainedInSimplifiedName(candidate);
}
bool CLivery::setAirlineIcaoCode(const CAirlineIcaoCode &airlineIcao)
{
if (m_airline == airlineIcao) { return false; }

View File

@@ -80,6 +80,12 @@ namespace BlackMisc
//! Get description.
const QString &getDescription() const { return m_description; }
//! Get corresponding airline name
const QString &getAirlineName() const { return this->getAirlineIcaoCode().getName(); }
//! Does simplified airline name contain the candidate
bool isContainedInSimplifiedAirlineName(const QString &candidate) const;
//! Get fuselage color.
const BlackMisc::CRgbColor &getColorFuselage() const { return m_colorFuselage; }

View File

@@ -37,21 +37,31 @@ namespace BlackMisc
return this->findBy(&CLivery::getAirlineIcaoCodeDesignator, i);
}
CLivery CLiveryList::findStdLiveryByAirlineIcaoDesignator(const QString &icao) const
CLivery CLiveryList::findStdLiveryByAirlineIcaoVDesignator(const QString &icao) const
{
QString i(icao.trimmed().toUpper());
if (i.isEmpty()) { return CLivery(); }
return this->findFirstByOrDefault([&](const CLivery & livery)
{
return livery.getAirlineIcaoCodeDesignator() == icao &&
livery.isAirlineStandardLivery();
if (!livery.isAirlineStandardLivery()) { return false; }
return livery.getAirlineIcaoCode().matchesVDesignator(i);
});
}
CLivery CLiveryList::findStdLiveryByAirlineIcaoDesignator(const CAirlineIcaoCode &icao) const
CLiveryList CLiveryList::findStdLiveriesBySimplifiedAirlineName(const QString &containedString) const
{
return this->findStdLiveryByAirlineIcaoDesignator(icao.getDesignator());
if (containedString.isEmpty()) { return CLiveryList(); }
return this->findBy([&](const CLivery & livery)
{
// keep isAirlineStandardLivery first (faster)
return livery.isAirlineStandardLivery() &&
livery.isContainedInSimplifiedAirlineName(containedString);
});
}
CLivery CLiveryList::findStdLiveryByAirlineIcaoVDesignator(const CAirlineIcaoCode &icao) const
{
return this->findStdLiveryByAirlineIcaoVDesignator(icao.getVDesignator());
}
CLivery CLiveryList::findByCombinedCode(const QString &combinedCode) const
@@ -81,6 +91,9 @@ namespace BlackMisc
CLivery CLiveryList::smartLiverySelector(const CLivery &liveryPattern) const
{
// multiple searches are slow, maybe we can performance optimize this
// in the futuew
// first try on id, that would be perfect
if (liveryPattern.hasValidDbKey())
{
@@ -97,14 +110,25 @@ namespace BlackMisc
if (l.hasCompleteData()) { return l; }
}
// by airline
if (liveryPattern.hasValidAirlineDesignator())
{
const QString icao(liveryPattern.getAirlineIcaoCodeDesignator());
const CLivery l(this->findStdLiveryByAirlineIcaoDesignator(icao));
const CLivery l(this->findStdLiveryByAirlineIcaoVDesignator(icao));
if (l.hasCompleteData()) { return l; }
}
// lenient search by name contained (slow)
if (liveryPattern.getAirlineIcaoCode().hasName())
{
const QString search(liveryPattern.getAirlineIcaoCode().getSimplifiedName());
const CLiveryList liveries(this->findStdLiveriesBySimplifiedAirlineName(search));
if (!liveries.isEmpty())
{
return liveries.front();
}
}
return CLivery();
}
} // namespace
} // namespace

View File

@@ -47,10 +47,13 @@ namespace BlackMisc
CLiveryList findByAirlineIcaoDesignator(const QString &icao) const;
//! Find livery by airline
CLivery findStdLiveryByAirlineIcaoDesignator(const QString &icao) const;
CLivery findStdLiveryByAirlineIcaoVDesignator(const QString &icao) const;
//! Find livery by airline
CLivery findStdLiveryByAirlineIcaoDesignator(const CAirlineIcaoCode &icao) const;
CLivery findStdLiveryByAirlineIcaoVDesignator(const CAirlineIcaoCode &icao) const;
//! By simplified name
CLiveryList findStdLiveriesBySimplifiedAirlineName(const QString &containedString) const;
//! Find livery by combined code
CLivery findByCombinedCode(const QString &combinedCode) const;
@@ -63,7 +66,6 @@ namespace BlackMisc
//! Find
CLivery smartLiverySelector(const CLivery &liveryPattern) const;
};
} //namespace
} // namespace

View File

@@ -218,6 +218,7 @@ namespace BlackMisc
const QString iso3(json.value(prefix + "iso3").toString());
const QString historic(json.value(prefix + "historic").toString());
CCountry country(iso, name);
country.setLoadedFromDb(true);
country.setAlias1(alias1);
country.setAlias2(alias2);
country.setIso3Code(iso3);

View File

@@ -149,7 +149,8 @@ namespace BlackMisc
BLACK_METACLASS(
CCountry,
BLACK_METAMEMBER(dbKey),
BLACK_METAMEMBER(dbKey, 0, CaseInsensitiveComparison),
BLACK_METAMEMBER(loadedFromDb),
BLACK_METAMEMBER(timestampMSecsSinceEpoch),
BLACK_METAMEMBER(iso3),
BLACK_METAMEMBER(name),

View File

@@ -41,6 +41,18 @@ namespace BlackMisc
this->m_dbKey = k;
}
bool IDatastoreObjectWithIntegerKey::isLoadedFromDb() const
{
return this->hasValidDbKey();
}
bool IDatastoreObjectWithIntegerKey::matchesDbKeyState(Db::DbKeyStateFilter filter) const
{
if (filter == All) { return true; }
const bool validKey = this->hasValidDbKey();
return (validKey && filter.testFlag(Valid)) || (!validKey && filter.testFlag(Invalid));
}
const CIcon &IDatastoreObjectWithIntegerKey::toDatabaseIcon() const
{
static const CIcon empty;
@@ -131,6 +143,12 @@ namespace BlackMisc
return null;
}
bool IDatastoreObjectWithStringKey::matchesDbKeyState(Db::DbKeyStateFilter filter) const
{
if (filter == All) { return true; }
return this->hasValidDbKey() && filter.testFlag(Valid);
}
const CIcon &IDatastoreObjectWithStringKey::toDatabaseIcon() const
{
static const CIcon empty;
@@ -159,6 +177,7 @@ namespace BlackMisc
{
case IndexDbStringKey: return CVariant::from(this->m_dbKey);
case IndexDatabaseIcon: return CVariant::from(this->toDatabaseIcon());
case IndexIsLoadedFromDb: return CVariant::from(this->m_loadedFromDb);
default:
break;
}
@@ -174,6 +193,9 @@ namespace BlackMisc
case IndexDbStringKey:
this->m_dbKey = variant.value<QString>();
break;
case IndexIsLoadedFromDb:
this->m_loadedFromDb = variant.toBool();
break;
default:
break;
}

View File

@@ -25,9 +25,20 @@
namespace BlackMisc
{
class CIcon;
namespace Db
{
//! State of key (in DB, ...?)
enum DbKeyState
{
Undefined = 0,
Valid = 1 << 0,
Invalid = 1 << 1,
All = Valid | Invalid
};
//! Supposed to be used only in filter operations
Q_DECLARE_FLAGS(DbKeyStateFilter, DbKeyState)
/*!
* Class from which a derived class can inherit datastore-related functions.
*/
@@ -62,6 +73,13 @@ namespace BlackMisc
//! Has valid DB key
bool hasValidDbKey() const { return m_dbKey >= 0; }
//! Loaded from DB
//! \remarks here not really needed, but added to have similar signature as IDatastoreObjectWithStringKey
bool isLoadedFromDb() const;
//! Matches filter?
bool matchesDbKeyState(Db::DbKeyStateFilter filter) const;
//! Database icon if this has valid key, otherwise empty
const CIcon &toDatabaseIcon() const;
@@ -109,6 +127,7 @@ namespace BlackMisc
enum ColumnIndex
{
IndexDbStringKey = CPropertyIndex::GlobalIndexIDatastoreString,
IndexIsLoadedFromDb,
IndexDatabaseIcon
};
@@ -124,6 +143,15 @@ namespace BlackMisc
//! Has valid DB key
bool hasValidDbKey() const { return !m_dbKey.isEmpty(); }
//! Loaded from DB
bool isLoadedFromDb() const { return m_loadedFromDb; }
//! Mark as loaded from DB
void setLoadedFromDb(bool loaded) { m_loadedFromDb = loaded; }
//! Matches filter?
bool matchesDbKeyState(Db::DbKeyStateFilter filter) const;
//! Database icon if this has valid key, otherwise empty
const CIcon &toDatabaseIcon() const;
@@ -155,11 +183,15 @@ namespace BlackMisc
//! Can given index be handled
static bool canHandleIndex(const BlackMisc::CPropertyIndex &index);
QString m_dbKey; //!< key
QString m_dbKey; //!< key
bool m_loadedFromDb = false; //!< as we have no artificial key, it can happen key is set, but not loaded from DB
};
} // ns
} // ns
Q_DECLARE_METATYPE(BlackMisc::Db::DbKeyState)
Q_DECLARE_METATYPE(BlackMisc::Db::DbKeyStateFilter)
Q_DECLARE_OPERATORS_FOR_FLAGS(BlackMisc::Db::DbKeyStateFilter)
Q_DECLARE_INTERFACE(BlackMisc::Db::IDatastoreObjectWithIntegerKey, "org.swift-project.blackmisc.db.idatastoreobjectwithintegerkey")
Q_DECLARE_INTERFACE(BlackMisc::Db::IDatastoreObjectWithStringKey, "org.swift-project.blackmisc.db.idatastoreobjectwithstringkey")

View File

@@ -372,6 +372,7 @@ namespace BlackMisc
bool CAircraftModel::matchesMode(ModelModeFilter mode) const
{
if (mode == All) { return true; }
return (mode & this->m_modelMode) > 0;
}

View File

@@ -365,7 +365,7 @@ namespace BlackMisc
return addOrReplaceList.size();
}
CAircraftModelList newModels(*this);
const QStringList keys(addOrReplaceList.getModelStrings(false));
const QStringList keys(addOrReplaceList.getModelStringList(false));
newModels.removeModelsWithString(keys, sensitivity);
int removed = newModels.size(); // size after removing
newModels.push_back(addOrReplaceList);
@@ -398,7 +398,7 @@ namespace BlackMisc
});
}
QStringList CAircraftModelList::getModelStrings(bool sort) const
QStringList CAircraftModelList::getModelStringList(bool sort) const
{
QStringList ms;
for (const CAircraftModel &model : (*this))
@@ -410,6 +410,17 @@ namespace BlackMisc
return ms;
}
QSet<QString> CAircraftModelList::getModelStringSet() const
{
QSet<QString> ms;
for (const CAircraftModel &model : (*this))
{
if (!model.hasModelString()) { continue; }
ms.insert(model.getModelString());
}
return ms;
}
CCountPerSimulator CAircraftModelList::countPerSimulator() const
{
CCountPerSimulator count;

View File

@@ -166,7 +166,10 @@ namespace BlackMisc
int replaceOrAddModelsWithString(const CAircraftModelList &addOrReplaceList, Qt::CaseSensitivity sensitivity);
//! Model strings
QStringList getModelStrings(bool sort = true) const;
QStringList getModelStringList(bool sort = true) const;
//! Model strings as set
QSet<QString> getModelStringSet() const;
//! Simulator counts
CCountPerSimulator countPerSimulator() const;
@@ -204,9 +207,8 @@ namespace BlackMisc
//! To database JSON
QString toDatabaseJsonString(QJsonDocument::JsonFormat format = QJsonDocument::Compact) const;
};
} //namespace
} // namespace
} // ns
} // ns
Q_DECLARE_METATYPE(BlackMisc::Simulation::CAircraftModelList)
Q_DECLARE_METATYPE(BlackMisc::CCollection<BlackMisc::Simulation::CAircraftModel>)

View File

@@ -178,6 +178,7 @@ namespace BlackMisc
Q_ASSERT_X(!description.isEmpty(), Q_FUNC_INFO, "Missing description");
CDistributor distributor("", description, alias1, alias2, simulator);
distributor.setKeyAndTimestampFromDatabaseJson(json, prefix);
distributor.setLoadedFromDb(true);
return distributor;
}
} // namespace

View File

@@ -130,6 +130,7 @@ namespace BlackMisc
BLACK_METACLASS(
CDistributor,
BLACK_METAMEMBER(dbKey, 0, CaseInsensitiveComparison),
BLACK_METAMEMBER(loadedFromDb),
BLACK_METAMEMBER(timestampMSecsSinceEpoch),
BLACK_METAMEMBER(order),
BLACK_METAMEMBER(description),

View File

@@ -7,7 +7,9 @@
* contained in the LICENSE file.
*/
#include "blackmisc/simulation/distributorlist.h"
#include "distributorlist.h"
#include "simulatorinfo.h"
#include "aircraftmodel.h"
#include "blackmisc/metaclassprivate.h"
#include <tuple>
@@ -32,20 +34,40 @@ namespace BlackMisc
return CDistributor();
}
CDistributor CDistributorList::smartDistributorSelector(const CDistributor &distributorPattern) const
CDistributor CDistributorList::findByModelData(const CAircraftModel &model) const
{
if (distributorPattern.hasValidDbKey())
// some stuipd hardcoded resolutions
if (model.getDistributor().hasValidDbKey()) { return model.getDistributor(); }
if (model.getModelString().startsWith("WOA", Qt::CaseInsensitive)) { return this->findByKeyOrAlias("WOAI"); }
if (model.getDescription().contains("WOA", Qt::CaseInsensitive)) { return this->findByKeyOrAlias("WOAI"); }
if (model.getDescription().contains("IVAO", Qt::CaseInsensitive)) { return this->findByKeyOrAlias("IVAO"); }
return CDistributor();
}
CDistributor CDistributorList::smartDistributorSelector(const CDistributor &distributor) const
{
// key is not necessarily a DB key, so use complete data, happens when key is set from raw data
if (distributor.isLoadedFromDb()) { return distributor; }
if (distributor.hasValidDbKey())
{
QString k(distributorPattern.getDbKey());
CDistributor d(this->findByKey(k));
const QString key(distributor.getDbKey());
CDistributor d(this->findByKey(key));
if (d.hasCompleteData()) { return d; }
// more lenient search
return this->findByKeyOrAlias(k);
return this->findByKeyOrAlias(key);
}
return CDistributor();
}
CDistributor CDistributorList::smartDistributorSelector(const CDistributor &distributorPattern, const CAircraftModel &model) const
{
const CDistributor d = this->smartDistributorSelector(distributorPattern);
// key is not necessarily a DB key, so use complete data, happens when key is set from raw data
if (d.hasCompleteData()) { return d; }
return this->findByModelData(model);
}
bool CDistributorList::matchesAnyKeyOrAlias(const QString &keyOrAlias) const
{
for (const CDistributor &distributor : (*this))

View File

@@ -18,7 +18,6 @@
#include "blackmisc/orderablelist.h"
#include "blackmisc/sequence.h"
#include "blackmisc/simulation/distributor.h"
#include "blackmisc/simulation/simulatorinfo.h"
#include "blackmisc/variant.h"
#include <QMetaType>
@@ -29,6 +28,8 @@ namespace BlackMisc
{
namespace Simulation
{
class CSimulatorModel;
//! Value object encapsulating a list of distributors.
class BLACKMISC_EXPORT CDistributorList :
public BlackMisc::CSequence<CDistributor>,
@@ -48,9 +49,16 @@ namespace BlackMisc
//! Find by id or alias
CDistributor findByKeyOrAlias(const QString &keyOrAlias) const;
//! Find by model string
//! \remark model strings may have a pattern which makes it impossible to find the distributor
CDistributor findByModelData(const CAircraftModel &model) const;
//! Best match by given pattern
CDistributor smartDistributorSelector(const CDistributor &distributorPattern) const;
//! Best match by given pattern
CDistributor smartDistributorSelector(const CDistributor &distributorPattern, const CAircraftModel &model) const;
//! At least is matching key or alias
bool matchesAnyKeyOrAlias(const QString &keyOrAlias) const;
@@ -58,7 +66,7 @@ namespace BlackMisc
QStringList getDbKeysAndAliases(bool sort) const;
//! Find for given simulator
CDistributorList matchesSimulator(const BlackMisc::Simulation::CSimulatorInfo &simulator) const;
CDistributorList matchesSimulator(const CSimulatorInfo &simulator) const;
};
} //namespace
} // namespace

View File

@@ -12,6 +12,7 @@
#include "stringutils.h"
#include <QChar>
#include <QTextCodec>
#include <QRegularExpression>
namespace BlackMisc
{
@@ -162,6 +163,13 @@ namespace BlackMisc
{
return c1.length() == c2.length() && c1.startsWith(c2, Qt::CaseInsensitive);
}
QString simplifyNameForSearch(const QString &name)
{
static const QRegularExpression reg("[^A-Z]");
const QString r = name.toUpper().remove(reg);
return r;
}
}
//! \endcond

View File

@@ -78,6 +78,9 @@ namespace BlackMisc
//! Case insensitive string compare
BLACKMISC_EXPORT bool caseInsensitiveStringCompare(const QString &c1, const QString &c2);
//! Get a simplified upper case name for searching by removing all characters except A-Z
BLACKMISC_EXPORT QString simplifyNameForSearch(const QString &name);
namespace Mixin
{
/*!