diff --git a/src/blackmisc/aviation/aircrafticaocode.cpp b/src/blackmisc/aviation/aircrafticaocode.cpp index addf5f9a6..2f303ac2b 100644 --- a/src/blackmisc/aviation/aircrafticaocode.cpp +++ b/src/blackmisc/aviation/aircrafticaocode.cpp @@ -55,6 +55,19 @@ namespace BlackMisc if (m_rank < 0 || m_rank >= 10) { m_rank = 10; } } + CAircraftIcaoCode::CAircraftIcaoCode(const QString &icao, const QString &iata, const QString &family, const QString &combinedType, const QString &manufacturer, + const QString &model, const QString &modelIata, const QString &modelSwift, const QString &wtc, bool realworld, bool legacy, bool military, int rank) + : m_designator(icao.trimmed().toUpper()), + m_iataCode(iata.trimmed().toUpper()), + m_family(family.trimmed().toUpper()), + m_combinedType(combinedType.trimmed().toUpper()), + m_manufacturer(manufacturer.trimmed()), + m_modelDescription(model.trimmed()), m_modelIataDescription(modelIata.trimmed()), m_modelSwiftDescription(modelSwift.trimmed()), + m_wtc(wtc.trimmed().toUpper()), m_realWorld(realworld), m_legacy(legacy), m_military(military), m_rank(rank) + { + if (m_rank < 0 || m_rank >= 10) { m_rank = 10; } + } + QString CAircraftIcaoCode::getDesignatorDbKey() const { if (this->isLoadedFromDb()) @@ -83,6 +96,9 @@ namespace BlackMisc if (!this->hasValidCombinedType() && otherIcaoCode.hasValidCombinedType()) { this->setCombinedType(otherIcaoCode.getCombinedType()); } if (this->m_manufacturer.isEmpty()) { this->setManufacturer(otherIcaoCode.getManufacturer());} if (this->m_modelDescription.isEmpty()) { this->setModelDescription(otherIcaoCode.getModelDescription()); } + if (this->m_modelIataDescription.isEmpty()) { this->setModelIataDescription(otherIcaoCode.getModelIataDescription()); } + if (this->m_modelSwiftDescription.isEmpty()) { this->setModelSwiftDescription(otherIcaoCode.getModelSwiftDescription()); } + if (this->m_family.isEmpty()) { this->setFamily(otherIcaoCode.getFamily()); } if (!this->hasValidDbKey()) { // need to observe if it makes sense to copy the key but not copying the whole object @@ -224,6 +240,18 @@ namespace BlackMisc return c; } + QString CAircraftIcaoCode::getCombinedModelDescription() const + { + // Shortcut for most cases + if (!this->hasModelIataDescription() && !this->hasModelSwiftDescription()) { return this->getModelDescription(); } + + QStringList combined({ this->getModelDescription() }); + if (this->hasModelIataDescription()) { combined.append(this->getModelIataDescription()); } + if (this->hasModelSwiftDescription()) { combined.append(this->getModelSwiftDescription()); } + combined.removeDuplicates(); + return combined.join(", "); + } + bool CAircraftIcaoCode::matchesCombinedType(const QString &combinedType) const { const QString cc(combinedType.toUpper().trimmed().replace(' ', '*').replace('-', '*')); @@ -291,6 +319,12 @@ namespace BlackMisc return false; } + bool CAircraftIcaoCode::isDbDuplicate() const + { + return m_modelIataDescription.startsWith("duplicate", Qt::CaseInsensitive) || + m_modelSwiftDescription.startsWith("duplicate", Qt::CaseInsensitive); + } + void CAircraftIcaoCode::setCodeFlags(bool military, bool legacy, bool realWorld) { m_military = military; @@ -402,7 +436,7 @@ namespace BlackMisc { if (index.isMyself()) { return CVariant::from(*this); } if (IDatastoreObjectWithIntegerKey::canHandleIndex(index)) { return IDatastoreObjectWithIntegerKey::propertyByIndex(index); } - ColumnIndex i = index.frontCasted(); + const ColumnIndex i = index.frontCasted(); switch (i) { case IndexAircraftDesignator: @@ -415,6 +449,12 @@ namespace BlackMisc return CVariant::fromValue(this->m_combinedType); case IndexModelDescription: return CVariant::fromValue(this->m_modelDescription); + case IndexModelIataDescription: + return CVariant::fromValue(this->m_modelIataDescription); + case IndexModelSwiftDescription: + return CVariant::fromValue(this->m_modelSwiftDescription); + case IndexCombinedDescription: + return CVariant::fromValue(this->getCombinedModelDescription()); case IndexManufacturer: return CVariant::fromValue(this->m_manufacturer); case IndexWtc: @@ -440,7 +480,7 @@ namespace BlackMisc { if (index.isMyself()) { (*this) = variant.to(); return; } if (IDatastoreObjectWithIntegerKey::canHandleIndex(index)) { IDatastoreObjectWithIntegerKey::setPropertyByIndex(index, variant); return; } - ColumnIndex i = index.frontCasted(); + const ColumnIndex i = index.frontCasted(); switch (i) { case IndexAircraftDesignator: @@ -458,6 +498,12 @@ namespace BlackMisc case IndexModelDescription: this->setModelDescription(variant.value()); break; + case IndexModelIataDescription: + this->setModelIataDescription(variant.value()); + break; + case IndexModelSwiftDescription: + this->setModelSwiftDescription(variant.value()); + break; case IndexManufacturer: this->setManufacturer(variant.value()); break; @@ -496,6 +542,24 @@ namespace BlackMisc return m_combinedType.compare(compareValue.getCombinedType(), Qt::CaseInsensitive); case IndexModelDescription: return m_modelDescription.compare(compareValue.getModelDescription(), Qt::CaseInsensitive); + case IndexModelIataDescription: + return m_modelIataDescription.compare(compareValue.getModelIataDescription(), Qt::CaseInsensitive); + case IndexModelSwiftDescription: + return m_modelSwiftDescription.compare(compareValue.getModelSwiftDescription(), Qt::CaseInsensitive); + case IndexCombinedDescription: + { + // compare without generating new strings + int c = m_modelDescription.compare(compareValue.getModelDescription(), Qt::CaseInsensitive); + if (c == 0) + { + c = m_modelIataDescription.compare(compareValue.getModelIataDescription(), Qt::CaseInsensitive); + if (c == 0) + { + c = m_modelSwiftDescription.compare(compareValue.getModelSwiftDescription(), Qt::CaseInsensitive); + } + } + return c; + } case IndexManufacturer: return m_manufacturer.compare(compareValue.getManufacturer(), Qt::CaseInsensitive); case IndexWtc: @@ -615,15 +679,17 @@ namespace BlackMisc return CAircraftIcaoCode(); } - QString designator(json.value(prefix + "designator").toString()); - QString iata(json.value(prefix + "iata").toString()); - QString family(json.value(prefix + "family").toString()); - QString manufacturer(json.value(prefix + "manufacturer").toString()); - QString model(json.value(prefix + "model").toString()); - QString type(json.value(prefix + "type").toString()); - QString engine(json.value(prefix + "engine").toString()); - int engineCount(json.value(prefix + "enginecount").toInt(-1)); - QString combined(createdCombinedString(type, engineCount, engine)); + const QString designator(json.value(prefix + "designator").toString()); + const QString iata(json.value(prefix + "iata").toString()); + const QString family(json.value(prefix + "family").toString()); + const QString manufacturer(json.value(prefix + "manufacturer").toString()); + const QString model(json.value(prefix + "model").toString()); + const QString modelIata(json.value(prefix + "modeliata").toString()); + const QString modelSwift(json.value(prefix + "modelswift").toString()); + const QString type(json.value(prefix + "type").toString()); + const QString engine(json.value(prefix + "engine").toString()); + const int engineCount(json.value(prefix + "enginecount").toInt(-1)); + const QString combined(createdCombinedString(type, engineCount, engine)); QString wtc(json.value(prefix + "wtc").toString()); if (wtc.length() > 1 && wtc.contains("/")) { @@ -632,17 +698,16 @@ namespace BlackMisc } Q_ASSERT_X(wtc.length() < 2, Q_FUNC_INFO, "WTC too long"); - bool real = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "realworld").toString()); - bool legacy = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "legacy").toString()); - bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "military").toString()); - int rank(json.value(prefix + "rank").toInt(10)); + const bool real = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "realworld").toString()); + const bool legacy = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "legacy").toString()); + const bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix + "military").toString()); + const int rank(json.value(prefix + "rank").toInt(10)); CAircraftIcaoCode code( - designator, iata, combined, - manufacturer, model, wtc, + designator, iata, family, combined, manufacturer, + model, modelIata, modelSwift, wtc, real, legacy, military, rank ); - code.setFamily(family); code.setKeyAndTimestampFromDatabaseJson(json, prefix); return code; } @@ -650,39 +715,17 @@ namespace BlackMisc QString CAircraftIcaoCode::createdCombinedString(const QString &type, const QString &engineCount, const QString &engine) { Q_ASSERT_X(engineCount.length() < 2, Q_FUNC_INFO, "Wrong engine count"); - - QString c(type.trimmed().toUpper().left(1)); - if (c.isEmpty()) { c.append("-"); } - if (engineCount.isEmpty()) - { - c.append("-"); - } - else - { - c.append(engineCount); - } - if (engine.isEmpty()) - { - c.append("-"); - } - else - { - c.append(engine.at(0)); - } + QString c(type.isEmpty() ? "-" : type.trimmed().left(1).toUpper()); + c += engineCount.isEmpty() ? "-" : engineCount.trimmed(); + c += engine.isEmpty() ? "-" : engine.trimmed().left(1).toUpper(); Q_ASSERT_X(c.length() == 3, Q_FUNC_INFO, "Wrong combined length"); return c; } QString CAircraftIcaoCode::createdCombinedString(const QString &type, int engineCount, const QString &engine) { - if (engineCount >= 0 && engineCount < 10) - { - return createdCombinedString(type, QString::number(engineCount), engine); - } - else - { - return createdCombinedString(type, "", engine); - } + const bool valid = engineCount >= 0 && engineCount < 10; + return createdCombinedString(type, valid ? QString::number(engineCount) : "", engine); } } // namespace } // namespace diff --git a/src/blackmisc/aviation/aircrafticaocode.h b/src/blackmisc/aviation/aircrafticaocode.h index 8e4ad803d..880df218f 100644 --- a/src/blackmisc/aviation/aircrafticaocode.h +++ b/src/blackmisc/aviation/aircrafticaocode.h @@ -44,13 +44,16 @@ namespace BlackMisc IndexCombinedAircraftType, IndexManufacturer, IndexModelDescription, + IndexModelIataDescription, + IndexModelSwiftDescription, + IndexCombinedDescription, IndexWtc, IndexIsRealworld, IndexIsMilitary, IndexIsLegacy, IndexIsVtol, IndexRank, - IndexDesignatorManufacturer //!< designator and manufacturer + IndexDesignatorManufacturer //!< designator and manufacturer }; //! Default constructor. @@ -67,6 +70,10 @@ namespace BlackMisc CAircraftIcaoCode(const QString &icao, const QString &iata, const QString &combinedType, const QString &manufacturer, const QString &model, const QString &wtc, bool realworld, bool legacy, bool military, int rank); + //! Constructor + CAircraftIcaoCode(const QString &icao, const QString &iata, const QString &family, const QString &combinedType, const QString &manufacturer, + const QString &model, const QString &modelIata, const QString &modelSwift, const QString &wtc, bool realworld, bool legacy, bool military, int rank); + //! Get ICAO designator, e.g. "B737" const QString &getDesignator() const { return m_designator; } @@ -133,9 +140,18 @@ namespace BlackMisc //! Set type void setCombinedType(const QString &type) { this->m_combinedType = type.trimmed().toUpper(); } - //! Get model description, e.g. "A-330-200" + //! Get IACO model description, e.g. "A-330-200" const QString &getModelDescription() const { return m_modelDescription; } + //! Get IATA model description + const QString &getModelIataDescription() const { return m_modelIataDescription; } + + //! Get swift model description + const QString &getModelSwiftDescription() const { return m_modelSwiftDescription; } + + //! Combined description + QString getCombinedModelDescription() const; + //! Matches given combined code //! \remark * can be used as wildcard, e.g. L*J, L** bool matchesCombinedType(const QString &combinedType) const; @@ -143,12 +159,24 @@ namespace BlackMisc //! Designator + Manufacturer QString getDesignatorManufacturer() const; - //! Set the model description + //! Set the model description (ICAO description) void setModelDescription(const QString &modelDescription) { m_modelDescription = modelDescription.trimmed(); } - //! Has model description + //! Set the alternative IATA model description + void setModelIataDescription(const QString &modelDescription) { m_modelIataDescription = modelDescription.trimmed(); } + + //! Set the alternative swift model description + void setModelSwiftDescription(const QString &modelDescription) { m_modelSwiftDescription = modelDescription.trimmed(); } + + //! Has model description? bool hasModelDescription() const { return !this->m_modelDescription.isEmpty(); } + //! Has IATA model description? + bool hasModelIataDescription() const { return !this->m_modelIataDescription.isEmpty(); } + + //! Has swift model description? + bool hasModelSwiftDescription() const { return !this->m_modelSwiftDescription.isEmpty(); } + //! Get manufacturer, e.g. "Airbus" const QString &getManufacturer() const { return m_manufacturer; } @@ -182,6 +210,10 @@ namespace BlackMisc //! Legacy aircraft (no current ICAO code) bool isLegacyAircraft() const { return m_legacy; } + //! Is DB duplicate, means redundant ICAO DB entry. + //! \see https://aviation.stackexchange.com/q/37848/4024 + bool isDbDuplicate() const; + //! Flags void setCodeFlags(bool military, bool legacy, bool realWorld); @@ -280,17 +312,19 @@ namespace BlackMisc static CAircraftIcaoCode fromDatabaseJson(const QJsonObject &json, const QString &prefix = QString()); private: - QString m_designator; //!< "B737" - QString m_iataCode; //!< "320" - QString m_family; //!< "A350" (not a real ICAO code, but a family) - QString m_combinedType; //!< "L2J" - QString m_manufacturer; //!< "Airbus" - QString m_modelDescription; //!< "A-330-200" - QString m_wtc; //!< wake turbulence "M","H" "L/M", "L", we only use the one letter versions - bool m_realWorld = true; //!< real world aircraft - bool m_legacy = false; //!< legacy code - bool m_military = false; //!< military aircraft? - int m_rank = 10; //!< rank among same codes + QString m_designator; //!< "B737" + QString m_iataCode; //!< "320" + QString m_family; //!< "A350" (not a real ICAO code, but a family) + QString m_combinedType; //!< "L2J" + QString m_manufacturer; //!< "Airbus" + QString m_modelDescription; //!< "A-330-200", the ICAO description + QString m_modelIataDescription; //!< alternative IATA description + QString m_modelSwiftDescription; //!< alternative swift description + QString m_wtc; //!< wake turbulence "M","H" "L/M", "L", we only use the one letter versions + bool m_realWorld = true; //!< real world aircraft + bool m_legacy = false; //!< legacy code + bool m_military = false; //!< military aircraft? + int m_rank = 10; //!< rank among same codes //! Create a combined string like L2J static QString createdCombinedString(const QString &type, const QString &engineCount, const QString &engine); @@ -308,6 +342,8 @@ namespace BlackMisc BLACK_METAMEMBER(combinedType), BLACK_METAMEMBER(manufacturer), BLACK_METAMEMBER(modelDescription), + BLACK_METAMEMBER(modelIataDescription), + BLACK_METAMEMBER(modelSwiftDescription), BLACK_METAMEMBER(wtc), BLACK_METAMEMBER(military), BLACK_METAMEMBER(realWorld), diff --git a/src/blackmisc/aviation/aircrafticaocodelist.cpp b/src/blackmisc/aviation/aircrafticaocodelist.cpp index 725b44695..321578d4f 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.cpp +++ b/src/blackmisc/aviation/aircrafticaocodelist.cpp @@ -172,6 +172,11 @@ namespace BlackMisc this->removeIf([](const CAircraftIcaoCode & icao) { return !icao.hasValidCombinedType(); }); } + void CAircraftIcaoCodeList::removeDuplicates() + { + this->removeIf(&CAircraftIcaoCode::isDbDuplicate, true); + } + QStringList CAircraftIcaoCodeList::toCompleterStrings(bool withIataCodes, bool withFamily, bool sort) const { QStringList c; @@ -218,6 +223,18 @@ namespace BlackMisc return c; } + QSet CAircraftIcaoCodeList::allFamilies() const + { + QSet c; + for (const CAircraftIcaoCode &icao : *this) + { + if (!icao.hasFamily()) { continue; } + const QString d(icao.getFamily()); + c.insert(d); + } + return c; + } + QSet CAircraftIcaoCodeList::allManufacturers(bool onlyKnownDesignators) const { QSet c; @@ -231,15 +248,16 @@ namespace BlackMisc return c; } - CAircraftIcaoCodeList CAircraftIcaoCodeList::fromDatabaseJson(const QJsonArray &array, bool ignoreIncomplete) + CAircraftIcaoCodeList CAircraftIcaoCodeList::fromDatabaseJson(const QJsonArray &array, bool ignoreIncompleteAndDuplicates) { CAircraftIcaoCodeList codes; for (const QJsonValue &value : array) { - CAircraftIcaoCode icao(CAircraftIcaoCode::fromDatabaseJson(value.toObject())); - if (ignoreIncomplete && !icao.hasSpecialDesignator() && !icao.hasCompleteData()) + const CAircraftIcaoCode icao(CAircraftIcaoCode::fromDatabaseJson(value.toObject())); + if (ignoreIncompleteAndDuplicates) { - continue; + if (!icao.hasSpecialDesignator() && !icao.hasCompleteData()) { continue; } + if (icao.isDbDuplicate()) { continue; } } codes.push_back(icao); } @@ -250,7 +268,7 @@ namespace BlackMisc { if (icaoPattern.hasValidDbKey()) { - int k = icaoPattern.getDbKey(); + const int k = icaoPattern.getDbKey(); CAircraftIcaoCode c(this->findByKey(k)); if (c.hasCompleteData()) { return c; } } diff --git a/src/blackmisc/aviation/aircrafticaocodelist.h b/src/blackmisc/aviation/aircrafticaocodelist.h index 50a12159a..38fb933f0 100644 --- a/src/blackmisc/aviation/aircrafticaocodelist.h +++ b/src/blackmisc/aviation/aircrafticaocodelist.h @@ -103,17 +103,23 @@ namespace BlackMisc //! Remove invalid combined codes void removeInvalidCombinedCodes(); + //! Remove duplicates as marked by CAircraftIcaoCode::isDbDuplicate + void removeDuplicates(); + //! For selection completion QStringList toCompleterStrings(bool withIataCodes = false, bool withFamily = false, bool sort = true) const; //! All ICAO codes, no duplicates QSet allIcaoCodes(bool noUnspecified = true) const; + //! All families, no duplicates + QSet allFamilies() const; + //! All manufacturers QSet allManufacturers(bool onlyKnownDesignators = true) const; //! From our database JSON format - static CAircraftIcaoCodeList fromDatabaseJson(const QJsonArray &array, bool ignoreIncomplete = true); + static CAircraftIcaoCodeList fromDatabaseJson(const QJsonArray &array, bool ignoreIncompleteAndDuplicates = true); }; } //namespace } // namespace