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