Files
shipheart-qt/tb_mesures.cpp
T

313 lines
16 KiB
C++

#include "tb_mesures.h"
#include "shipheart.h"
//#include <QDateTime>
#include <QElapsedTimer>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QXYSeries>
//============================================================================
// Prototypes :
//============================================================================
qreal conversion_unite(qreal val, quint8 type_unite, quint32 unite);
//============================================================================
// Création du modèle
//============================================================================
TbMesuresModel::TbMesuresModel() :
QObject() {
}
//================================================================================================================================
// Fonction : Fonction principale de mise à jour des data à partir des données de la BDD. Les données proprement dites sont
// stockées dans le vecteur 'points'. Et les valeurs de min et max en x et y sont également calculées.
// Reçoit : Le pointeur sur la série (donc la courbe),
// le début de la requête à exécuter, en UTC (jusqu'aux filtre sur les Dates),
// la fin de la requete à exécuter, en UTC (après le filtre sur les Dates). En général, comprend les GROUP BY et les SORT,
// la date de début de la courbe,
// la date de fin de la courbe,
// le coefficient valeur/unité du capteur en cours (pour pouvoir calculer les valeurs réelles de Y auxquelles on ne touchera ensuite plus jusqu'à l'affichage de la courbe),
// le fuseau horaire afin de retourner les points avec des DateTime dans le bon fuseau (NB : l'heure d'été peut être ajoutée dedans lors de l'appel).
// Retour : Une liste de chaine contenant le nombre de lignes retournées, les points m_xMin(x, y), m_xMax(x, y), m_yMin(x, y),
// m_yMax(x, y) et la valeur m_yAvg.
//================================================================================================================================
QStringList TbMesuresModel::maj_data(QAbstractSeries *serie, QString table, quint32 id, quint32 id_variable, QDateTime date_debut, QDateTime date_fin, quint32 regroupement_donnees, qreal coeff_valeur_unite, qreal fuseau, quint8 type_unite, quint32 unite)
{
QStringList retour;
QVector<QPointF> temp_points; // Vecteur temporaire
QPointF m_xMin;
QPointF m_xMax;
QPointF m_yMin;
QPointF m_yMax;
qreal xVal, yVal;
qreal xVal_prec, yVal_prec;
qreal xCumul, yCumul;
qreal moyenne;
quint32 diviseur;
QString requete;
QSqlQuery query;
QSqlRecord resultat;
qint32 nb_lignes;
quint32 i;
bool flag_premier_point_ok;
QElapsedTimer myTimer;
myTimer.start();
//-----------------------------------------------
// Init des valeurs par défaut :
nb_lignes = 0;
temp_points.reserve(1500); // A priori, on réserve la mémoire pour 1500 lignes (soit un peu plus que 24h x 60 min.). Mais la taille sera agrandie en dynamique si besoin...
m_xMin.ry() = std::numeric_limits<qreal>::max();
m_xMax.ry() = std::numeric_limits<qreal>::min();
m_yMin.ry() = std::numeric_limits<qreal>::max();
m_yMax.ry() = std::numeric_limits<qreal>::min();
if (coeff_valeur_unite == 0) diviseur = 1;
else diviseur = coeff_valeur_unite;
xCumul = 0;
yCumul = 0;
query.setForwardOnly(true);
//-----------------------------------------------
// Recherche du point de départ :
requete = " SELECT Date, Valeur FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
requete += " AND Date<'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' ";
// requete += " AND Valeur IS NOT NULL ";
requete += " ORDER BY Date DESC LIMIT 1 ";
//qDebug() << "Requête de recherche du premier point : " << requete;
yVal = 0;
flag_premier_point_ok = false;
if (query.exec(requete) == true) // Exécution sans erreur de la requête :
{
if (query.next()) // Cas normal où on a trouvé un point précédent l'intervalle :
{
yVal = conversion_unite(query.record().value(1).toReal() / diviseur, type_unite, unite);
flag_premier_point_ok = true;
}
else qDebug() << "La requête de recherche du premier point n'a trouvé aucune valeur précédent le début de la courbe. La valeur Y initiale est fixée à " + QString::number(yVal);
}
else qDebug() << "/!\\ Erreur d'exécution de la requête de recherche du premier point. La valeur Y initiale est fixée à " + QString::number(yVal);
xVal = date_debut.toMSecsSinceEpoch() + (fuseau * 60 * 60 * 1000); // On ajuste immédiatement les valeurs de date au fuseau en cours
m_xMin = QPointF(xVal, yVal); // Mémorisation du point de début de l'axe X
if (flag_premier_point_ok)
{
temp_points << QPointF(xVal, yVal); // Ajout à la courbe, comme premier point...
if (yVal < m_yMin.ry()) m_yMin = QPointF(xVal, yVal); // Mise à jour du mini
if (yVal > m_yMax.ry()) m_yMax = QPointF(xVal, yVal); // Mise à jour du maxi
nb_lignes ++;
}
//-------------------------------------------------------------------
// Génération de la requête principale de récupération des data de la courbe :
if (regroupement_donnees <= 1)
{
requete = " SELECT Date, Valeur FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
requete += " AND Date>'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' AND Date<='" + date_fin.toString(FORMAT_DATE_HEURE_BDD) + "' ";
requete += " AND Valeur IS NOT NULL ";
requete += " ORDER BY Date ";
}
else
{
requete = " SELECT Date, AVG(Valeur) FROM " + table + " WHERE ID='" + QString::number(id) + "' AND ID_Variable='" + QString::number(id_variable) + "' ";
requete += " AND Date>'" + date_debut.toString(FORMAT_DATE_HEURE_BDD) + "' AND Date<='" + date_fin.toString(FORMAT_DATE_HEURE_BDD) + "' ";
requete += " AND Valeur IS NOT NULL ";
requete += " GROUP BY (UNIX_TIMESTAMP(Date) DIV " + QString::number(regroupement_donnees) + ") ORDER BY Date ";
}
//qDebug() << "Requête générée : " << requete;
//-----------------------------------------------
// Récupération des données dans la BDD :
if (query.exec(requete) == true) // TRUE = pas d'erreur
{
nb_lignes += query.size();
//qDebug() << "Nombre d'enregistrements récupérés par la requête : " << nb_lignes << " et temps écoulé : " << myTimer.elapsed() << "ms";
//-----------------------------------------------
// Traitement des tous les enregistrements récupérés :
for (i = 0; i < (quint32)nb_lignes; i ++)
{
if (!query.next()) break; // Ce break ne devrait pas arriver puisqu'on a compté la taille du query avant de lancer la boucle...
resultat = query.record();
// Mise à jour des valeurs précédentes avant de calculer les nouvelles :
xVal_prec = xVal;
yVal_prec = yVal;
// Mise à jour des nouvelles valeurs :
xVal = resultat.value(0).toDateTime().toMSecsSinceEpoch() + (fuseau * 60 * 60 * 1000); // On ajuste immédiatement les valeurs de date au fuseau en cours
yVal = resultat.value(1).toReal() / diviseur;
// Ajustement des valeurs en fonction de la conversion d'unité concernée :
yVal = conversion_unite(yVal, type_unite, unite);
//qDebug() << "Nouveau point " << i << " : Date = " << resultat.value(0).toDateTime() << " / " << xVal << " et y = " << yVal;
// Calcul des cumuls :
if ((flag_premier_point_ok) || (i > 0)) // Si le premier point n'a pas été trouvé, on ne prends pas en compte l'intervalle entre le départ et le premier point dans la fenêtre
{
yCumul += (xVal - xVal_prec) * (yVal_prec);
xCumul += (xVal - xVal_prec);
}
// Calcul des points yMin et yMax :
if (yVal < m_yMin.ry()) m_yMin = QPointF(xVal, yVal);
if (yVal > m_yMax.ry()) m_yMax = QPointF(xVal, yVal);
// Ajout des points dans la série temporaire :
temp_points << QPointF(xVal, yVal_prec); // Ajout d'un point avec la valeur courante de X mais avec le Y du point précédent (permet de "maintenir" la valeur précédente jusqu'au point en cours).
temp_points << QPointF(xVal, yVal); // Point en cours
}
//qDebug() << "Fin de boucle et temps écoulé : " << myTimer.elapsed() << "ms";
//-----------------------------------------------
// Calcul du point de fin :
if (nb_lignes > 0) // Si aucun point n'a été trouvé, il ne faut pas ajouter le dernier point (qui n'aura aucune signification)
{
xVal_prec = xVal;
yVal_prec = yVal;
xVal = date_fin.toMSecsSinceEpoch() + (fuseau * 60 * 60 * 1000); // On ajuste immédiatement les valeurs de date au fuseau en cours
yCumul += (xVal - xVal_prec) * (yVal_prec); // Dernier calcul de cumul
xCumul += (xVal - xVal_prec); // Dernier calcul de cumul
m_xMax = QPointF(xVal, yVal); // Mémorisation du point de fin de l'axe X. NB : la valeur de yVal est volontairement laissée inchangée (valeur Y du dernier point). Au passage, on évite les ajustements d'unités...
temp_points << QPointF(xVal, yVal); // Ajout à la courbe, comme dernier point...
nb_lignes ++;
}
//-----------------------------------------------
// Mise à jour des valeurs de la courbe :
if (serie)
{
QXYSeries *xySeries = static_cast<QXYSeries *>(serie);
xySeries->replace(temp_points); // Mise à jour des valeurs de la courbe avec le vecteur temporaire (Use replace instead of clear + append, it's optimized for performance)
}
else qDebug() << "Valeur de 'series' non valide !";
//qDebug() << "yMin : " << m_yMin.ry() << " et yMax : " << m_yMax.ry();
//-----------------------------------------------
// Préparation des valeurs de retour :
// NB : le 'f' est nécessaire pour les dates ('-----rx()') au moins ; sinon la précision n'est pas suffisante !!
if (xCumul != 0) moyenne = yCumul / xCumul;
else moyenne = 0;
retour << QString::number(nb_lignes);
retour = retour << QString::number(m_xMin.rx(), 'f') << QString::number(m_xMin.ry(), 'f') << QString::number(m_xMax.rx(), 'f') << QString::number(m_xMax.ry(), 'f');
retour = retour << QString::number(m_yMin.rx(), 'f') << QString::number(m_yMin.ry(), 'f') << QString::number(m_yMax.rx(), 'f') << QString::number(m_yMax.ry(), 'f');
retour = retour << QString::number(moyenne, 'f');
//qDebug() << "FIN de maj_data : " << nb_lignes << " enregistrements en " << myTimer.elapsed() << "ms\n";
}
else // Si une erreur lors de la requête a été détectée :
{
qDebug() << "/!\\ Erreur à l'exécution de la requête principale de 'maj_data()' ";
retour << QString::number(0);
}
//qDebug() << "FIN de maj_data";
return(retour);
}
//================================================================================================================================
// Fonction : Calcule une valeur dans l'unité demandée.
// Reçoit : La valeur à convertir (dans l'une des unités de base : °C ou litres. Les autres sont laissées inchangées
// Retour : la valeur converte
//================================================================================================================================
qreal conversion_unite(qreal val, quint8 type_unite, quint32 unite)
{
qreal retour = val;
switch(type_unite)
{
//-----------------------
case UNITE_VOLUME :
{
switch(unite)
{
case UNITE_VOLUME_LITRES: { break; } // Aucune conversion, on reste en litres
case UNITE_VOLUME_US_GALLONS: { retour = val / 3.785411784; break; } // Conversion litres => US Ga
case UNITE_VOLUME_IMP_GALLONS: { retour = val / 4.54609; break; } // Conversion litres => Imp. Ga
default : { break; } // Aucune conversion, on reste en ... ce qu'on est.
}
break;
}
//-----------------------
case UNITE_TEMPERATURE :
{
switch(unite)
{
case UNITE_TEMPERATURE_CELSIUS: { break; } // Aucune conversion, on reste en °C
case UNITE_TEMPERATURE_FAHRENHEIT: { retour = (1.8 * val) + 32; break; } // Conversion °C => °F
default : { break; } // Aucune conversion, on reste en ... ce qu'on est.
}
break;
}
//-----------------------
default: break;
}
return (retour);
}
//================================================================================================================================
// Fonction : Fonction de copie des points d'une série (par exemple, une LineSerie d'un graphique) dans ceux d'une autre série.
// Reçoit : Le pointeur sur la série source et un sur la série destinataire.
// Retour : Rien
//================================================================================================================================
void TbMesuresModel::copie_series(QAbstractSeries *serie_source, QAbstractSeries *serie_destination)
{
QVector<QPointF> tempo;
qint64 i;
qint64 nb;
qint64 taille_destination;
//QElapsedTimer myTimer;
//myTimer.start();
if ((serie_source) && (serie_destination))
{
QXYSeries *xySerie_source = static_cast<QXYSeries *>(serie_source);
QXYSeries *xySerie_destination = static_cast<QXYSeries *>(serie_destination);
// Copie des points de la source vers destination :
nb = xySerie_source->count(); // Nombre de points à transférer
for (i = 0; i < nb; i ++) // Boucle sur tous les points de la source
{
tempo << xySerie_source->at(i); // Copie de chaque point dans une structure temporaire
}
xySerie_destination->replace(tempo); // Copie de la structure temporaire dans la série destination
// Suppression des éventuels points de la destination si la source est plus petite :
taille_destination = xySerie_destination->count();
if (taille_destination > nb) xySerie_destination->removePoints(nb, (taille_destination - nb));
//qDebug() << "Copie Série faite. Nb d'enregistrements copiés = " << nb << "Temps d'exécution = " << myTimer.elapsed() << "ms\n";
}
else qDebug() << "Copie série impossible car au moins un pointeur (source et/ou destination) est NULL\n";
}