summaryrefslogtreecommitdiff
path: root/src/model/SportModel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/model/SportModel.cpp')
-rw-r--r--src/model/SportModel.cpp454
1 files changed, 454 insertions, 0 deletions
diff --git a/src/model/SportModel.cpp b/src/model/SportModel.cpp
new file mode 100644
index 0000000..2e847b3
--- /dev/null
+++ b/src/model/SportModel.cpp
@@ -0,0 +1,454 @@
+#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;
+}
+
+
+void SportModel::request(QString discipline) {
+ m_discipline = 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;
+}