From c452597f535fe1abf2bf31daa5fa34aa6d357b43 Mon Sep 17 00:00:00 2001
From: Oussmou Rayan <rayan.oussmou@etu.ec-lyon.fr>
Date: Wed, 15 Jan 2025 10:57:51 +0000
Subject: [PATCH] Update SERVEUR.py

---
 SERVEUR.py | 492 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 491 insertions(+), 1 deletion(-)

diff --git a/SERVEUR.py b/SERVEUR.py
index 050b12e..78d9b81 100644
--- a/SERVEUR.py
+++ b/SERVEUR.py
@@ -1 +1,491 @@
-okay errtn
\ No newline at end of file
+import http.server
+import socketserver
+from urllib.parse import urlparse, parse_qs
+import sqlite3
+import matplotlib.pyplot as plt
+import datetime as dt
+import matplotlib.dates as pltd
+import numpy as np
+
+from urllib.parse import unquote
+import os.path
+
+import json
+
+
+###############################################################################
+#
+# Classe dérivée pour traiter les requêtes entrantes du serveur
+#
+class RequestHandler(http.server.SimpleHTTPRequestHandler):
+
+    # nom du serveur
+    server_version = "serveur.py"
+
+    # sous-répertoire racine des documents statiques
+    static_dir = 'client'
+
+    #
+    # Surcharge du constructeur pour imposer 'client' comme sous-répertoire racine
+    #
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, directory=self.static_dir, **kwargs)
+
+    #
+    # Traitement des requêtes GET
+    #
+    def do_GET(self):
+        self.init_params()
+
+        # on envoie l'heure du serveur
+        if self.path_info[0] == "time":
+            self.send_time()
+
+        # on envoie la liste des stations
+        elif self.path_info[0] == "lieu":
+            self.send_lieux()
+            
+        # données pour placer les POIs
+        elif self.path_info[0] == "location":
+            self.send_location()
+
+        # on génère une courbe de pluviométrie et on envoie <img>
+        elif self.path_info[0] == "pluie" and len(self.path_info) > 1:
+            self.send_pluie()
+            
+        # envoie le nom du lieu sélectionné
+        elif self.path_info[0] == "ajout" and len(self.path_info) > 1:
+            self.ajout_courbe2()
+            
+        # calcul la pluviométrie moyenne sur la zone selectionnée
+        elif self.path_info[0] == "moyenne" and len(self.path_info) > 1:
+            self.calcul_moyenne2()
+            
+        # on traite les autres requêtes via la classe parent
+        else:
+            super().do_GET()
+
+    #
+    # Traitement de la requête pour la liste des POIs
+    #
+    def send_location(self):
+
+        # requête SQL
+        c = conn.cursor()
+        query = "SELECT * FROM 'stations-pluvio' ORDER BY nom"
+        c.execute(query)
+        r = c.fetchall()
+
+        # réponse au format JSON
+        donnees = []
+        for a in r:
+            donnees.append({
+                "id": a[7],
+                "lat": a[10],
+                "lon": a[9],
+                "name": a[0]
+            })
+        data = json.dumps(donnees)
+
+        # envoi de la réponse
+        headers = [('Content-Type', 'application/json')]
+        self.send(data, headers)
+
+    #
+    # Traitement de la requête pour une courbe de pluviométrie
+    #
+    
+    def send_pluie(self):
+
+        # on récupère le nom de la région
+        nom = unquote(self.path_info[1])
+        titre = f'Historique des hauteurs de pluies (en mm) pour la Station de {nom}'
+
+        # cache serveur : on génère le fichier uniquement s'il n'existe pas déjà
+        fichier = 'courbes/pluie_{}.png'.format(nom)
+        if not os.path.isfile(fichier):
+            self.create_figure(nom, titre, fichier)
+
+        # données de la réponse au format JSON
+        info = {
+            "title": titre,
+            "img": "/" + fichier
+        }
+        data = json.dumps(info)
+
+        # envoi de la réponse
+        headers = [('Content-Type', 'application/json')]
+        self.send(data, headers)
+
+    def ajout_courbe2(self):
+        # Ajout_courbe2 est créée pour renvoyer dans la liste html l'identifiant et le nom de 
+        # la station sélectionnée
+        # Ici on récupère l'identifiant.
+        ids = unquote(self.path_info[1])
+
+        conn = sqlite3.connect('pluie.sqlite')
+        c = conn.cursor()
+        c.execute(
+            f'SELECT nom FROM "stations-pluvio" WHERE "stations-pluvio"."identifiant" = {ids}')
+        nom = c.fetchall()[0][0]
+
+        info = {"nom": nom}
+        # On récupère au format json le nom de la station
+        data = json.dumps(info)
+        # envoi de la réponse
+        headers = [('Content-Type', 'application/json')]
+        # On l'envoie à notre liste Json
+        self.send(data, headers)
+        
+    
+    #
+    # Traitement de la requête pour une courbe de pluviométrie moyenne pour
+    # une zone selectionée
+    #
+
+    def calcul_moyenne2(self):
+
+        # changement du format de date
+        def change_date(date):
+            date_format = ''
+            date_format += date[8:10] + '/'
+            date_format += date[5:7] + '/'
+            date_format += date[0:4]
+            return date_format
+
+        #
+        # creation d'une date afin d'inclure les donnees des jours de début 
+        # et de fin 
+        # ex : conv_date(date debut)= annee mois jour 00:00:00 (sans espace ni ":")
+        #
+        def conv_date(date):
+            return date[6:10] + date[3:5] + date[0:2] + date[11:13] + date[14:16] + date[17:19]
+        
+        conn = sqlite3.connect('pluie.sqlite')
+        c = conn.cursor()
+        # Demande est une liste de 0 ou de 1, avec en indice l'identitifiant de chaque station
+        # un 0 si la station n'a pas été slectionnée, et un 1 si la station a été selectionnée
+        demande = self.path_info[1]
+        # On récupère également les dates de debut et de fin
+        dd = self.path_info[2]
+        df = self.path_info[3]
+        dated = change_date(dd)
+        datef = change_date(df)
+        dd_conv = conv_date(dated + " 00:00:00")
+        df_conv = conv_date(datef + " 23:59:59")
+        Liste_LR = []
+        Liste_nom = []
+        Liste_nom2 = []
+        Liste_nom3 = []
+        # On récupère dans la base de données tous les noms des stations pluvio
+        for i, lieu in enumerate(demande):
+            if lieu != "0" and i > 0:
+                
+                c.execute(
+                    f'SELECT nom FROM "stations-pluvio" WHERE "stations-pluvio"."identifiant" = {i}')
+                # Transformation des informations du curseur en une liste
+                a = c.fetchall()
+                # Le premier élément de cette liste contient le nom
+                nom = a[0][0]
+                Liste_nom.append(nom) 
+     
+        # Récupération des noms pour le titre
+        if len(Liste_nom) > 1:
+            nom1 = ''
+            for names in Liste_nom:
+                nom1 += names + ' et '
+                
+            nom2 = nom1[:-3]
+            # Ce titre sera identifique qu'il soit présent dans le cache ou non, on le 
+            #fait donc hors conditions. 
+            titre = 'Historique moyen des pluies aux stations de {}'.format(nom2)
+            
+        else: 
+             nom2 = Liste_nom[0]
+             # Adaptation du titre s'il n'y a qu'une seule station sélectionnée
+             titre = 'Historique des pluies à la station de {}'.format(nom2)
+        #fichier = 'courbes/pluie_{}.png'.format(identifiant)
+        conn2 = sqlite3.connect('cache.db')
+        ccache = conn2.cursor()
+        ccache.execute(
+            'SELECT * FROM "cache"')
+        cache=ccache.fetchall()
+        # Ici on créé ce compteur pour vérifier si les régions sélectionnées et la date sont déjà dans la base de données
+        cpt=0
+        #identifiant_correle prendra la valeur de l'identifiant en colonne 1 dans la table cache
+        # si une requête est présente dans le cache, sinon on crééra un nouvel identifiant 
+        #  correspondant à une nouvelle requête. 
+        identifiant_correle  = -1
+        for i in range(len(cache)):
+            # Conditions pour que la requête soit déjà existante
+            if cache[i][1]==nom2 and cache[i][2]==dd and cache[i][3]==df:
+                
+                cpt = 1
+                # On modifie la valeur de l'identifiant_correle
+                identifiant_correle = cache[i][0]
+        # On créé si le compteur est nul un nouvel identifant, on fait donc deux cas pour savoir
+        # quel valeur donner à notre identifiant.
+        if len(cache) == 0 :
+            identifiant = 1
+        else : 
+            identifiant = len(cache) + 1
+            
+        # Si le compteur est à 1, on vérifie que la courbe se trouve dans notre repertoire "courbes"
+        if cpt == 1 : 
+            #On prend bien ici identifiant_correle
+            fichier = 'courbes/pluie_{}.png'.format(identifiant_correle)
+            
+            # Si ce n'est pas le cas on créé de nouveau la courbe
+            if not os.path.isfile("client/"+fichier): 
+                
+                for i, lieu in enumerate(demande):
+                    if lieu != "0" and i > 0:
+                        
+                        c.execute(
+                            f'SELECT nom FROM "stations-pluvio" WHERE "stations-pluvio"."identifiant" = {i}')
+                        a = c.fetchall()
+                        
+                        nom = a[0][0]
+                        Liste_nom2.append(nom)
+                        # On récupère au premier abord toutes les données pluviométrique des stations choisies
+                        c.execute(
+                            f'SELECT  "pluvio-histo"."sta-{i}", "pluvio-histo"."date" FROM "pluvio-histo" ')
+                        Lr = []
+                        # de même on transforme en liste
+                        r = c.fetchall()
+                        # Puis cette boucle permet de ne sélectionner que les dates qui nous intéressent
+                        # C'est le seul moyen que nous avons trouvé pour comparer les dates
+                        for x in r:
+                            x = list(x)
+                            x[1] = conv_date(x[1])
+                            if x[1] >= dd_conv and x[1] <= df_conv:
+                                if x[0] == None:
+                                    x[0] = 0.
+                                else:
+                                    x[0] = float(x[0])
+                                Lr.append(x)
+                                #Lr contient  les données pluviométriques aux dates intéressantes
+                                #Pour une station
+                        Liste_LR.append(Lr)
+                        #Liste_LR contient ces données pour toute les stations.
+                
+        
+                nbr_dates = len(Liste_LR[0])
+                
+                nbr_lieux = len(Liste_LR)
+                pluie_moy = np.zeros(nbr_dates)
+                dates = []
+                # On récupère pour chaque station la pluviomètrie à toutes les dates, et on fait ensuite la moyenne
+                for i_date in range(nbr_dates):
+                    dates.append(Liste_LR[0][i_date][1])
+                    for indice_lieu in range(nbr_lieux):
+                        pluie_moy[i_date] += Liste_LR[indice_lieu][i_date][0]
+                    pluie_moy[i_date] = pluie_moy[i_date] / nbr_lieux
+                #on créé la courbe en appelant create_figure_moyenne
+                self.create_figure_moyenne(nom2, titre, fichier, dated, datef, dates, pluie_moy)
+         # Si le compteur est à 0, on effectue le même processus que dansle cas où
+         # le compteur est à 1, mais on va rajouter dans la base de données les informations. 
+        if cpt==0:
+            if len(cache) == 0 :
+                identifiant = 1
+            else : 
+                identifiant = len(cache) + 1
+                
+            for i, lieu in enumerate(demande):
+                if lieu != "0" and i > 0:
+                    
+                    c.execute(
+                        f'SELECT nom FROM "stations-pluvio" WHERE "stations-pluvio"."identifiant" = {i}')
+                    a = c.fetchall()
+                    
+                    nom = a[0][0]
+                    Liste_nom3.append(nom)
+                    c.execute(
+                        f'SELECT  "pluvio-histo"."sta-{i}", "pluvio-histo"."date" FROM "pluvio-histo" ')
+                    Lr = []
+                    r = c.fetchall()
+                    for x in r:
+                        x = list(x)
+                        x[1] = conv_date(x[1])
+                        if x[1] >= dd_conv and x[1] <= df_conv:
+                            if x[0] == None:
+                                x[0] = 0.
+                            else:
+                                x[0] = float(x[0])
+                            Lr.append(x)
+                    Liste_LR.append(Lr)
+                
+        
+            nbr_dates = len(Liste_LR[0])
+            
+            nbr_lieux = len(Liste_LR)
+            pluie_moy = np.zeros(nbr_dates)
+            dates = []
+            for i_date in range(nbr_dates):
+                dates.append(Liste_LR[0][i_date][1])
+                for indice_lieu in range(nbr_lieux):
+                    pluie_moy[i_date] += Liste_LR[indice_lieu][i_date][0]
+                pluie_moy[i_date] = pluie_moy[i_date] / nbr_lieux
+
+            fichier = 'courbes/pluie_{}.png'.format(identifiant)
+            
+            # Les lignes qui suivents correspondent à l'ajout dans la base de données des informations
+            cur = conn2.cursor()
+            cur.execute("INSERT INTO cache (identifiant, régions, date_début, date_fin) VALUES (?, ?, ?, ?)", (identifiant, nom2, dd, df))
+            conn2.commit()
+            conn2.close()
+            # On créé la courbe en appelant create_figure_moyenne
+            self.create_figure_moyenne(
+                nom2, titre, fichier, dated, datef, dates, pluie_moy)
+
+        # données de la réponse au format JSON
+        info = {
+            "title": titre,
+            "img": "/" + fichier
+        }
+
+        data = json.dumps(info)
+
+        # envoi de la réponse
+        headers = [('Content-Type', 'application/json')]
+        self.send(data, headers)
+        
+        
+    def create_figure_moyenne(self, lieux, titre, fichier, dated, datef, dates, moy_pluie):
+        # llieux correspond à toutes les stations sélectionnées.
+        def change_date(date):
+            date_format = ''
+            date_format += date[8:10] + '/'
+            date_format += date[5:7] + '/'
+            date_format += date[0:4]
+            return date_format
+
+        def conv_date(date):
+            return date[6:10] + date[3:5] + date[0:2] + date[11:13] + date[14:16] + date[17:19]
+
+        dated = change_date(dated)
+        datef = change_date(datef)
+        
+        
+        # erreur sur le nom de la région -> "404 Not Found" via la classe mère
+        if len(moy_pluie) == 0:
+            super().do_GET()
+            return
+
+        # abscisses et ordonnées
+        x = [dt.datetime.strptime(str(date), "%Y%m%d%H%M%S").date()
+             for date in dates]
+        x = [pltd.datestr2num(date) for date in dates]
+        # On convertit toutes les données de moy_pluie, la liste moyenne en nombres réels.
+        y = [float(l) for l in moy_pluie]
+        # On choisit le max pour ajuster la taille du graphique.
+        y_max = max(y)
+
+        # décoration des axes
+        fig1 = plt.figure(figsize=(18, 4))
+        ax = fig1.add_subplot(111)
+        ax.set_ylim(bottom=0, top=y_max * 1.2)
+        ax.grid(which='major', color='#888888', linestyle='-')
+        ax.grid(which='minor', axis='x', color='#888888', linestyle=':')
+        ax.xaxis.set_tick_params(labelsize=10)
+
+        # génération de la courbe
+        
+        plt.plot(x, y)
+        ax.xaxis.axis_date()
+        # légendes et titre
+        plt.legend(loc='lower right')
+        plt.title(titre, fontsize=16)
+        plt.ylabel('Quantité de pluie tombée (en mm)')
+        plt.xlabel('Date')
+
+        # enregistrement du fichier image
+        plt.savefig('client/{}'.format(fichier))
+        plt.show()
+        
+
+    def change_date(date):
+        date_format = ''
+        date_format += date[8:10] + '/'
+        date_format += date[5:7] + '/'
+        date_format += date[0:4]
+        return date_format
+
+  
+   
+    
+
+
+
+    def send_lieux(self):
+
+        # requête SQL
+        c = conn.cursor()
+        c.execute("SELECT DISTINCT nom FROM 'stations-pluvio' ORDER BY nom")
+        r = c.fetchall()
+
+        # texte de la réponse
+        body = 'Liste des {} lieux\n'.format(len(r))
+        for a in r:
+            body += a[0] + "\n"
+
+        # envoi de la réponse
+        self.send(body, [('Content-Type', 'text/plain; charset=utf-8')])
+
+    #
+    # Traitement de la requête pour l'heure du serveur
+    #
+    def send_time(self):
+        response = '''<!DOCTYPE html>
+       <title>time</title>
+       <meta charset="utf-8">
+       <p>Voici l'heure du serveur :</p>
+       <code>{}</code>'''.format(self.date_time_string())
+        self.send(response)
+
+    #
+    # Envoi d'une réponse
+    #
+    def send(self, body, headers=[]):
+        self.send_response(200)
+
+        # encodage du corps en utf-8
+        encoded = bytes(body, 'UTF-8')
+
+        # envoi des entêtes http
+        [self.send_header(*t) for t in headers]
+        self.send_header('Content-Length', int(len(encoded)))
+        self.end_headers()
+
+        # envoi du corps
+        self.wfile.write(encoded)
+
+    #
+    # Récupération des arguments de la requête
+    #
+    def init_params(self):
+
+        # décomposition du chemin
+        info = urlparse(self.path)
+        self.path_info = info.path.split('/')[1:]
+
+        # analyse de la chaîne de requête
+        self.query_string = info.query
+        self.params = parse_qs(info.query)
+
+
+###############################################################################
+#
+# Programme principal
+#
+
+# ouverture d'une connexion avec la base de données
+conn = sqlite3.connect('pluvio.db')
+conn2 = sqlite3.connect('cache.db') # Création d'une deuxième connexion à la base de données
+
+# numéro du port TCP utilisé par le serveur
+server_port = 8080
+
+# instanciation et lancement du serveur web
+httpd = socketserver.TCPServer(("", server_port), RequestHandler)
+httpd.serve_forever()
-- 
GitLab