refs #820, utility functions for score and groupBy

This commit is contained in:
Klaus Basan
2016-12-02 02:50:12 +01:00
parent d398fc7e9e
commit 630ec78d38
13 changed files with 250 additions and 40 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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))

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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

View File

@@ -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_"));

View File

@@ -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() }));

View File

@@ -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

View File

@@ -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;

View File

@@ -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;