mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-07 02:35:33 +08:00
refs #820, utility functions for score and groupBy
This commit is contained in:
@@ -108,6 +108,55 @@ namespace BlackMisc
|
||||
return this->getCombinedIcaoStringWithKey();
|
||||
}
|
||||
|
||||
int CAircraftIcaoCode::calculateScore(const CAircraftIcaoCode &otherCode) const
|
||||
{
|
||||
const bool bothFromDb = this->isLoadedFromDb() && otherCode.isLoadedFromDb();
|
||||
if (bothFromDb && otherCode.getDbKey() == this->getDbKey()) { return 100; }
|
||||
|
||||
int score = 0;
|
||||
if (this->hasValidDesignator() && this->getDesignator() == otherCode.getDesignator())
|
||||
{
|
||||
score += 50;
|
||||
|
||||
if (this->getRank() == 0) { score += 15; }
|
||||
else if (this->getRank() == 1) { score += 12; }
|
||||
else if (this->getRank() < 10) { score += (10 - this->getRank()); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this->hasFamily() && this->getFamily() == otherCode.getFamily())
|
||||
{
|
||||
score += 30;
|
||||
}
|
||||
else if (this->hasValidCombinedType() && otherCode.getCombinedType() == this->getCombinedType())
|
||||
{
|
||||
score += 20;
|
||||
}
|
||||
else if (this->hasValidCombinedType())
|
||||
{
|
||||
if (this->getEngineCount() == otherCode.getEngineCount()) { score += 2; }
|
||||
if (this->getEngineType() == otherCode.getEngineType()) { score += 2; }
|
||||
if (this->getAircraftType() == otherCode.getAircraftType()) { score += 2; }
|
||||
}
|
||||
}
|
||||
|
||||
// score needs to higher than ranking
|
||||
if (this->hasManufacturer() && otherCode.hasManufacturer())
|
||||
{
|
||||
if (this->matchesManufacturer(otherCode.getManufacturer()))
|
||||
{
|
||||
score += 20;
|
||||
}
|
||||
else if (this->getManufacturer().contains(otherCode.getManufacturer(), Qt::CaseInsensitive))
|
||||
{
|
||||
score += 15;
|
||||
}
|
||||
}
|
||||
|
||||
// 0..85
|
||||
return score;
|
||||
}
|
||||
|
||||
bool CAircraftIcaoCode::hasDesignator() const
|
||||
{
|
||||
return !this->m_designator.isEmpty();
|
||||
@@ -153,7 +202,7 @@ namespace BlackMisc
|
||||
int CAircraftIcaoCode::getEngineCount() const
|
||||
{
|
||||
if (this->m_combinedType.length() < 2) { return -1; }
|
||||
QString c(this->m_combinedType.mid(1, 1));
|
||||
const QString c(this->m_combinedType.mid(1, 1));
|
||||
if (c == "-") { return -1; }
|
||||
bool ok;
|
||||
int ec = c.toInt(&ok);
|
||||
@@ -212,6 +261,12 @@ namespace BlackMisc
|
||||
return !m_manufacturer.isEmpty();
|
||||
}
|
||||
|
||||
bool CAircraftIcaoCode::matchesManufacturer(const QString &manufacturer) const
|
||||
{
|
||||
if (manufacturer.isEmpty()) { return false; }
|
||||
return (manufacturer.length() == this->m_manufacturer.length() && this->m_manufacturer.startsWith(manufacturer, Qt::CaseInsensitive));
|
||||
}
|
||||
|
||||
bool CAircraftIcaoCode::isVtol() const
|
||||
{
|
||||
// special designators
|
||||
|
||||
@@ -159,6 +159,9 @@ namespace BlackMisc
|
||||
//! Manufacturer
|
||||
bool hasManufacturer() const;
|
||||
|
||||
//! Matching the manufacturer?
|
||||
bool matchesManufacturer(const QString &manufacturer) const;
|
||||
|
||||
//! Get WTC
|
||||
const QString &getWtc() const { return m_wtc; }
|
||||
|
||||
@@ -249,6 +252,10 @@ namespace BlackMisc
|
||||
//! As a brief HTML summary (e.g. used in tooltips)
|
||||
QString asHtmlSummary () const;
|
||||
|
||||
//! Considers rank, manufacturer and family 0..90
|
||||
//! \remark normally used with a selected set of ICAO codes or combined types
|
||||
int calculateScore(const CAircraftIcaoCode &otherCode) const;
|
||||
|
||||
//! Valid designator?
|
||||
static bool isValidDesignator(const QString &designator);
|
||||
|
||||
|
||||
@@ -146,6 +146,11 @@ namespace BlackMisc
|
||||
this->sortBy(&CAircraftIcaoCode::getDesignator, &CAircraftIcaoCode::getRank);
|
||||
}
|
||||
|
||||
void CAircraftIcaoCodeList::sortByDesignatorManufacturerAndRank()
|
||||
{
|
||||
this->sortBy(&CAircraftIcaoCode::getDesignator, &CAircraftIcaoCode::getManufacturer, &CAircraftIcaoCode::getRank);
|
||||
}
|
||||
|
||||
QStringList CAircraftIcaoCodeList::toCompleterStrings(bool withIataCodes, bool withFamily, bool sort) const
|
||||
{
|
||||
QStringList c;
|
||||
@@ -230,49 +235,38 @@ namespace BlackMisc
|
||||
}
|
||||
|
||||
// get an initial set of data we can choose from
|
||||
const QString d(icaoPattern.getDesignator());
|
||||
if (d.isEmpty()) { return CAircraftIcaoCode(); }
|
||||
CAircraftIcaoCodeList codes;
|
||||
|
||||
// try all designators, even unvalid ones
|
||||
if (!icaoPattern.getDesignator().isEmpty())
|
||||
do
|
||||
{
|
||||
const QString d(icaoPattern.getDesignator());
|
||||
codes = this->findByDesignator(d);
|
||||
if (!codes.isEmpty()) break;
|
||||
|
||||
// we have one exact match
|
||||
if (codes.size() == 1) { return codes.front(); }
|
||||
// now we search if the ICAO designator is actually an IATA code
|
||||
codes = this->findByIataCode(d);
|
||||
if (!codes.isEmpty()) break;
|
||||
|
||||
if (codes.isEmpty())
|
||||
if (d.length() > 4)
|
||||
{
|
||||
// now we search if the ICAO designator is
|
||||
// actually an IATA code
|
||||
codes = this->findByIataCode(d);
|
||||
|
||||
// we have one exact match
|
||||
if (codes.size() == 1) { return codes.front(); }
|
||||
|
||||
if (codes.isEmpty())
|
||||
{
|
||||
// still empty, try to find by family
|
||||
codes = this->findByFamily(d);
|
||||
|
||||
// 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; }
|
||||
|
||||
// continue here, we have more than one code and
|
||||
// will try to further reduce
|
||||
}
|
||||
codes = this->findByDesignator(d.left(4));
|
||||
if (!codes.isEmpty()) break;
|
||||
}
|
||||
codes.sortByRank();
|
||||
|
||||
// still empty, try to find by family
|
||||
codes = this->findByFamily(d);
|
||||
if (!codes.isEmpty()) break;
|
||||
|
||||
// now try to find as ending
|
||||
codes = this->findEndingWith(d);
|
||||
}
|
||||
while (false);
|
||||
|
||||
if (codes.isEmpty()) { return icaoPattern; }
|
||||
if (codes.size() == 1) { return codes.front(); }
|
||||
|
||||
// further reduce by manufacturer
|
||||
codes.sortByRank();
|
||||
if (icaoPattern.hasManufacturer() && codes.contains(&CAircraftIcaoCode::getManufacturer, icaoPattern.getManufacturer()))
|
||||
{
|
||||
const QString m(icaoPattern.getManufacturer());
|
||||
@@ -302,5 +296,24 @@ namespace BlackMisc
|
||||
}
|
||||
return codes.frontOrDefault(); // sorted by rank
|
||||
}
|
||||
|
||||
CAircraftIcaoCodeList CAircraftIcaoCodeList::groupByDesignatorAndManufacturer() const
|
||||
{
|
||||
CAircraftIcaoCodeList copy(*this);
|
||||
copy.sortByDesignatorManufacturerAndRank();
|
||||
CAircraftIcaoCodeList grouped;
|
||||
QString designator;
|
||||
QString manufacturer;
|
||||
for (const CAircraftIcaoCode code : as_const(copy))
|
||||
{
|
||||
if (code.getDesignator() != designator || code.getManufacturer() != manufacturer)
|
||||
{
|
||||
designator = code.getDesignator();
|
||||
manufacturer = code.getManufacturer();
|
||||
grouped.push_back(code);
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
@@ -82,12 +82,18 @@ namespace BlackMisc
|
||||
//! Best selection by given pattern, also searches IATA and family information
|
||||
CAircraftIcaoCode smartAircraftIcaoSelector(const CAircraftIcaoCode &icaoPattern) const;
|
||||
|
||||
//! Group by designator and manufacturer
|
||||
CAircraftIcaoCodeList groupByDesignatorAndManufacturer() const;
|
||||
|
||||
//! Sort by rank
|
||||
void sortByRank();
|
||||
|
||||
//! Sort by designator first, then by rank
|
||||
void sortByDesignatorAndRank();
|
||||
|
||||
//! Sort by designator first, then by manufacturer and rank
|
||||
void sortByDesignatorManufacturerAndRank();
|
||||
|
||||
//! For selection completion
|
||||
QStringList toCompleterStrings(bool withIataCodes = false, bool withFamily = false, bool sort = true) const;
|
||||
|
||||
|
||||
@@ -153,6 +153,11 @@ namespace BlackMisc
|
||||
return it != simplifiedName.end();
|
||||
}
|
||||
|
||||
bool CAirlineIcaoCode::hasSimplifiedName() const
|
||||
{
|
||||
return this->hasName() && !this->getSimplifiedName().isEmpty();
|
||||
}
|
||||
|
||||
bool CAirlineIcaoCode::hasCompleteData() const
|
||||
{
|
||||
return this->hasValidDesignator() && this->hasValidCountry() && this->hasName();
|
||||
@@ -391,6 +396,38 @@ namespace BlackMisc
|
||||
return this->getCombinedStringWithKey();
|
||||
}
|
||||
|
||||
int CAirlineIcaoCode::calculateScore(const CAirlineIcaoCode &otherCode) const
|
||||
{
|
||||
const bool bothFromDb = otherCode.isLoadedFromDb() && this->isLoadedFromDb();
|
||||
if (bothFromDb && this->getDbKey() == otherCode.getDbKey())
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
int score = 0;
|
||||
if (otherCode.hasValidDesignator() && this->getDesignator() == otherCode.getDesignator())
|
||||
{
|
||||
score += 60;
|
||||
}
|
||||
|
||||
if (bothFromDb && this->isVirtualAirline() == otherCode.isVirtualAirline())
|
||||
{
|
||||
score += 20;
|
||||
}
|
||||
if (this->hasName() && this->getName() == otherCode.getName())
|
||||
{
|
||||
score += 20;
|
||||
}
|
||||
else if (this->hasTelephonyDesignator() && this->getTelephonyDesignator() == otherCode.getTelephonyDesignator())
|
||||
{
|
||||
score += 15;
|
||||
}
|
||||
else if (this->hasSimplifiedName() && this->getSimplifiedName() == otherCode.getSimplifiedName())
|
||||
{
|
||||
score += 10;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
CAirlineIcaoCode CAirlineIcaoCode::fromDatabaseJson(const QJsonObject &json, const QString &prefix)
|
||||
{
|
||||
if (!existsKey(json, prefix))
|
||||
|
||||
@@ -164,6 +164,9 @@ namespace BlackMisc
|
||||
//! Has (airline) name?
|
||||
bool hasName() const { return !m_name.isEmpty(); }
|
||||
|
||||
//! Has simplified airline name?
|
||||
bool hasSimplifiedName() const;
|
||||
|
||||
//! Complete data
|
||||
bool hasCompleteData() const;
|
||||
|
||||
@@ -197,6 +200,9 @@ namespace BlackMisc
|
||||
//! As a brief HTML summary (e.g. used in tooltips)
|
||||
QString asHtmlSummary () const;
|
||||
|
||||
//! Score against other code 0..100
|
||||
int calculateScore(const CAirlineIcaoCode &otherCode) const;
|
||||
|
||||
//! Valid designator?
|
||||
static bool isValidAirlineDesignator(const QString &airline);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace BlackMisc
|
||||
|
||||
CAirlineIcaoCodeList CAirlineIcaoCodeList::findByDesignator(const QString &designator) const
|
||||
{
|
||||
if (CAirlineIcaoCode::isValidAirlineDesignator(designator)) { return CAirlineIcaoCodeList(); }
|
||||
if (!CAirlineIcaoCode::isValidAirlineDesignator(designator)) { return CAirlineIcaoCodeList(); }
|
||||
return this->findBy([&](const CAirlineIcaoCode & code)
|
||||
{
|
||||
return code.matchesDesignator(designator);
|
||||
@@ -115,7 +115,16 @@ namespace BlackMisc
|
||||
CAirlineIcaoCodeList codesFound;
|
||||
if (patternUsed.hasValidDesignator())
|
||||
{
|
||||
codesFound = this->findByVDesignator(patternUsed.getVDesignator());
|
||||
if (patternUsed.isVirtualAirline())
|
||||
{
|
||||
// we can tell for sure we search an VA
|
||||
codesFound = this->findByVDesignator(patternUsed.getVDesignator());
|
||||
}
|
||||
else
|
||||
{
|
||||
// we do not know if we are looking for an VA
|
||||
codesFound = this->findByDesignator(patternUsed.getDesignator());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -98,6 +98,11 @@ namespace BlackMisc
|
||||
return m_colorTail.isValid();
|
||||
}
|
||||
|
||||
bool CLivery::hasValidColors() const
|
||||
{
|
||||
return this->hasColorFuselage() && this->hasColorTail();
|
||||
}
|
||||
|
||||
bool CLivery::matchesCombinedCode(const QString &candidate) const
|
||||
{
|
||||
if (candidate.isEmpty() || !this->hasCombinedCode()) { return false; }
|
||||
@@ -185,6 +190,11 @@ namespace BlackMisc
|
||||
return m_combinedCode.startsWith(colorLiveryMarker());
|
||||
}
|
||||
|
||||
double CLivery::getColorDistance(const CLivery &otherLivery) const
|
||||
{
|
||||
return this->getColorDistance(otherLivery.getColorFuselage(), otherLivery.getColorTail());
|
||||
}
|
||||
|
||||
double CLivery::getColorDistance(const CRgbColor &fuselage, const CRgbColor &tail) const
|
||||
{
|
||||
if (!fuselage.isValid() || !tail.isValid()) { return 1.0; }
|
||||
@@ -256,7 +266,7 @@ namespace BlackMisc
|
||||
|
||||
QString CLivery::getStandardCode(const CAirlineIcaoCode &airline)
|
||||
{
|
||||
QString code(airline.getDesignator());
|
||||
QString code(airline.getVDesignator());
|
||||
return code.isEmpty() ? "" : code.append('.').append(standardLiveryMarker());
|
||||
}
|
||||
|
||||
@@ -383,5 +393,34 @@ namespace BlackMisc
|
||||
return this->getCombinedCodePlusInfo();
|
||||
}
|
||||
|
||||
int CLivery::calculateScore(const CLivery &otherLivery) const
|
||||
{
|
||||
int score = 0;
|
||||
if (this->isLoadedFromDb() && otherLivery.isLoadedFromDb() && (this->getCombinedCode() == otherLivery.getCombinedCode()))
|
||||
{
|
||||
return 100; // exact
|
||||
}
|
||||
else
|
||||
{
|
||||
score += 0.35 * this->getAirlineIcaoCode().calculateScore(otherLivery.getAirlineIcaoCode());
|
||||
}
|
||||
|
||||
// 0..50 so far
|
||||
if (score == 0) { return 0; }
|
||||
if (this->isAirlineStandardLivery()) { score += 10; }
|
||||
|
||||
// 0..60 so far
|
||||
if (!this->hasValidColors()) { return score; }
|
||||
const double cd = this->getColorDistance(otherLivery); // 0..1
|
||||
if (cd == 0)
|
||||
{
|
||||
score += 40;
|
||||
}
|
||||
else
|
||||
{
|
||||
score += (1.0 - cd) * 25; // 0..25
|
||||
}
|
||||
return score;
|
||||
}
|
||||
} // namespace
|
||||
} // namespace
|
||||
|
||||
@@ -113,6 +113,9 @@ namespace BlackMisc
|
||||
//! Tail color set?
|
||||
bool hasColorTail() const;
|
||||
|
||||
//! Has valid (fuselage/tail) colors?
|
||||
bool hasValidColors() const;
|
||||
|
||||
//! Set description
|
||||
void setDescription(const QString &description) { this->m_description = description; }
|
||||
|
||||
@@ -158,14 +161,21 @@ namespace BlackMisc
|
||||
//! Color livery
|
||||
bool isColorLivery() const;
|
||||
|
||||
//! Combined color distance (fuselage/tail): 0..1
|
||||
//! Color distance 0..1 (0 is best)
|
||||
double getColorDistance(const CLivery &otherLivery) const;
|
||||
|
||||
//! Combined color distance (fuselage/tail): 0..1 (0 is best)
|
||||
double getColorDistance(const BlackMisc::CRgbColor &fuselage, const BlackMisc::CRgbColor &tail) const;
|
||||
|
||||
//! Update missing parts
|
||||
void updateMissingParts(const CLivery &otherLivery);
|
||||
|
||||
//! As a brief HTML summary (e.g. used in tooltips)
|
||||
QString asHtmlSummary () const;
|
||||
QString asHtmlSummary() const;
|
||||
|
||||
//! Score by comparison to another livery 0..100
|
||||
//! \remark normally used with liveries preselect by airline ICAO code
|
||||
int calculateScore(const CLivery &otherLivery) const;
|
||||
|
||||
//! Object from JSON
|
||||
static CLivery fromDatabaseJson(const QJsonObject &json, const QString &prefix = QString("liv_"));
|
||||
|
||||
@@ -584,6 +584,13 @@ namespace BlackMisc
|
||||
this->m_modelString.startsWith(modelString, sensitivity);
|
||||
}
|
||||
|
||||
int CAircraftModel::calculateScore(const CAircraftModel &compareModel) const
|
||||
{
|
||||
int score = this->getAircraftIcaoCode().calculateScore(compareModel.getAircraftIcaoCode());
|
||||
score += this->getLivery().calculateScore(compareModel.getLivery());
|
||||
return 0.5 * score;
|
||||
}
|
||||
|
||||
CStatusMessageList CAircraftModel::validate(bool withNestedObjects) const
|
||||
{
|
||||
static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::validation() }));
|
||||
|
||||
@@ -338,6 +338,9 @@ namespace BlackMisc
|
||||
//! Matches model string?
|
||||
bool matchesModelString(const QString &modelString, Qt::CaseSensitivity sensitivity) const;
|
||||
|
||||
//! Calculate score
|
||||
int calculateScore(const CAircraftModel &compareModel) const;
|
||||
|
||||
//! Validate
|
||||
BlackMisc::CStatusMessageList validate(bool withNestedObjects) const;
|
||||
|
||||
@@ -368,7 +371,7 @@ namespace BlackMisc
|
||||
//! Model mode
|
||||
static ModelMode modelModeFromString(const QString &mode);
|
||||
|
||||
//! Model mode-
|
||||
//! Model mode
|
||||
static const QString &modelModeToString(ModelMode mode);
|
||||
|
||||
//! From swift DB JSON
|
||||
|
||||
@@ -528,6 +528,18 @@ namespace BlackMisc
|
||||
}
|
||||
}
|
||||
|
||||
ScoredModels CAircraftModelList::scoreFull(const CAircraftModel &remoteModel, bool ignoreZeroScores) const
|
||||
{
|
||||
ScoredModels scoreMap;
|
||||
for (const CAircraftModel &model : *this)
|
||||
{
|
||||
const int score = model.calculateScore(remoteModel);
|
||||
if (ignoreZeroScores && score < 1) { continue; }
|
||||
scoreMap.insertMulti(score, model);
|
||||
}
|
||||
return scoreMap;
|
||||
}
|
||||
|
||||
QStringList CAircraftModelList::toCompleterStrings(bool sorted) const
|
||||
{
|
||||
QStringList c;
|
||||
|
||||
@@ -43,6 +43,9 @@ namespace BlackMisc
|
||||
|
||||
namespace Simulation
|
||||
{
|
||||
//! Individual (matching) score for each model
|
||||
using ScoredModels = QMap<int, CAircraftModel>;
|
||||
|
||||
//! Value object encapsulating a list of aircraft models
|
||||
class BLACKMISC_EXPORT CAircraftModelList :
|
||||
public BlackMisc::CSequence<CAircraftModel>,
|
||||
@@ -204,6 +207,9 @@ namespace BlackMisc
|
||||
//! File name normalized for DB
|
||||
void normalizeFileNamesForDb();
|
||||
|
||||
//! Score by aircraft ICAO code
|
||||
ScoredModels scoreFull(const CAircraftModel &remoteModel, bool ignoreZeroScores = true) const;
|
||||
|
||||
//! Completer strings
|
||||
QStringList toCompleterStrings(bool sorted = true) const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user