diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index 77f98ab1..8516b1bb 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -13,6 +13,8 @@ use App\Service\Motocultrice; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\Request; use function uuid_create; +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpFoundation\JsonResponse; final class AdminController extends AbstractController { @@ -781,4 +783,158 @@ final class AdminController extends AbstractController $this->addFlash('success', 'Email envoyé avec succès à ' . $place->getName() . ' le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s')); return $this->redirectToRoute('app_public_index'); } + + #[Route('/admin/fraicheur/histogramme', name: 'admin_fraicheur_histogramme')] + public function showFraicheurHistogramme(): Response + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + if (!file_exists($jsonPath)) { + // Générer le fichier si absent + $this->calculateFraicheur(); + } + return $this->render('admin/fraicheur_histogramme.html.twig'); + } + + #[Route('/admin/fraicheur/calculate', name: 'admin_fraicheur_calculate')] + public function calculateFraicheur(): Response + { + $filesystem = new Filesystem(); + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + $now = new \DateTime(); + // Si le fichier existe et a moins de 12h, on ne régénère pas + if ($filesystem->exists($jsonPath)) { + $fileMTime = filemtime($jsonPath); + if ($fileMTime && ($now->getTimestamp() - $fileMTime) < 43200) { // 12h = 43200s + return $this->redirectToRoute('admin_fraicheur_histogramme'); + } + } + $places = $this->entityManager->getRepository(Place::class)->findAll(); + $histogram = []; + $total = 0; + foreach ($places as $place) { + $date = $place->getOsmDataDate(); + if ($date) { + $key = $date->format('Y-m'); + if (!isset($histogram[$key])) { + $histogram[$key] = 0; + } + $histogram[$key]++; + $total++; + } + } + ksort($histogram); + $data = [ + 'generated_at' => $now->format('c'), + 'total' => $total, + 'histogram' => $histogram + ]; + $filesystem->dumpFile($jsonPath, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + + // --- Distribution villes selon lieux/habitants --- + $distJsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; + // Toujours régénérer + $statsRepo = $this->entityManager->getRepository(Stats::class); + $allStats = $statsRepo->findAll(); + $histogram_lieux_par_habitant = []; + $histogram_habitants_par_lieu = []; + $totalVilles = 0; + foreach ($allStats as $stat) { + $places = $stat->getPlacesCount(); + $population = $stat->getPopulation(); + if ($places && $population && $population > 0) { + // lieux par habitant (pas de 0.01) + $ratio_lph = round($places / $population, 4); + $bin_lph = round(floor($ratio_lph / 0.01) * 0.01, 2); + if (!isset($histogram_lieux_par_habitant[$bin_lph])) $histogram_lieux_par_habitant[$bin_lph] = 0; + $histogram_lieux_par_habitant[$bin_lph]++; + // habitants par lieu (pas de 10) + $ratio_hpl = ceil($population / $places); + $bin_hpl = ceil($ratio_hpl / 10) * 10; + if (!isset($histogram_habitants_par_lieu[$bin_hpl])) $histogram_habitants_par_lieu[$bin_hpl] = 0; + $histogram_habitants_par_lieu[$bin_hpl]++; + $totalVilles++; + } + } + ksort($histogram_lieux_par_habitant); + ksort($histogram_habitants_par_lieu); + $distData = [ + 'generated_at' => $now->format('c'), + 'total_villes' => $totalVilles, + 'histogram_001' => $histogram_lieux_par_habitant, + 'histogram_10' => $histogram_habitants_par_lieu + ]; + $filesystem->dumpFile($distJsonPath, json_encode($distData, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + + return $this->redirectToRoute('admin_fraicheur_histogramme'); + } + + #[Route('/admin/fraicheur/download', name: 'admin_fraicheur_download')] + public function downloadFraicheur(): JsonResponse + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + if (!file_exists($jsonPath)) { + return new JsonResponse(['error' => 'Fichier non généré'], 404); + } + $content = file_get_contents($jsonPath); + $data = json_decode($content, true); + return new JsonResponse($data); + } + + #[Route('/admin/distribution_villes_lieux_par_habitant_download', name: 'admin_distribution_villes_lieux_par_habitant_download')] + public function downloadDistributionVillesLieuxParHabitant(): JsonResponse + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; + if (!file_exists($jsonPath)) { + // Générer à la volée si absent + $now = new \DateTime(); + $filesystem = new \Symfony\Component\Filesystem\Filesystem(); + $statsRepo = $this->entityManager->getRepository(\App\Entity\Stats::class); + $allStats = $statsRepo->findAll(); + $distribution = []; + $histogram = []; + $totalVilles = 0; + foreach ($allStats as $stat) { + $places = $stat->getPlacesCount(); + $population = $stat->getPopulation(); + if ($places && $population && $population > 0) { + $ratio = round($places / $population, 4); // lieux par habitant + $bin = round(floor($ratio / 0.01) * 0.01, 2); // pas de 0.01 + if (!isset($histogram[$bin])) $histogram[$bin] = 0; + $histogram[$bin]++; + $totalVilles++; + } + } + ksort($histogram); + $distData = [ + 'generated_at' => $now->format('c'), + 'total_villes' => $totalVilles, + 'histogram_001' => $histogram + ]; + $filesystem->dumpFile($jsonPath, json_encode($distData, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + } + $content = file_get_contents($jsonPath); + $data = json_decode($content, true); + return new JsonResponse($data); + } + + #[Route('/admin/distribution_villes_lieux_par_habitant_villes', name: 'admin_distribution_villes_lieux_par_habitant_villes')] + public function downloadDistributionVillesLieuxParHabitantVilles(): JsonResponse + { + $statsRepo = $this->entityManager->getRepository(\App\Entity\Stats::class); + $allStats = $statsRepo->findAll(); + $villesByBin = []; + foreach ($allStats as $stat) { + $places = $stat->getPlacesCount(); + $population = $stat->getPopulation(); + $name = $stat->getName(); + if ($places && $population && $population > 0 && $name) { + $ratio = round($places / $population, 4); // lieux par habitant + $bin = round(floor($ratio / 0.01) * 0.01, 2); // pas de 0.01 + if (!isset($villesByBin[$bin])) $villesByBin[$bin] = []; + $villesByBin[$bin][] = $name; + } + } + ksort($villesByBin); + return new JsonResponse(['villes_by_bin' => $villesByBin]); + } } diff --git a/src/Controller/FraicheurController.php b/src/Controller/FraicheurController.php new file mode 100644 index 00000000..57ebeffa --- /dev/null +++ b/src/Controller/FraicheurController.php @@ -0,0 +1,143 @@ +getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + if (!file_exists($jsonPath)) { + $this->calculateFraicheur(); + } + return $this->render('admin/fraicheur_histogramme.html.twig'); + } + + #[Route('/admin/fraicheur/calculate', name: 'admin_fraicheur_calculate')] + public function calculateFraicheur(): Response + { + $filesystem = new Filesystem(); + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + $now = new \DateTime(); + $places = $this->entityManager->getRepository(Place::class)->findAll(); + $histogram = []; + $total = 0; + foreach ($places as $place) { + $date = $place->getOsmDataDate(); + if ($date) { + $key = $date->format('Y-m'); + if (!isset($histogram[$key])) { + $histogram[$key] = 0; + } + $histogram[$key]++; + $total++; + } + } + ksort($histogram); + $data = [ + 'generated_at' => $now->format('c'), + 'total' => $total, + 'histogram' => $histogram + ]; + $filesystem->dumpFile($jsonPath, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + + // --- Distribution villes selon lieux/habitants --- + $distJsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; + $statsRepo = $this->entityManager->getRepository(Stats::class); + $allStats = $statsRepo->findAll(); + $histogram_lieux_par_habitant = []; + $histogram_habitants_par_lieu = []; + $villesByBinLph = []; + $villesByBinHpl = []; + $totalVilles = 0; + foreach ($allStats as $stat) { + $places = $stat->getPlacesCount(); + $population = $stat->getPopulation(); + $name = $stat->getName(); + if ($places && $population && $population > 0 && $name) { + // lieux par habitant (pas de 0.01) + $ratio_lph = $places / $population; + $bin_lph = number_format(floor($ratio_lph / 0.01) * 0.01, 2, '.', ''); + if (!isset($histogram_lieux_par_habitant[$bin_lph])) $histogram_lieux_par_habitant[$bin_lph] = 0; + $histogram_lieux_par_habitant[$bin_lph]++; + if (!isset($villesByBinLph[$bin_lph])) $villesByBinLph[$bin_lph] = []; + $villesByBinLph[$bin_lph][] = $name; + // habitants par lieu (pas de 10) + $ratio_hpl = $population / $places; + $bin_hpl = (string)(ceil($ratio_hpl / 10) * 10); + if (!isset($histogram_habitants_par_lieu[$bin_hpl])) $histogram_habitants_par_lieu[$bin_hpl] = 0; + $histogram_habitants_par_lieu[$bin_hpl]++; + if (!isset($villesByBinHpl[$bin_hpl])) $villesByBinHpl[$bin_hpl] = []; + $villesByBinHpl[$bin_hpl][] = $name; + $totalVilles++; + } + } + ksort($histogram_lieux_par_habitant); + ksort($histogram_habitants_par_lieu); + ksort($villesByBinLph); + ksort($villesByBinHpl); + $distData = [ + 'generated_at' => $now->format('c'), + 'total_villes' => $totalVilles, + 'histogram_001' => $histogram_lieux_par_habitant, + 'histogram_10' => $histogram_habitants_par_lieu, + 'villes_by_bin_001' => $villesByBinLph, + 'villes_by_bin_10' => $villesByBinHpl + ]; + $filesystem->dumpFile($distJsonPath, json_encode($distData, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + + return $this->redirectToRoute('admin_fraicheur_histogramme'); + } + + #[Route('/admin/fraicheur/download', name: 'admin_fraicheur_download')] + public function downloadFraicheur(): JsonResponse + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; + if (!file_exists($jsonPath)) { + return new JsonResponse(['error' => 'Fichier non généré'], 404); + } + $content = file_get_contents($jsonPath); + $data = json_decode($content, true); + return new JsonResponse($data); + } + + #[Route('/admin/distribution_villes_lieux_par_habitant_download', name: 'admin_distribution_villes_lieux_par_habitant_download')] + public function downloadDistributionVillesLieuxParHabitant(): JsonResponse + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; + if (!file_exists($jsonPath)) { + $this->calculateFraicheur(); + } + $content = file_get_contents($jsonPath); + $data = json_decode($content, true); + return new JsonResponse($data); + } + + #[Route('/admin/distribution_villes_lieux_par_habitant_villes', name: 'admin_distribution_villes_lieux_par_habitant_villes')] + public function downloadDistributionVillesLieuxParHabitantVilles(): JsonResponse + { + $jsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; + if (!file_exists($jsonPath)) { + $this->calculateFraicheur(); + } + $content = file_get_contents($jsonPath); + $data = json_decode($content, true); + // On renvoie les deux listes de villes par bin + return new JsonResponse([ + 'villes_by_bin_001' => $data['villes_by_bin_001'] ?? [], + 'villes_by_bin_10' => $data['villes_by_bin_10'] ?? [] + ]); + } +} \ No newline at end of file diff --git a/templates/admin/fraicheur_histogramme.html.twig b/templates/admin/fraicheur_histogramme.html.twig new file mode 100644 index 00000000..1cb0b90c --- /dev/null +++ b/templates/admin/fraicheur_histogramme.html.twig @@ -0,0 +1,260 @@ +{% extends 'base.html.twig' %} + +{% block title %}Histogramme de fraîcheur OSM{% endblock %} + +{% block body %} +