diff options
Diffstat (limited to 'src/model/SportModel.cpp')
| -rw-r--r-- | src/model/SportModel.cpp | 455 | 
1 files changed, 455 insertions, 0 deletions
| diff --git a/src/model/SportModel.cpp b/src/model/SportModel.cpp new file mode 100644 index 0000000..be9d326 --- /dev/null +++ b/src/model/SportModel.cpp @@ -0,0 +1,455 @@ +#include "SportModel.h" +#include "Competitor.h" + +// categories +#include <QNetworkReply> +#include <qlogging.h> +#include <qobject.h> +#include <set> + +// sorting and filtering +#include <map> +#include <algorithm> +#include <regex> + +// float to string formatting +#include <iostream> +#include <iomanip> + +#include <QJsonObject> +#include <QJsonArray> +#include <QJsonValueRef> +#include <QString> + +namespace { +  const QString &k_requestUrl = "https://sph-s-api.olympics.com/summer/schedules/api/ENG/schedule/discipline/"; +} + +SportModel::SportModel(QObject *parent) : QAbstractListModel(parent) { +} + +int SportModel::rowCount(const QModelIndex &parent) const { +  Q_UNUSED(parent); +  return m_sportList.size(); +} + +QVariant SportModel::data(const QModelIndex &index, int role) const { +  if (index.isValid() && index.row() >= 0 && index.row() < m_sportList.size()) { +    EventInfo *event = m_sportList[index.row()]; + +    switch ((Role) role) { +      case EventName: +        return event->eventName(); + +      case Competitors: +        return event->competitors(); +    } +  } + +  return {}; +} + + +QHash<int, QByteArray> SportModel::roleNames() const { +  QHash<int, QByteArray> names; +  names[EventName] = "eventName"; +  names[Competitors] = "competitors"; + +  return names; +} + +QString SportModel::discipline() const { +  return m_discipline; +} + +void SportModel::setDiscipline(const QString &discipline) { +  m_discipline = discipline; +  disciplineChanged(); +} + + +void SportModel::request(QString discipline) { +  setDiscipline(discipline); +  m_reply = m_networkManager.get(QNetworkRequest( k_requestUrl + m_discipline)); +  qDebug() << m_reply; +  connect(m_reply, &QNetworkReply::finished, this, &SportModel::parseData); +} + +void SportModel::parseData() { + +    if (m_reply->error() == QNetworkReply::NoError) { +      beginResetModel(); +      qDeleteAll(m_sportList); +      m_sportList.clear(); + + + +      QByteArray strReply = m_reply->readAll(); + +      //parse json +      // qDebug() << "Response:" << strReply; +      QJsonDocument jsonDocument = QJsonDocument::fromJson(strReply); + +      QJsonArray sports = jsonDocument["units"].toArray(); +      for (const auto &sport : sports) { +        QJsonObject entry = sport.toObject(); + +        EventInfo *event = new EventInfo(this); +        event->setEventName(entry["eventUnitName"].toString()); + +        QList<QString> competitors; +        for (const auto &competitor : entry["competitors"].toArray()) { +          competitors << competitor.toObject()["name"].toString(); +        } +        event->setCompetitors(competitors); + +        qDebug() << entry["eventUnitName"].toString(); +        m_sportList << event; +      } +      endResetModel(); +    } +} + +// QJsonArray filter function, provide with input array and evaluation function +QJsonArray filter(QJsonArray input, function<bool (QJsonObject)> eval) { +    QJsonArray output; + +    for (const QJsonValueRef &elemRef :input) { +        QJsonObject elem = elemRef.toObject(); +        if(eval(elem)) { +            output.append(elem); +        } +    } + +    return output; +} + +/** + * @brief Sport::lastName Reduce the full name to the part that is marked in capital letters (probably last name). + * @param competitors The competitors of one category. + */ +void SportModel::lastName(QList<Competitor*> &competitors) { +    // validate competitors +    if (competitors.isEmpty()) return; + +    for (int i = 0; i < competitors.size(); i++) { +        Competitor* comp = competitors.value(i); +        string fullName = comp->getName().toUtf8().constData(); + +        // regex to identify names, written in CAPS +        regex r("[A-Z']{2,}"); +        smatch m; + +        regex_search(fullName, m, r); + +        // combine found names again +        string lastName = ""; +        for (string s : m) lastName = lastName + s + " "; + +        // remove last space +        QString name = QString(lastName.substr(0, lastName.size() - 1).c_str()); + +        // replace competitor name in list +        comp->setName(name); +    } +} + +/** + * @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute. + * @return True, if discipline contains units. + */ +bool SportModel::validateDiscipline() { +    return this->o_discipline.contains("units"); +} + +/** + * @brief Sport::getCategories Reads all possible categories (also called units). + * @return A set of all category names. + */ +set<QString> SportModel::getCategories() { +    set<QString> categoryNames; + +    if (!validateDiscipline()) return categoryNames; + +    // search in each unit for the category (named "eventUnitName") +    for (const QJsonValueRef &unitRef : this->o_discipline["units"].toArray()) { +        QJsonObject unit = unitRef.toObject(); + +        // validate unit +        if (!unit.contains("eventUnitName")) continue; + +        categoryNames.insert(unit["eventUnitName"].toString()); +    } + +    return categoryNames; +} + +/** + * @brief Sport::getCompetitorsByCategory Searches for all competitors, who took part in the given category. + * @param category The category to search in. + * @return An QJsonArray with all competitors as QJsonValueRef, which can be casted to QJsonObject. + */ +QList<CompetitorWithResults*> SportModel::getCompetitorsByCategory(QString category) { +    QList<CompetitorWithResults*> competitors; + +    if (!validateDiscipline()) return competitors; + +    for (const QJsonValueRef &unitRef : this->o_discipline["units"].toArray()) { +        QJsonObject unit = unitRef.toObject(); + +        // validate unit +        if (!unit.contains("eventUnitName") || !unit.contains("competitors")) continue; + +        // search all units with the same category +        if (unit["eventUnitName"].toString().compare(category, Qt::CaseSensitive) != 0) continue; + +        // add all competitors from one unit +        for (const QJsonValueRef &compRef : unit["competitors"].toArray()) { +            CompetitorWithResults *comp = new CompetitorWithResults(); // TODO declare comp +            comp->setCompetitorWithResults(compRef.toObject()); +            competitors.push_back(comp); +        } +    } + +    return competitors; +} + +/** + * @brief Sport::getCompetitorsWithMedal Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!! + * @return All competitors, who won at least one medal. Structure of one competitor: {code, name, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} + */ +QList<MedalWinner*> SportModel::getCompetitorsWithMedal() { +    map<QString, QJsonObject> competitors; + +    if (!validateDiscipline()) return QList<MedalWinner*>(); + +    // filter all units, which have medal events +    QJsonArray units = filter(this->o_discipline["units"].toArray(), [](QJsonObject unit){ +        // search all units with Final, Gold or Bronze in their name, because these are the categories with the medal winners +        QString unitName = unit["eventUnitName"].toString(); +        return unitName.contains("Bronze", Qt::CaseSensitive) +               || unitName.contains("Gold", Qt::CaseSensitive) +               || unitName.contains("Final", Qt::CaseSensitive); +    }); + +    for (const QJsonValueRef &unitRef : units) { +        QJsonObject unit = unitRef.toObject(); + +        // validate unit +        if (!unit.contains("competitors")) continue; + +        // filter all competitors, who won medals +        QJsonArray medalComps = filter(unit["competitors"].toArray(), [](QJsonObject comp) { +            if (!comp.contains("results")) return false; + +            QString medalType = comp["results"].toObject()["m_medalType"].toString(); +            return !medalType.isEmpty(); +        }); + +        for (const QJsonValueRef &medalCompRef : medalComps) { +            QJsonObject medalComp = medalCompRef.toObject(); + +            // validate competitor (with medal) +            if (!medalComp.contains("name") +                || !medalComp.contains("results") +                || !medalComp["results"].toObject().contains("m_medalType")) continue; + +            QString name = medalComp["name"].toString(); +            QString medalType = medalComp["results"].toObject()["m_medalType"].toString(); + +            // check if competitor has other medal(s) +            if (competitors.find(name) == competitors.end()) { +                competitors.insert({name, createCompetitorWithMedals(medalComp)}); +            } + +            // update the medal count +            QJsonObject updatedMedalCount = QJsonObject(competitors.find(name)->second["medals"].toObject()); + +            int amount = updatedMedalCount[medalType].toInt() + 1; +            updatedMedalCount.remove(medalType); +            updatedMedalCount.insert(medalType, amount); + +            // create new medals QJsonObject and set it in the map +            competitors.find(name)->second["medals"] = updatedMedalCount; +        } +    } + +    // convert map to QJsonArray +    QList<MedalWinner*> output; +    for (const pair<QString, QJsonObject> &competitor : competitors) { +        MedalWinner *comp = new MedalWinner(); // TODO declare comp +        comp->setMedalWinner(competitor.second); +        output.append(comp); +    } + +    return output; +} + +/** + * @brief Sport::createCompetitorWithMedals Creates a competitor QJsonObject with the following attributes: code, name, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE} + * @param comp The original competitor object. + * @return A competitor object with medal counts. + */ +QJsonObject SportModel::createCompetitorWithMedals(QJsonObject comp) { +    // repair competitor if something is missing +    if (!comp.contains("code")) comp.insert("code", "0"); +    if (!comp.contains("name")) comp.insert("code", ""); +    if (!comp.contains("m_noc")) comp.insert("code", ""); + +    // create new competitor QJsonObject and add it to the competitor map +    QJsonObject medals { +        {"ME_GOLD", 0}, +        {"ME_SILVER", 0}, +        {"ME_BRONZE", 0} +    }; + +    QJsonObject medalComp { +        {"code", comp["code"].toString()}, +        {"name", comp["name"].toString()}, +        {"m_noc", comp["m_noc"].toString()}, +        {"medals", medals} +    }; + +    return medalComp; +} + +/** + * @brief Sport::filterByName Filter the competitors by name (case insensitive). + * @param competitors The competitors of one category. + * @param name The (part of the) name to search for. + */ +void SportModel::filterByName(QList<Competitor*> &competitors, QString name) { +    filterCompetitors(competitors, name); +} + +/** + * @brief Sport::filterByCountry Filter the competitors by their national olympics comittee (case insensitive, short form). + * @param competitors The competitors of one category. + * @param nocShort The (part of the) national olympics comittee short name to search for. + */ +void SportModel::filterByCountry(QList<Competitor*> &competitors, QString nocShort) { +    filterCompetitors(competitors, nocShort); +} + +/** + * @brief Sport::filterCompetitors Filters the given QJsonArray by comparing the value of a certain attribute with the given filter string. + * @param competitors The competitors of one category. + * @param attribute The attribute to filter by. + * @param filter The string, which should be contained. + */ +void SportModel::filterCompetitors(QList<Competitor*> &competitors, QString filter) { +    for (int i = 0; i < competitors.size(); i++) { +        if (!competitors.value(i)->getNOC().contains(filter)) { +            competitors.remove(i); +            i--; +        } +    } +} + +/** + * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByName(QList<Competitor*> &competitors) { +    if (competitors.isEmpty()) return; +    std::sort(competitors.begin(), competitors.end(), Competitor::compareName); +} + +/** + * @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByCountry(QList<Competitor*> &competitors) { +    if (competitors.isEmpty()) return; +    std::sort(competitors.begin(), competitors.end(), Competitor::compareNOC); +} + +/** + * @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByResult(QList<CompetitorWithResults*> &competitors) { +    if (competitors.isEmpty()) return; +    std::sort(competitors.begin(), competitors.end(), CompetitorWithResults::compare); +} + +/** + * @brief Sport::sortByMedals Sort the competitors by their medal amounts in one specific category (numerical, ascending). + * @param competitors The competitors of one category. + */ +void SportModel::sortByMedals(QList<MedalWinner*> &competitors) { +    if (competitors.isEmpty()) return; +    std::sort(competitors.begin(), competitors.end(), MedalWinner::compare); +} + +/** + * @brief Sport::reverseOrder Reverses the order of the competitors. + * @param competitors The competitors of one category. + */ +void SportModel::reverseOrder(QList<Competitor*> &competitors) { +    int iterations = competitors.size() / 2; // automatically rounds down + +    for (int i = 0; i < iterations; i++) { +        Competitor *left = competitors.value(i); +        Competitor *right = competitors.value(competitors.size() - 1 - i); + +        competitors.replace(i, right); +        competitors.replace(competitors.size() - 1 - i, left); +    } +} + +/** + * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. + * Stores the m_statistic in obj->results->stat for each competitor. + * @param competitors The competitors of one category. + */ +void SportModel::addRelativeToFirst(QList<CompetitorWithResults*> &competitors) { +    if (competitors.isEmpty()) return; + +    QString reference = competitors.value(0)->getMark(); + +    for (int i = 0; i < competitors.size(); i++) { +        CompetitorWithResults *comp = competitors.value(i); + +        QString result = comp->getMark(); + +        // format relative float value to string with 2 digits after decimal point and sign +        stringstream sstream; +        sstream << fixed << setprecision(2) << calcRelativeStat(reference, result); +        QString stat(sstream.str().c_str()); +        stat.append("%"); +        if (stat.at(0).isNumber()) stat = QString("+").append(stat); + +        comp->setStatistic(stat); +    } + +} + +/** + * @brief Sport::calcRelativeStat Calculates the relative deviation of val from ref in percent. + * @param ref The reference value. + * @param val The value to calculate the deviation from. + * @return The deviation from ref to val in percent. + */ +float SportModel::calcRelativeStat(QString ref, QString val) { +    // check if the value is not a time +    if (!ref.contains(":") && !val.contains(":")) { +        float fRef = ref.toFloat(); + +        if (fRef == 0) return 0.0; +        return ((val.toFloat() * 100)/ fRef) - 100; +    } + +    regex r("\\d+"); +    smatch mref, mval; +    string sref = ref.toUtf8().constData(); +    string sval = val.toUtf8().constData(); + +    regex_search(sref, mref, r); +    regex_search(sval, mval, r); + +    float timeRef = stof(mref.str(1)) * 60 * 100 + stof(mref.str(2)) * 100 + stof(mref.str(3)); +    float timeVal = stof(mval.str(1)) * 60 * 100 + stof(mval.str(2)) * 100 + stof(mval.str(3)); + +    return ((timeVal * 100) / timeRef) - 100; +} | 
