<?php
namespace App\Controller;
use App\Service\MeteoFranceService;
use App\Service\StationService;
use App\DTO\CaniculeMeteonormDTO;
use App\Form\CaniculeMeteonormType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use App\Entity\Pluviometrie;
use App\Entity\StationsMeteo;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use App\Form\UploadFichierType;
set_time_limit(36000);
class CaniculeMeteonormController extends AbstractController {
private StationService $stationService;
public function __construct(private MeteoFranceService $meteoService, StationService $stationService){//, HttpClientInterface $client, CacheInterface $cache, string $apiKey, string $meteoFranceTokenUrl, string $meteoFranceApiUrl) {
$this->stationService = $stationService;
}
/**
* @Route("/caniculeMeteonorm", name="caniculeMeteonorm")
*/
public function index(Request $request, EntityManagerInterface $em): Response
{
ini_set('memory_limit', '1024M'); // ou '2G' si besoin
// Créer une instance du DTO pour passer au formulaire
$CaniculeMeteonormDTO = new CaniculeMeteonormDTO();
// Créer le formulaire avec l'objet DTO
$form = $this->createForm(CaniculeMeteonormType::class, $CaniculeMeteonormDTO);
// Gérer la soumission du formulaire
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Récupérer les données du formulaire
$data = $form->getData();
$fichier = $form->get('fichier')->getData();
$lignes = file($fichier->getPathname(), FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$annee = 1983;
$donneesComfie = [];
$DH26 = [];
foreach ($lignes as $ligne) {
// Largeur fixe : extraits selon les positions données
$stationId = substr($ligne, 0, 3);
$temp = substr($ligne, 3, 4);
$rayonnementGlobal = substr($ligne, 7, 4);
$rayonnementDiffus = substr($ligne, 11, 4);
$rayonnementDirect = substr($ligne, 15, 4);
$ensoleillement = substr($ligne, 19, 4);
$humidite = substr($ligne, 23, 3);
$vent = substr($ligne, 26, 3);
$mois = (int) substr($ligne, 29, 2);
$jour = (int) substr($ligne, 31, 2);
$heure = (int) substr($ligne, 33, 2);
// Nettoyage des valeurs "EEE"
$val = fn($x) => trim($x) === 'EEE' || str_contains($x, 'E') ? null : intval($x);
$datetime = sprintf('%04d-%02d-%02d %02d:00', $annee, $mois, $jour, $heure - 1); // heure 1 = 00h
$donneesComfie[] = [
'datetime' => $datetime,
'station' => $stationId,
'temperature' => $val($temp) !== null ? $val($temp) / 10 : null,
'humidite' => $val($humidite),
'vent' => $val($vent),
'rayonnement' => $val($rayonnementGlobal),
];
}
$donneesJournalieres = [];
$DH26['fichier'] = 0 ;
foreach ($donneesComfie as $ligne) {
$jour = substr($ligne['datetime'], 0, 10); // YYYY-MM-DD
if (!isset($donneesJournalieres[$jour])) {
$donneesJournalieres[$jour] = [
'min' => $ligne['temperature'],
'max' => $ligne['temperature'],
];
} else {
$donneesJournalieres[$jour]['min'] = min($donneesJournalieres[$jour]['min'], $ligne['temperature']);
$donneesJournalieres[$jour]['max'] = max($donneesJournalieres[$jour]['max'], $ligne['temperature']);
if ($ligne['temperature'] >26){
$DH26['fichier'] = $DH26['fichier'] + $ligne['temperature'] - 26;
}
}
}
// Tu as directement accès à l'objet StationMeteoDTO ici
$apikey = $data->getApikey(); // Accès via la méthode getter
$stationAPI = $data->getStationAPI(); // Accès via la méthode getter
// Appeler le service pour obtenir les données météo
$stationsData = [];
$commandeArray = []; // Initialisation pour éviter l'erreur Undefined variable
$columnDataArray = []; // Initialisation pour stocker les données des colonnes
$AnneesEtudiees = [];
foreach (range(2002, 2024) as $annee) {
$AnneesEtudiees[$annee] = [
'dateDeb' => "{$annee}-01-01T00%3A00%3A00Z",
'dateFin' => "{$annee}-12-31T00%3A00%3A00Z",
];
}
foreach ($AnneesEtudiees as $annee => $AnneeEtudiee) {
$commande = $this->meteoService->getStationDataHoraire($stationAPI, $AnneeEtudiee, $apikey);
$commandeArray[$stationAPI][$annee] = $commande;
// Pause pour respecter la limite de 50 requêtes par minute
usleep(1300000); // 1,2 secondes = 1 200 000 microsecondes
}
$erreur500 = null;
$extractedData = [];
// Récupération des fichiers à partir des commandes
foreach ($commandeArray as $stationAPI => $anneesData) {
$erreur = null;
foreach ($anneesData as $AnneeEtudiee => $commandeAnnee) {
$commandeId = $commandeAnnee['elaboreProduitAvecDemandeResponse']['return'];
$csvString = $this->meteoService->getFichier($commandeId, $apikey);
// dump($csvString);
// Pause pour respecter la limite de 50 requêtes par minute
usleep(1300000); // 1,2 secondes = 1 200 000 microsecondes
if ($csvString == 'Erreur 500') {
$erreur500[$commandeId] = $stationAPI;
$erreur = 1;
} else {
// Diviser la chaîne en lignes
$lines = explode("\n", $csvString);
// Tableau pour stocker les données extraites
$data = [];
foreach ($lines as $index => $line) {
// Ignorer la première ligne (en-tête)
if ($index === 0) {
continue;
}
// Diviser chaque ligne en colonnes par le séparateur ;
$columns = explode(";", $line);
// Vérifier qu'il y a suffisamment de colonnes
if (count($columns) > 2) {
// Extraire ce qui se situe entre le 3ème et 4ème point-virgule
$data['date'] = str_replace(",", ".", $columns[1]);
$data['temperature'] = str_replace(",", ".", $columns[10]);
$data['vitesse'] = str_replace(",", ".", $columns[48]);
$data['direction'] = str_replace(",", ".", $columns[50]);
$extractedData[] = $data;
$extractedDataParAn[$AnneeEtudiee][] = $data;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////
// CALCUL DES CANICULES///////////////////////////////////////////////////////////
function convertirEnDonneesJournalieres($donneesHoraires,$annee) {
$donneesJournalieres = [];
$DH26[$annee] = 0;
foreach ($donneesHoraires as $donnee) {
$dateHeure = \DateTime::createFromFormat('YmdH', $donnee['date']);
if (!$dateHeure) {
throw new \Exception("Format de date invalide : " . $donnee['date']);
}
$jour = $dateHeure->format('Y-m-d');
$temp = floatval($donnee['temperature']);
if (!isset($donneesJournalieres[$jour])) {
$donneesJournalieres[$jour] = [
'date' => $jour,
'min' => $temp,
'max' => $temp,
];
} else {
$donneesJournalieres[$jour]['min'] = min($donneesJournalieres[$jour]['min'], $temp);
$donneesJournalieres[$jour]['max'] = max($donneesJournalieres[$jour]['max'], $temp);
if ($temp > 26) {
$DH26[$annee] = round($DH26[$annee] + $temp - 26);
}
}
}
// On retourne un tableau indexé numériquement pour garder l'ordre chronologique
return [array_values($donneesJournalieres), $DH26[$annee]];
}
function detecterCaniculesAvecIBM(array $donneesJournalieres, float $seuilMin, float $seuilMax): array {
$canicules = [];
$buffer = [];
for ($i = 2; $i < count($donneesJournalieres); $i++) {
$sous_tableau = array_slice($donneesJournalieres, $i - 2, 3);
$moy_min = array_sum(array_column($sous_tableau, 'min')) / 3;
$moy_max = array_sum(array_column($sous_tableau, 'max')) / 3;
if ($moy_min >= $seuilMin && $moy_max >= $seuilMax) {
// On ajoute tous les 3 jours du trio dans le buffer
foreach ($sous_tableau as $jour) {
$buffer[ $jour['date'] ] = $jour; // clé = date, pour éviter les doublons
}
} else {
if (count($buffer) >= 3) {
$canicules[] = array_values($buffer);
}
$buffer = [];
}
}
if (count($buffer) >= 3) {
$canicules[] = array_values($buffer);
}
return $canicules;
}
function analyserCanicule(array $periode): array {
if (empty($periode)) {
return [
'debut' => null,
'fin' => null,
'duree' => 0,
'intensite_max' => null,
'severite' => 0,
];
}
$dates = array_column($periode, 'date');
$maxs = array_column($periode, 'max');
// Nouvelle méthode de calcul de la sévérité
$severite = array_sum(array_map(function ($jour) {
$moyenne = ($jour['min'] + $jour['max']) / 2;
return $moyenne - 24;
}, $periode));
return [
'debut' => reset($dates),
'fin' => end($dates),
'duree' => count($periode),
'intensite_max' => max($maxs),
'severite' => $severite,
];
}
function detecterVaguesDeChaleur(array $donneesJournalieres): array {
$vagues = [];
$vagueEnCours = [];
$joursSous23 = 0;
foreach ($donneesJournalieres as $jour) {
$date = $jour['date'];
$tmin = $jour['min'];
$tmax = $jour['max'];
$moyenne = ($tmin + $tmax) / 2;
if ($moyenne > 23) {
$vagueEnCours[] = $jour;
$joursSous23 = 0;
} elseif ($moyenne < 20) {
// Fin immédiate de la vague
if (count($vagueEnCours) > 0) {
$vagues[] = $vagueEnCours;
}
$vagueEnCours = [];
$joursSous23 = 0;
} elseif ($moyenne < 23) {
if (!empty($vagueEnCours)) {
$vagueEnCours[] = $jour;
$joursSous23++;
if ($joursSous23 >= 3) {
// Fin après 3 jours consécutifs < 23°C
$vagues[] = $vagueEnCours;
$vagueEnCours = [];
$joursSous23 = 0;
}
}
} else {
$joursSous23 = 0; // Sécurité, même si ce cas ne devrait pas arriver
}
}
// Cas où la vague continue jusqu’à la fin
if (count($vagueEnCours) > 0) {
$vagues[] = $vagueEnCours;
}
return $vagues;
}
function resumerVaguesDeChaleur(array $vaguesParAn): array {
$synthese = [];
foreach ($vaguesParAn as $annee => $vagues) {
foreach ($vagues as $vague) {
if (empty($vague)) continue;
$duree = count($vague);
if ($duree < 5) continue; // On ignore les vagues < 5 jours
$sommeDJR24 = 0;
foreach ($vague as $jour) {
$moyenne = ($jour['min'] + $jour['max']) / 2;
$djr24 = max(0, $moyenne - 24);
$sommeDJR24 += $djr24;
}
$intensite = $sommeDJR24 / $duree; // DJR24 moyen par jour
$synthese[] = [
'annee' => $annee,
'debut' => $vague[0]['date'],
'fin' => end($vague)['date'],
'duree' => $duree,
'intensite' => round($intensite, 2),
'severite' => round($sommeDJR24, 2),
];
}
}
return $synthese;
}
$caniculesParAn = [];
$vagues = [];
//Préciser les seuils
$stationDB = $em->getRepository(StationsMeteo::class)->findOneBy(['IdStation' => $stationAPI]);
$departement = $stationDB->getDepartement();
$seuils = $this->getSeuilsParDepartement()[$departement];
foreach ($extractedDataParAn as $annee => $donneesHoraires) {
[$donneesJournalieres,$DH26[$annee]] = convertirEnDonneesJournalieres($donneesHoraires,$annee);
//dump($donneesJournalieres);
$caniculesParAn[$annee] = detecterCaniculesAvecIBM($donneesJournalieres, $seuils['seuil_nuit'], $seuils['seuil_jour']);// Adapter les seuils ici
$vagues[$annee] = detecterVaguesDeChaleur($donneesJournalieres);
}
$analysesParAn = [];
$severiteTotaleParAn = [];
foreach ($caniculesParAn as $annee => $canicules) {
foreach ($canicules as $periode) {
$analyse = analyserCanicule($periode);
$analysesParAn[$annee][] = $analyse;
// Ajout de la sévérité à la somme annuelle
if (!isset($severiteTotaleParAn[$annee])) {
$severiteTotaleParAn[$annee] = 0;
}
$severiteTotaleParAn[$annee] += $analyse['severite'];
}
}
dump($vagues);
$synthese = resumerVaguesDeChaleur($vagues);
dump($synthese);
$donneesGraphique = [];
foreach ($synthese as $vague) {
$mois = ucfirst(strftime('%B', strtotime($vague['debut'])));
$donneesGraphique[] = [
'x' => $vague['duree'],
'y' => $vague['intensite'],
'z' => $vague['severite'], // utile si bubble
'name' => $mois . ' ' . $vague['annee'],
];
}
// Trier les années par sévérité décroissante
arsort($severiteTotaleParAn);
// Extraire les 5 années les plus critiques
$top10Annees = array_slice(array_keys($severiteTotaleParAn), 0, 10, true);
// Affichage
$top10AnneesData = [];
foreach ($top10Annees as $annee) {
if (!isset($analysesParAn[$annee])) {
continue;
}
foreach ($analysesParAn[$annee] as $analyse) {
if (empty($analyse['debut'])) {
continue;
}
$dateDebut = \DateTime::createFromFormat('Y-m-d', $analyse['debut']);
$mois = $dateDebut->format('F'); // en anglais (ex: July)
$mois = strtr($mois, [// option : traduction rapide
'January' => 'janvier', 'February' => 'février', 'March' => 'mars',
'April' => 'avril', 'May' => 'mai', 'June' => 'juin',
'July' => 'juillet', 'August' => 'août', 'September' => 'septembre',
'October' => 'octobre', 'November' => 'novembre', 'December' => 'décembre',
]);
$top10AnneesData[] = [
'annee' => $annee,
'mois' => $mois,
'duree' => $analyse['duree'],
'intensite_max' => round($analyse['intensite_max'], 1),
'severite' => round($analyse['severite']),
];
}
}
$stationKey = $this->getKeyById($stationAPI);
// Renommer la clé "fichier" pour affichage
$categories = array_map(function ($key) {
return is_int($key) ? $key : 'Meteonorm';
}, array_keys($DH26));
$valeurs = array_values($DH26);
dump($donneesGraphique);
// Passer les données au template
return $this->render('caniculeMeteonorm.html.twig', [
'form' => $form->createView(),
'top10Canicules' => $top10AnneesData,
'stationKey' => $stationKey,
'seuils' => $seuils,
'DH26' => $DH26,
'categories' => $categories,
'valeurs' => $valeurs,
'donneesVagues' => $donneesGraphique,
]);
// } catch (\Exception $e) {
// // Gérer les erreurs
// $this->addFlash('error', 'Erreur : ' . $e->getMessage());
// }
}
// Rendre la vue du formulaire
return $this->render('caniculeFormulaire.html.twig', [
'form' => $form->createView(),
]);
}
// Méthode pour obtenir la clé en fonction de l'ID
public function getKeyById($id)
{
// Récupérer le tableau des stations et IDs à partir de StationMeteoType
$choices = $this->stationService->getStationAPIChoices();
// caniculeMeteonormType->getStationAPIChoices();
// Rechercher la clé correspondant à l'ID
$key = array_search($id, $choices);
// Si l'ID est trouvé, retourne la clé, sinon retourne null
return $key !== false ? $key : null;
}
public function getSeuilsParDepartement(): array {
return [
'01' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'02' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'03' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'04' => ['seuil_jour' => 36, 'seuil_nuit' => 19],
'05' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'06' => ['seuil_jour' => 31, 'seuil_nuit' => 24],
'07' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'08' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'09' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'10' => ['seuil_jour' => 35, 'seuil_nuit' => 18],
'11' => ['seuil_jour' => 35, 'seuil_nuit' => 22],
'12' => ['seuil_jour' => 36, 'seuil_nuit' => 19],
'13' => ['seuil_jour' => 35, 'seuil_nuit' => 24],
'14' => ['seuil_jour' => 31, 'seuil_nuit' => 18],
'15' => ['seuil_jour' => 32, 'seuil_nuit' => 18],
'16' => ['seuil_jour' => 36, 'seuil_nuit' => 20],
'17' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'18' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'19' => ['seuil_jour' => 36, 'seuil_nuit' => 19],
'20' => ['seuil_jour' => 33, 'seuil_nuit' => 23],
// '2B' => ['seuil_jour' => 33, 'seuil_nuit' => 23],
'21' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'22' => ['seuil_jour' => 31, 'seuil_nuit' => 18],
'23' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'24' => ['seuil_jour' => 36, 'seuil_nuit' => 20],
'25' => ['seuil_jour' => 33, 'seuil_nuit' => 19],
'26' => ['seuil_jour' => 36, 'seuil_nuit' => 21],
'27' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'28' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'29' => ['seuil_jour' => 32, 'seuil_nuit' => 19],
'30' => ['seuil_jour' => 36, 'seuil_nuit' => 23],
'31' => ['seuil_jour' => 36, 'seuil_nuit' => 21],
'32' => ['seuil_jour' => 36, 'seuil_nuit' => 20],
'33' => ['seuil_jour' => 35, 'seuil_nuit' => 21],
'34' => ['seuil_jour' => 35, 'seuil_nuit' => 22],
'35' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'36' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'37' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'38' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'39' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'40' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'41' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'42' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'43' => ['seuil_jour' => 32, 'seuil_nuit' => 18],
'44' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'45' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'46' => ['seuil_jour' => 36, 'seuil_nuit' => 20],
'47' => ['seuil_jour' => 36, 'seuil_nuit' => 20],
'48' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'49' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'50' => ['seuil_jour' => 31, 'seuil_nuit' => 18],
'51' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'52' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'53' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'54' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'55' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'56' => ['seuil_jour' => 32, 'seuil_nuit' => 18],
'57' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'58' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'59' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'60' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'61' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'62' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'63' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'64' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'65' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'66' => ['seuil_jour' => 35, 'seuil_nuit' => 23],
'67' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'68' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'69' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'70' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'71' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'72' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'73' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'74' => ['seuil_jour' => 34, 'seuil_nuit' => 19],
'75' => ['seuil_jour' => 31, 'seuil_nuit' => 21],
'76' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'77' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'78' => ['seuil_jour' => 33, 'seuil_nuit' => 20],
'79' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'80' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'81' => ['seuil_jour' => 36, 'seuil_nuit' => 21],
'82' => ['seuil_jour' => 36, 'seuil_nuit' => 21],
'83' => ['seuil_jour' => 35, 'seuil_nuit' => 23],
'84' => ['seuil_jour' => 36, 'seuil_nuit' => 21],
'85' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'86' => ['seuil_jour' => 34, 'seuil_nuit' => 20],
'87' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'88' => ['seuil_jour' => 34, 'seuil_nuit' => 18],
'89' => ['seuil_jour' => 35, 'seuil_nuit' => 19],
'90' => ['seuil_jour' => 33, 'seuil_nuit' => 18],
'91' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
'92' => ['seuil_jour' => 31, 'seuil_nuit' => 21],
'93' => ['seuil_jour' => 31, 'seuil_nuit' => 21],
'94' => ['seuil_jour' => 31, 'seuil_nuit' => 21],
'95' => ['seuil_jour' => 35, 'seuil_nuit' => 20],
];
}
}