/* 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 "airlineicaocode.h" #include "callsign.h" #include "blackmisc/db/datastoreutility.h" #include "blackmisc/comparefunctions.h" #include "blackmisc/icons.h" #include "blackmisc/logcategory.h" #include "blackmisc/logcategorylist.h" #include "blackmisc/propertyindex.h" #include "blackmisc/statusmessage.h" #include "blackmisc/stringutils.h" #include "blackmisc/directoryutils.h" #include "blackmisc/variant.h" #include "blackmisc/verify.h" #include #include #include #include #include #include #include #include using namespace BlackMisc; using namespace BlackMisc::Db; namespace BlackMisc { namespace Aviation { CAirlineIcaoCode::CAirlineIcaoCode(const QString &airlineDesignator) : m_designator(airlineDesignator.trimmed().toUpper()) { if (this->m_designator.length() == 4) { this->setDesignator(this->m_designator); } } CAirlineIcaoCode::CAirlineIcaoCode(const QString &airlineDesignator, const QString &airlineName, const BlackMisc::CCountry &country, const QString &telephony, bool virtualAirline, bool operating) : m_designator(airlineDesignator.trimmed().toUpper()), m_name(airlineName), m_telephonyDesignator(telephony), m_country(country), m_isVa(virtualAirline), m_isOperating(operating) { if (this->m_designator.length() == 4) { this->setDesignator(this->m_designator); } } const QString CAirlineIcaoCode::getVDesignator() const { if (!isVirtualAirline()) { return this->m_designator; } return QLatin1Char('V') % this->m_designator; } QString CAirlineIcaoCode::getVDesignatorDbKey() const { return this->isLoadedFromDb() ? this->getVDesignator() % this->getDbKeyAsStringInParentheses(" ") : this->getVDesignator(); } void CAirlineIcaoCode::setDesignator(const QString &icaoDesignator) { this->m_designator = icaoDesignator.trimmed().toUpper(); if (this->m_designator.length() == 4 && this->m_designator.startsWith("V")) { // a virtual designator was provided this->setVirtualAirline(true); this->m_designator = this->m_designator.right(3); } } QString CAirlineIcaoCode::getDesignatorNameCountry() const { QString s(this->getDesignator()); if (this->hasName()) { s = s.append(" ").append(this->getName()); } if (this->hasValidCountry()) { s = s.append(" ").append(this->getCountryIso()); } return s.trimmed(); } QString CAirlineIcaoCode::getSimplifiedName() const { return BlackMisc::simplifyNameForSearch(this->getName()); } bool CAirlineIcaoCode::hasValidCountry() const { return this->m_country.isValid(); } bool CAirlineIcaoCode::hasValidDesignator() const { return isValidAirlineDesignator(m_designator); } bool CAirlineIcaoCode::hasIataCode() const { return !this->m_iataCode.isEmpty(); } bool CAirlineIcaoCode::matchesDesignator(const QString &designator) const { if (designator.isEmpty()) { return false; } return designator.trimmed().toUpper() == this->m_designator; } bool CAirlineIcaoCode::matchesVDesignator(const QString &designator) const { if (designator.isEmpty()) { return false; } return designator.trimmed().toUpper() == this->getVDesignator(); } bool CAirlineIcaoCode::matchesIataCode(const QString &iata) const { if (iata.isEmpty()) { return false; } return iata.trimmed().toUpper() == this->m_iataCode; } bool CAirlineIcaoCode::matchesDesignatorOrIataCode(const QString &candidate) const { if (candidate.isEmpty()) { return false; } return this->matchesDesignator(candidate) || this->matchesIataCode(candidate); } bool CAirlineIcaoCode::matchesVDesignatorOrIataCode(const QString &candidate) const { if (candidate.isEmpty()) { return false; } return this->matchesVDesignator(candidate) || this->matchesIataCode(candidate); } bool CAirlineIcaoCode::matchesNamesOrTelephonyDesignator(const QString &candidate) const { const QString cand(candidate.toUpper().trimmed()); if (this->getName().contains(cand, Qt::CaseInsensitive) || this->getTelephonyDesignator().contains(cand, Qt::CaseInsensitive)) { return true; } return this->isContainedInSimplifiedName(candidate); } bool CAirlineIcaoCode::isContainedInSimplifiedName(const QString &candidate) const { if (candidate.isEmpty() || !this->hasName()) { return false; } auto simplifiedName = makeRange(getName().begin(), getName().end()).findBy([](QChar c) { return c.isLetter(); }); auto it = std::search(simplifiedName.begin(), simplifiedName.end(), candidate.begin(), candidate.end(), [](QChar a, QChar b) { return a.toUpper() == b.toUpper(); }); 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(); } CIcon CAirlineIcaoCode::toIcon() const { if (this->hasValidDbKey() && CAirlineIcaoCode::iconIds().contains(this->getDbKey())) { static const QString p("airlines/%1_%2.png"); const QString n(p.arg(this->getDbKey(), 5, 10, QChar('0')).arg(this->getDesignator())); return CIcon(n, this->convertToQString()); } return CIcon::iconByIndex(CIcons::StandardIconEmpty); } QString CAirlineIcaoCode::convertToQString(bool i18n) const { Q_UNUSED(i18n); return m_designator % QLatin1String(" (") % m_name % QLatin1String(")") % QLatin1String(" Op: ") % boolToYesNo(this->isOperating()) % QLatin1String(" VA: ") % boolToYesNo(this->isVirtualAirline()) % QLatin1String(" Mil: ") % boolToYesNo(this->isMilitary()); } CVariant CAirlineIcaoCode::propertyByIndex(const BlackMisc::CPropertyIndex &index) const { if (index.isMyself()) { return CVariant::from(*this); } if (IDatastoreObjectWithIntegerKey::canHandleIndex(index)) { return IDatastoreObjectWithIntegerKey::propertyByIndex(index); } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexAirlineDesignator: return CVariant::fromValue(this->m_designator); case IndexIataCode: return CVariant::fromValue(this->m_iataCode); case IndexAirlineCountryIso: return CVariant::fromValue(this->getCountryIso()); case IndexAirlineCountry: return this->m_country.propertyByIndex(index.copyFrontRemoved()); case IndexAirlineName: return CVariant::fromValue(this->m_name); case IndexTelephonyDesignator: return CVariant::fromValue(this->m_telephonyDesignator); case IndexIsVirtualAirline: return CVariant::fromValue(this->m_isVa); case IndexIsOperating: return CVariant::fromValue(this->m_isOperating); case IndexIsMilitary: return CVariant::fromValue(this->m_isMilitary); case IndexDesignatorNameCountry: return CVariant::fromValue(this->getDesignatorNameCountry()); case IndexGroupDesignator: return CVariant::fromValue(this->getGroupDesignator()); case IndexGroupName: return CVariant::fromValue(this->getGroupName()); case IndexGroupId: return CVariant::fromValue(this->getGroupId()); default: return CValueObject::propertyByIndex(index); } } void CAirlineIcaoCode::setPropertyByIndex(const CPropertyIndex &index, const CVariant &variant) { if (index.isMyself()) { (*this) = variant.to(); return; } if (IDatastoreObjectWithIntegerKey::canHandleIndex(index)) { IDatastoreObjectWithIntegerKey::setPropertyByIndex(index, variant); return; } const ColumnIndex i = index.frontCasted(); switch (i) { case IndexAirlineDesignator: this->setDesignator(variant.value()); break; case IndexIataCode: this->setIataCode(variant.value()); break; case IndexAirlineCountry: this->setCountry(variant.value()); break; case IndexAirlineName: this->setName(variant.value()); break; case IndexTelephonyDesignator: this->setTelephonyDesignator(variant.value()); break; case IndexIsVirtualAirline: this->setVirtualAirline(variant.toBool()); break; case IndexIsOperating: this->setOperating(variant.toBool()); break; case IndexIsMilitary: this->setMilitary(variant.toBool()); break; case IndexGroupDesignator: this->setGroupDesignator(variant.toQString()); break; case IndexGroupName: this->setGroupName(variant.toQString()); break; case IndexGroupId: this->setGroupId(variant.toInt()); break; default: CValueObject::setPropertyByIndex(index, variant); break; } } int CAirlineIcaoCode::comparePropertyByIndex(const CPropertyIndex &index, const CAirlineIcaoCode &compareValue) const { if (index.isMyself()) { return m_designator.compare(compareValue.getDesignator(), Qt::CaseInsensitive); } if (IDatastoreObjectWithIntegerKey::canHandleIndex(index)) { return IDatastoreObjectWithIntegerKey::comparePropertyByIndex(index, compareValue);} const ColumnIndex i = index.frontCasted(); switch (i) { case IndexAirlineDesignator: return this->m_designator.compare(compareValue.getDesignator()); case IndexIataCode: return this->m_iataCode.compare(compareValue.getIataCode()); case IndexAirlineCountry: return this->m_country.comparePropertyByIndex(index.copyFrontRemoved(), compareValue.getCountry()); case IndexDesignatorNameCountry: return this->m_country.getName().compare(compareValue.getCountry().getName(), Qt::CaseInsensitive); case IndexAirlineName: return this->m_name.compare(compareValue.getName(), Qt::CaseInsensitive); case IndexTelephonyDesignator: return this->m_telephonyDesignator.compare(compareValue.getTelephonyDesignator(), Qt::CaseInsensitive); case IndexIsVirtualAirline: return Compare::compare(this->isVirtualAirline(), compareValue.isVirtualAirline()); case IndexIsOperating: return Compare::compare(this->isOperating(), compareValue.isOperating()); case IndexIsMilitary: return Compare::compare(this->isMilitary(), compareValue.isMilitary()); case IndexGroupDesignator: return this->m_groupDesignator.compare(compareValue.getGroupDesignator(), Qt::CaseInsensitive); case IndexGroupName: return this->m_groupName.compare(compareValue.getGroupName(), Qt::CaseInsensitive); case IndexGroupId: return Compare::compare(this->m_groupId, compareValue.getGroupId()); default: break; } Q_ASSERT_X(false, Q_FUNC_INFO, "No compare function"); return 0; } CStatusMessageList CAirlineIcaoCode::validate() const { static const CLogCategoryList cats(CLogCategoryList(this).join({ CLogCategory::validation() })); CStatusMessageList msgs; if (!hasValidDesignator()) { msgs.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, "Airline: missing designator")); } if (!hasValidCountry()) { msgs.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, "Airline: missing country")); } if (!hasName()) { msgs.push_back(CStatusMessage(cats, CStatusMessage::SeverityError, "Airline: no name")); } return msgs; } bool CAirlineIcaoCode::isValidAirlineDesignator(const QString &airline) { // allow 2 chars for IATA if (airline.length() < 2 || airline.length() > 5) { return false; } const auto chars = makeRange(airline.begin(), airline.end()); if (chars.containsBy([](QChar c) { return !c.isUpper() && !c.isDigit(); })) { return false; } return true; } QSet CAirlineIcaoCode::specialValidDesignators() { static const QSet valid({ "VV", "VM"}); return valid; } QString CAirlineIcaoCode::normalizeDesignator(const QString &candidate) { QString n(candidate.trimmed().toUpper()); n = n.left(indexOfChar(n, [](QChar c) { return c.isSpace(); })); return removeChars(n, [](QChar c) { return !c.isLetterOrNumber(); }); } QString CAirlineIcaoCode::getCombinedStringWithKey() const { QString s(getVDesignator()); if (s.isEmpty()) s = "????"; if (hasName()) { s = s.append(" ").append(getName()); } return s.append(getDbKeyAsStringInParentheses(" ")); } CAirlineIcaoCode CAirlineIcaoCode::thisOrCallsignCode(const CCallsign &callsign) const { if (this->hasValidDbKey()) { return *this; } if (callsign.isEmpty()) { return *this; } const QString callsignAirline = callsign.getAirlineSuffix(); if (callsignAirline.isEmpty()) { return *this; } if (callsignAirline == this->m_designator) { return *this; } const CAirlineIcaoCode callsignIcao(callsignAirline); if (this->m_designator.isEmpty()) { return callsignIcao; } // here we have 2 possible codes if (callsignIcao.isVirtualAirline()) { if (callsignIcao.getDesignator().endsWith(this->m_designator)) { // callsign ICAO is virtual airline of myself, this is more accurate return callsignIcao; } } return *this; } QString CAirlineIcaoCode::getNameWithKey() const { if (!this->hasValidDbKey()) { return this->getName(); } return this->hasName() ? QString(this->getName()).append(" ").append(this->getDbKeyAsStringInParentheses()) : this->getDbKeyAsStringInParentheses(); } void CAirlineIcaoCode::updateMissingParts(const CAirlineIcaoCode &otherIcaoCode) { if (!this->hasValidDbKey() && otherIcaoCode.hasValidDbKey()) { // we have no DB data, but the other one has // so we change roles. We take the DB object as base, and update our parts CAirlineIcaoCode copy(otherIcaoCode); copy.updateMissingParts(*this); *this = copy; return; } if (!this->hasValidDesignator()) { this->setDesignator(otherIcaoCode.getDesignator()); } if (!this->hasValidCountry()) { this->setCountry(otherIcaoCode.getCountry()); } if (!this->hasName()) { this->setName(otherIcaoCode.getName()); } if (!this->hasTelephonyDesignator()) { this->setTelephonyDesignator(otherIcaoCode.getTelephonyDesignator()); } if (!this->hasValidDbKey()) { this->setDbKey(otherIcaoCode.getDbKey()); this->setUtcTimestamp(otherIcaoCode.getUtcTimestamp()); } } QString CAirlineIcaoCode::asHtmlSummary() const { 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)) { // when using relationship, this can be null (e.g. for color liveries) return CAirlineIcaoCode(); } QString designator(json.value(prefix % QLatin1String("designator")).toString()); if (!CAirlineIcaoCode::isValidAirlineDesignator(designator)) { designator = CAirlineIcaoCode::normalizeDesignator(designator); } const QString iata(json.value(prefix % QLatin1String("iata")).toString()); const QString telephony(json.value(prefix % QLatin1String("callsign")).toString()); const QString name(json.value(prefix % QLatin1String("name")).toString()); const QString countryIso(json.value(prefix % QLatin1String("country")).toString()); const QString countryName(json.value(prefix % QLatin1String("countryname")).toString()); const QString groupName(json.value(prefix % QLatin1String("groupname")).toString()); const QString groupDesignator(json.value(prefix % QLatin1String("groupdesignator")).toString()); const int groupId(json.value(prefix % QLatin1String("groupid")).toInt(-1)); const bool va = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % QLatin1String("va")).toString()); const bool operating = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % QLatin1String("operating")).toString()); const bool military = CDatastoreUtility::dbBoolStringToBool(json.value(prefix % QLatin1String("military")).toString()); CAirlineIcaoCode code( designator, name, CCountry(countryIso, countryName), telephony, va, operating ); code.setIataCode(iata); code.setMilitary(military); code.setGroupDesignator(groupDesignator); code.setGroupId(groupId); code.setGroupName(groupName); code.setKeyAndTimestampFromDatabaseJson(json, prefix); return code; } //! \private QSet iconIdsImpl() { QDir dir(CDirectoryUtils::imagesAirlinesDirectory()); Q_ASSERT_X(dir.exists(), Q_FUNC_INFO, "image directory missing"); QSet ids; dir.setFilter(QDir::Files | QDir::NoSymLinks); dir.setSorting(QDir::Name); bool ok = false; for (const QFileInfo &fileInfo : dir.entryInfoList()) { const QString fn(fileInfo.fileName()); ok = fn.size() > 5; if (!ok) { continue; } BLACK_VERIFY_X(ok, Q_FUNC_INFO, "wrong file name"); const int id = fn.left(5).toInt(&ok); BLACK_VERIFY_X(ok, Q_FUNC_INFO, "wrong id format"); if (!ok) { continue; } ids.insert(id); } return ids; } const QSet &CAirlineIcaoCode::iconIds() { static const QSet ids = iconIdsImpl(); return ids; } } // namespace } // namespace