documentation wiki osm, ajout dashboard issues osmose
This commit is contained in:
parent
b28f8eac63
commit
7665f1d99c
12 changed files with 1758 additions and 76 deletions
230
src/Command/ExtractInseeZonesCommand.php
Normal file
230
src/Command/ExtractInseeZonesCommand.php
Normal file
|
@ -0,0 +1,230 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Stats;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:extract-insee-zones',
|
||||
description: 'Extrait les données OSM pour chaque zone INSEE à partir du fichier france-latest.osm.pbf',
|
||||
)]
|
||||
class ExtractInseeZonesCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('insee-code', InputArgument::OPTIONAL, 'Code INSEE spécifique à traiter')
|
||||
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limite le nombre de villes à traiter', null)
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force l\'extraction même si le fichier JSON existe déjà')
|
||||
->addOption('keep-pbf', 'k', InputOption::VALUE_NONE, 'Conserve les fichiers PBF intermédiaires')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$inseeCode = $input->getArgument('insee-code');
|
||||
$limit = $input->getOption('limit');
|
||||
$force = $input->getOption('force');
|
||||
$keepPbf = $input->getOption('keep-pbf');
|
||||
|
||||
// Créer le dossier oss_data s'il n'existe pas
|
||||
$ossDataDir = __DIR__ . '/../../oss_data';
|
||||
if (!is_dir($ossDataDir)) {
|
||||
$io->note('Création du dossier oss_data');
|
||||
mkdir($ossDataDir, 0755, true);
|
||||
}
|
||||
|
||||
// Vérifier que le fichier france-latest.osm.pbf existe
|
||||
$francePbfFile = $ossDataDir . '/france-latest.osm.pbf';
|
||||
if (!file_exists($francePbfFile)) {
|
||||
$io->note('Le fichier france-latest.osm.pbf n\'existe pas. Téléchargement en cours depuis Geofabrik...');
|
||||
|
||||
// URL de téléchargement
|
||||
$downloadUrl = 'https://download.geofabrik.de/europe/france-latest.osm.pbf';
|
||||
|
||||
// Télécharger le fichier
|
||||
try {
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'header' => "User-Agent: OSM-Commerces/1.0\r\n"
|
||||
]
|
||||
]);
|
||||
|
||||
// Utiliser file_get_contents pour télécharger le fichier
|
||||
$io->section('Téléchargement du fichier france-latest.osm.pbf');
|
||||
$io->progressStart(100);
|
||||
|
||||
// Téléchargement par morceaux pour pouvoir afficher une progression
|
||||
$fileHandle = fopen($francePbfFile, 'w');
|
||||
$curlHandle = curl_init($downloadUrl);
|
||||
|
||||
curl_setopt($curlHandle, CURLOPT_FILE, $fileHandle);
|
||||
curl_setopt($curlHandle, CURLOPT_HEADER, 0);
|
||||
curl_setopt($curlHandle, CURLOPT_USERAGENT, 'OSM-Commerces/1.0');
|
||||
curl_setopt($curlHandle, CURLOPT_NOPROGRESS, false);
|
||||
// Use a simpler progress reporting approach
|
||||
curl_setopt($curlHandle, CURLOPT_PROGRESSFUNCTION, function($resource, $downloadSize, $downloaded) use ($io) {
|
||||
static $lastProgress = 0;
|
||||
if ($downloadSize > 0) {
|
||||
$progress = round(($downloaded / $downloadSize) * 100);
|
||||
if ($progress > $lastProgress) {
|
||||
$io->progressAdvance($progress - $lastProgress);
|
||||
$lastProgress = $progress;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$success = curl_exec($curlHandle);
|
||||
curl_close($curlHandle);
|
||||
fclose($fileHandle);
|
||||
|
||||
$io->progressFinish();
|
||||
|
||||
if (!$success) {
|
||||
throw new \Exception('Échec du téléchargement');
|
||||
}
|
||||
|
||||
$io->success('Le fichier france-latest.osm.pbf a été téléchargé avec succès.');
|
||||
} catch (\Exception $e) {
|
||||
$io->error('Erreur lors du téléchargement du fichier france-latest.osm.pbf: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier que le dossier polygons existe
|
||||
$polygonsDir = __DIR__ . '/../../counting_osm_objects/polygons';
|
||||
if (!is_dir($polygonsDir)) {
|
||||
$io->error('Le dossier des polygones n\'existe pas. Veuillez d\'abord exécuter la commande app:retrieve-city-polygons.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Créer le dossier pour les extractions JSON si nécessaire
|
||||
$extractsDir = __DIR__ . '/../../insee_extracts';
|
||||
if (!is_dir($extractsDir)) {
|
||||
$io->note('Création du dossier insee_extracts');
|
||||
mkdir($extractsDir, 0755, true);
|
||||
}
|
||||
|
||||
// Récupérer les Stats à traiter
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
|
||||
if ($inseeCode) {
|
||||
$io->note(sprintf('Traitement du code INSEE spécifique: %s', $inseeCode));
|
||||
$allStats = $statsRepo->findBy(['zone' => $inseeCode]);
|
||||
|
||||
if (empty($allStats)) {
|
||||
$io->error(sprintf('Aucune ville trouvée avec le code INSEE %s', $inseeCode));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
} else {
|
||||
$io->note('Traitement de toutes les villes');
|
||||
$criteria = [];
|
||||
$orderBy = ['id' => 'ASC'];
|
||||
$limitValue = $limit ? (int)$limit : null;
|
||||
|
||||
$allStats = $statsRepo->findBy($criteria, $orderBy, $limitValue);
|
||||
}
|
||||
|
||||
$totalCount = count($allStats);
|
||||
$existingCount = 0;
|
||||
$createdCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
$io->progressStart($totalCount);
|
||||
|
||||
// Pour chaque Stats, extraire les données si nécessaire
|
||||
foreach ($allStats as $stat) {
|
||||
$inseeCode = $stat->getZone();
|
||||
if (!$inseeCode) {
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$polygonFile = $polygonsDir . '/commune_' . $inseeCode . '.poly';
|
||||
$extractPbfFile = $extractsDir . '/commune_' . $inseeCode . '.osm.pbf';
|
||||
$extractJsonFile = $extractsDir . '/commune_' . $inseeCode . '.json';
|
||||
|
||||
// Vérifier si le polygone existe
|
||||
if (!file_exists($polygonFile)) {
|
||||
$io->debug(sprintf('Polygone manquant pour %s', $inseeCode));
|
||||
$errorCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier si l'extraction JSON existe déjà
|
||||
if (file_exists($extractJsonFile) && !$force) {
|
||||
$existingCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Étape 1: Extraire les données de france-latest.osm.pbf vers un fichier PBF pour la zone
|
||||
$extractCommand = 'osmium extract -p ' . $polygonFile . ' ' . $francePbfFile . ' -o ' . $extractPbfFile;
|
||||
$outputLines = [];
|
||||
$returnVar = 0;
|
||||
exec($extractCommand, $outputLines, $returnVar);
|
||||
|
||||
if ($returnVar !== 0 || !file_exists($extractPbfFile)) {
|
||||
$io->debug(sprintf('Erreur lors de l\'extraction PBF pour %s: %s', $inseeCode, implode("\n", $outputLines)));
|
||||
$errorCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Étape 2: Convertir le fichier PBF en JSON
|
||||
$exportCommand = 'osmium export ' . $extractPbfFile . ' -f json -o ' . $extractJsonFile;
|
||||
$outputLines = [];
|
||||
$returnVar = 0;
|
||||
exec($exportCommand, $outputLines, $returnVar);
|
||||
|
||||
if ($returnVar === 0 && file_exists($extractJsonFile)) {
|
||||
$createdCount++;
|
||||
} else {
|
||||
$io->debug(sprintf('Erreur lors de l\'export JSON pour %s: %s', $inseeCode, implode("\n", $outputLines)));
|
||||
$errorCount++;
|
||||
}
|
||||
|
||||
// Supprimer le fichier PBF intermédiaire pour économiser de l'espace
|
||||
if (!$keepPbf && file_exists($extractPbfFile)) {
|
||||
unlink($extractPbfFile);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$io->debug(sprintf('Exception pour %s: %s', $inseeCode, $e->getMessage()));
|
||||
}
|
||||
|
||||
$io->progressAdvance();
|
||||
}
|
||||
|
||||
$io->progressFinish();
|
||||
|
||||
$io->success(sprintf(
|
||||
"Extraction des zones INSEE terminée : %d extractions créées, %d déjà existantes, %d erreurs sur un total de %d communes.",
|
||||
$createdCount,
|
||||
$existingCount,
|
||||
$errorCount,
|
||||
$totalCount
|
||||
));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
149
src/Command/ProcessInseeExtractsCommand.php
Normal file
149
src/Command/ProcessInseeExtractsCommand.php
Normal file
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Stats;
|
||||
use App\Service\Motocultrice;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:process-insee-extracts',
|
||||
description: 'Traite les extraits JSON des zones INSEE pour calculer les mesures de thèmes',
|
||||
)]
|
||||
class ProcessInseeExtractsCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private Motocultrice $motocultrice;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager, Motocultrice $motocultrice)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entityManager = $entityManager;
|
||||
$this->motocultrice = $motocultrice;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('insee-code', InputArgument::OPTIONAL, 'Code INSEE spécifique à traiter')
|
||||
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limite le nombre de villes à traiter', null)
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force le traitement même si déjà effectué')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$inseeCode = $input->getArgument('insee-code');
|
||||
$limit = $input->getOption('limit');
|
||||
$force = $input->getOption('force');
|
||||
|
||||
// Vérifier que le dossier des extractions existe
|
||||
$extractsDir = __DIR__ . '/../../insee_extracts';
|
||||
if (!is_dir($extractsDir)) {
|
||||
$io->error('Le dossier des extractions n\'existe pas. Veuillez d\'abord exécuter la commande app:extract-insee-zones.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Récupérer les Stats à traiter
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
|
||||
if ($inseeCode) {
|
||||
$io->note(sprintf('Traitement du code INSEE spécifique: %s', $inseeCode));
|
||||
$allStats = $statsRepo->findBy(['zone' => $inseeCode]);
|
||||
|
||||
if (empty($allStats)) {
|
||||
$io->error(sprintf('Aucune ville trouvée avec le code INSEE %s', $inseeCode));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
} else {
|
||||
$io->note('Traitement de toutes les villes');
|
||||
$criteria = [];
|
||||
$orderBy = ['id' => 'ASC'];
|
||||
$limitValue = $limit ? (int)$limit : null;
|
||||
|
||||
$allStats = $statsRepo->findBy($criteria, $orderBy, $limitValue);
|
||||
}
|
||||
|
||||
$totalCount = count($allStats);
|
||||
$processedCount = 0;
|
||||
$skippedCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
$io->progressStart($totalCount);
|
||||
|
||||
// Pour chaque Stats, traiter les données si nécessaire
|
||||
foreach ($allStats as $stat) {
|
||||
$inseeCode = $stat->getZone();
|
||||
if (!$inseeCode) {
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$extractJsonFile = $extractsDir . '/commune_' . $inseeCode . '.json';
|
||||
|
||||
// Vérifier si l'extraction JSON existe
|
||||
if (!file_exists($extractJsonFile)) {
|
||||
$io->debug(sprintf('Fichier JSON manquant pour %s', $inseeCode));
|
||||
$errorCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vérifier si le traitement a déjà été effectué
|
||||
if (!$force && $stat->getDateLabourageDone() !== null) {
|
||||
$skippedCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Utiliser la Motocultrice pour traiter les données
|
||||
$result = $this->motocultrice->labourer($inseeCode);
|
||||
|
||||
if ($result) {
|
||||
// Mettre à jour la date de labourage
|
||||
$stat->setDateLabourageDone(new \DateTime());
|
||||
$this->entityManager->persist($stat);
|
||||
$processedCount++;
|
||||
|
||||
// Flush tous les 10 objets pour éviter de surcharger la mémoire
|
||||
if ($processedCount % 10 === 0) {
|
||||
$this->entityManager->flush();
|
||||
$io->debug(sprintf('Flush après %d traitements', $processedCount));
|
||||
}
|
||||
} else {
|
||||
$io->debug(sprintf('Échec du traitement pour %s', $inseeCode));
|
||||
$errorCount++;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$io->debug(sprintf('Exception pour %s: %s', $inseeCode, $e->getMessage()));
|
||||
}
|
||||
|
||||
$io->progressAdvance();
|
||||
}
|
||||
|
||||
// Flush les derniers objets
|
||||
$this->entityManager->flush();
|
||||
|
||||
$io->progressFinish();
|
||||
|
||||
$io->success(sprintf(
|
||||
"Traitement des extractions INSEE terminé : %d communes traitées, %d ignorées, %d erreurs sur un total de %d communes.",
|
||||
$processedCount,
|
||||
$skippedCount,
|
||||
$errorCount,
|
||||
$totalCount
|
||||
));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
131
src/Command/RetrieveCityPolygonsCommand.php
Normal file
131
src/Command/RetrieveCityPolygonsCommand.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Stats;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:retrieve-city-polygons',
|
||||
description: 'Récupère les polygones des villes selon leur zone donnée par le code INSEE',
|
||||
)]
|
||||
class RetrieveCityPolygonsCommand extends Command
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->entityManager = $entityManager;
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addArgument('insee-code', InputArgument::OPTIONAL, 'Code INSEE spécifique à traiter')
|
||||
->addOption('limit', 'l', InputOption::VALUE_REQUIRED, 'Limite le nombre de villes à traiter', null)
|
||||
->addOption('force', 'f', InputOption::VALUE_NONE, 'Force la récupération même si le polygone existe déjà')
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$inseeCode = $input->getArgument('insee-code');
|
||||
$limit = $input->getOption('limit');
|
||||
$force = $input->getOption('force');
|
||||
|
||||
// Vérifier que le dossier polygons existe, sinon le créer
|
||||
$polygonsDir = __DIR__ . '/../../counting_osm_objects/polygons';
|
||||
if (!is_dir($polygonsDir)) {
|
||||
$io->note('Création du dossier polygons');
|
||||
mkdir($polygonsDir, 0755, true);
|
||||
}
|
||||
|
||||
// Récupérer les Stats à traiter
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
|
||||
if ($inseeCode) {
|
||||
$io->note(sprintf('Traitement du code INSEE spécifique: %s', $inseeCode));
|
||||
$allStats = $statsRepo->findBy(['zone' => $inseeCode]);
|
||||
|
||||
if (empty($allStats)) {
|
||||
$io->error(sprintf('Aucune ville trouvée avec le code INSEE %s', $inseeCode));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
} else {
|
||||
$io->note('Traitement de toutes les villes');
|
||||
$criteria = [];
|
||||
$orderBy = ['id' => 'ASC'];
|
||||
$limitValue = $limit ? (int)$limit : null;
|
||||
|
||||
$allStats = $statsRepo->findBy($criteria, $orderBy, $limitValue);
|
||||
}
|
||||
|
||||
$totalCount = count($allStats);
|
||||
$existingCount = 0;
|
||||
$createdCount = 0;
|
||||
$errorCount = 0;
|
||||
|
||||
$io->progressStart($totalCount);
|
||||
|
||||
// Pour chaque Stats, récupérer le polygone si nécessaire
|
||||
foreach ($allStats as $stat) {
|
||||
$inseeCode = $stat->getZone();
|
||||
if (!$inseeCode) {
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
$polygonFile = $polygonsDir . '/commune_' . $inseeCode . '.poly';
|
||||
|
||||
// Vérifier si le polygone existe déjà
|
||||
if (file_exists($polygonFile) && !$force) {
|
||||
$existingCount++;
|
||||
$io->progressAdvance();
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Utiliser le script Python existant pour récupérer le polygone
|
||||
$command = 'cd ' . __DIR__ . '/../../counting_osm_objects && python3 get_poly.py ' . $inseeCode;
|
||||
$output = [];
|
||||
$returnVar = 0;
|
||||
exec($command, $output, $returnVar);
|
||||
|
||||
if ($returnVar === 0 && file_exists($polygonFile)) {
|
||||
$createdCount++;
|
||||
} else {
|
||||
$errorCount++;
|
||||
if ($output) {
|
||||
$io->debug(sprintf('Erreur pour %s: %s', $inseeCode, implode("\n", $output)));
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$errorCount++;
|
||||
$io->debug(sprintf('Exception pour %s: %s', $inseeCode, $e->getMessage()));
|
||||
}
|
||||
|
||||
$io->progressAdvance();
|
||||
}
|
||||
|
||||
$io->progressFinish();
|
||||
|
||||
$io->success(sprintf(
|
||||
"Récupération des polygones terminée : %d polygones créés, %d déjà existants, %d erreurs sur un total de %d communes.",
|
||||
$createdCount,
|
||||
$existingCount,
|
||||
$errorCount,
|
||||
$totalCount
|
||||
));
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
|
@ -1826,11 +1826,48 @@ final class AdminController extends AbstractController
|
|||
{
|
||||
$this->actionLogger->log('admin/extract_insee_zones', []);
|
||||
|
||||
// Créer le dossier oss_data s'il n'existe pas
|
||||
$ossDataDir = __DIR__ . '/../../oss_data';
|
||||
if (!is_dir($ossDataDir)) {
|
||||
mkdir($ossDataDir, 0755, true);
|
||||
}
|
||||
|
||||
// Vérifier que le fichier france-latest.osm.pbf existe
|
||||
$francePbfFile = __DIR__ . '/../../france-latest.osm.pbf';
|
||||
$francePbfFile = $ossDataDir . '/france-latest.osm.pbf';
|
||||
if (!file_exists($francePbfFile)) {
|
||||
$this->addFlash('error', 'Le fichier france-latest.osm.pbf n\'existe pas. Veuillez le télécharger depuis https://download.geofabrik.de/europe/france.html');
|
||||
return $this->redirectToRoute('app_admin');
|
||||
$this->actionLogger->log('admin/download_france_pbf', []);
|
||||
$this->addFlash('info', 'Le fichier france-latest.osm.pbf n\'existe pas. Téléchargement en cours depuis Geofabrik...');
|
||||
|
||||
// URL de téléchargement
|
||||
$downloadUrl = 'https://download.geofabrik.de/europe/france-latest.osm.pbf';
|
||||
|
||||
// Télécharger le fichier
|
||||
try {
|
||||
$context = stream_context_create([
|
||||
'http' => [
|
||||
'header' => "User-Agent: OSM-Commerces/1.0\r\n"
|
||||
]
|
||||
]);
|
||||
|
||||
// Utiliser file_get_contents pour télécharger le fichier
|
||||
$fileContent = file_get_contents($downloadUrl, false, $context);
|
||||
if ($fileContent === false) {
|
||||
throw new \Exception('Échec du téléchargement');
|
||||
}
|
||||
|
||||
// Sauvegarder le fichier
|
||||
if (file_put_contents($francePbfFile, $fileContent) === false) {
|
||||
throw new \Exception('Échec de l\'écriture du fichier');
|
||||
}
|
||||
|
||||
$this->addFlash('success', 'Le fichier france-latest.osm.pbf a été téléchargé avec succès.');
|
||||
} catch (\Exception $e) {
|
||||
$this->actionLogger->log('error_download_france_pbf', [
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
$this->addFlash('error', 'Erreur lors du téléchargement du fichier france-latest.osm.pbf: ' . $e->getMessage());
|
||||
return $this->redirectToRoute('app_admin');
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier que le dossier polygons existe
|
||||
|
@ -2008,6 +2045,571 @@ final class AdminController extends AbstractController
|
|||
return $this->redirectToRoute('app_admin');
|
||||
}
|
||||
|
||||
#[Route('/admin/completion-statistics', name: 'app_admin_completion_statistics')]
|
||||
public function completionStatistics(Request $request): Response
|
||||
{
|
||||
$this->actionLogger->log('admin/completion_statistics', []);
|
||||
|
||||
// Récupérer le thème sélectionné (par défaut: 'places')
|
||||
$theme = $request->query->get('theme', 'places');
|
||||
|
||||
// Récupérer le niveau géographique sélectionné (par défaut: 'department')
|
||||
$level = $request->query->get('level', 'department');
|
||||
|
||||
// Récupérer tous les thèmes disponibles
|
||||
$themes = $this->followUpService->getFollowUpThemes();
|
||||
|
||||
// Récupérer les données de complétion selon le niveau géographique
|
||||
$completionData = [];
|
||||
$chartLabels = [];
|
||||
$chartData = [];
|
||||
|
||||
switch ($level) {
|
||||
case 'department':
|
||||
// Agréger les données par département
|
||||
$completionData = $this->aggregateCompletionByDepartment($theme);
|
||||
break;
|
||||
|
||||
case 'region':
|
||||
// Agréger les données par région
|
||||
$completionData = $this->aggregateCompletionByRegion($theme);
|
||||
break;
|
||||
|
||||
case 'country':
|
||||
// Agréger les données pour la France entière
|
||||
$completionData = $this->aggregateCompletionForCountry($theme);
|
||||
break;
|
||||
|
||||
case 'city':
|
||||
// Récupérer les données pour les villes (limité aux 50 plus grandes)
|
||||
$completionData = $this->aggregateCompletionForTopCities($theme, 50);
|
||||
break;
|
||||
}
|
||||
|
||||
// Préparer les données pour les graphiques
|
||||
foreach ($completionData as $key => $value) {
|
||||
$chartLabels[] = $key;
|
||||
$chartData[] = $value;
|
||||
}
|
||||
|
||||
return $this->render('admin/completion_statistics.html.twig', [
|
||||
'theme' => $theme,
|
||||
'level' => $level,
|
||||
'themes' => $themes,
|
||||
'chartLabels' => json_encode($chartLabels),
|
||||
'chartData' => json_encode($chartData),
|
||||
'completionData' => $completionData
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les données de complétion par département
|
||||
*/
|
||||
private function aggregateCompletionByDepartment(string $theme): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Récupérer toutes les Stats
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
$allStats = $statsRepo->findAll();
|
||||
|
||||
// Grouper par département (2 premiers chiffres du code INSEE)
|
||||
$departmentData = [];
|
||||
$departmentCounts = [];
|
||||
|
||||
foreach ($allStats as $stat) {
|
||||
$inseeCode = $stat->getZone();
|
||||
if (!$inseeCode || strlen($inseeCode) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extraire le code département (2 premiers chiffres du code INSEE)
|
||||
$departmentCode = substr($inseeCode, 0, 2);
|
||||
|
||||
// Cas particuliers pour la Corse
|
||||
if ($departmentCode === '2A' || $departmentCode === '2B') {
|
||||
$departmentCode = substr($inseeCode, 0, 3);
|
||||
}
|
||||
|
||||
// Récupérer les mesures pour le thème spécifié
|
||||
$latestMeasure = $this->getLatestMeasureForTheme($stat, $theme);
|
||||
|
||||
if ($latestMeasure !== null) {
|
||||
if (!isset($departmentData[$departmentCode])) {
|
||||
$departmentData[$departmentCode] = 0;
|
||||
$departmentCounts[$departmentCode] = 0;
|
||||
}
|
||||
|
||||
$departmentData[$departmentCode] += $latestMeasure;
|
||||
$departmentCounts[$departmentCode]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer les moyennes par département
|
||||
foreach ($departmentData as $departmentCode => $total) {
|
||||
if ($departmentCounts[$departmentCode] > 0) {
|
||||
$result[$departmentCode] = round($total / $departmentCounts[$departmentCode], 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par code département
|
||||
ksort($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les données de complétion par région
|
||||
*/
|
||||
private function aggregateCompletionByRegion(string $theme): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Mapping des départements vers les régions
|
||||
$departmentToRegion = $this->getDepartmentToRegionMapping();
|
||||
|
||||
// Récupérer les données par département
|
||||
$departmentData = $this->aggregateCompletionByDepartment($theme);
|
||||
|
||||
// Grouper par région
|
||||
$regionData = [];
|
||||
$regionCounts = [];
|
||||
|
||||
foreach ($departmentData as $departmentCode => $value) {
|
||||
// Trouver la région correspondante
|
||||
$region = $departmentToRegion[$departmentCode] ?? 'Autre';
|
||||
|
||||
if (!isset($regionData[$region])) {
|
||||
$regionData[$region] = 0;
|
||||
$regionCounts[$region] = 0;
|
||||
}
|
||||
|
||||
$regionData[$region] += $value;
|
||||
$regionCounts[$region]++;
|
||||
}
|
||||
|
||||
// Calculer les moyennes par région
|
||||
foreach ($regionData as $region => $total) {
|
||||
if ($regionCounts[$region] > 0) {
|
||||
$result[$region] = round($total / $regionCounts[$region], 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par nom de région
|
||||
ksort($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les données de complétion pour la France entière
|
||||
*/
|
||||
private function aggregateCompletionForCountry(string $theme): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Récupérer toutes les Stats
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
$allStats = $statsRepo->findAll();
|
||||
|
||||
// Récupérer les mesures pour le thème spécifié
|
||||
$totalMeasure = 0;
|
||||
$count = 0;
|
||||
|
||||
foreach ($allStats as $stat) {
|
||||
$latestMeasure = $this->getLatestMeasureForTheme($stat, $theme);
|
||||
|
||||
if ($latestMeasure !== null) {
|
||||
$totalMeasure += $latestMeasure;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculer la moyenne nationale
|
||||
if ($count > 0) {
|
||||
$result['France'] = round($totalMeasure / $count, 2);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Agrège les données de complétion pour les principales villes
|
||||
*/
|
||||
private function aggregateCompletionForTopCities(string $theme, int $limit = 50): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// Récupérer les Stats triées par population (descendant)
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
$topCities = $statsRepo->findBy(
|
||||
['population' => null], // Critère (tous)
|
||||
['population' => 'DESC'], // Tri par population descendante
|
||||
$limit // Limite
|
||||
);
|
||||
|
||||
// Récupérer les mesures pour le thème spécifié
|
||||
foreach ($topCities as $stat) {
|
||||
$cityName = $stat->getName() ?? $stat->getZone();
|
||||
$latestMeasure = $this->getLatestMeasureForTheme($stat, $theme);
|
||||
|
||||
if ($latestMeasure !== null) {
|
||||
$result[$cityName] = $latestMeasure;
|
||||
}
|
||||
}
|
||||
|
||||
// Trier par valeur décroissante
|
||||
arsort($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la dernière mesure pour un thème donné
|
||||
*/
|
||||
private function getLatestMeasureForTheme(Stats $stat, string $theme): ?float
|
||||
{
|
||||
$cityFollowUps = $stat->getCityFollowUps();
|
||||
|
||||
// Filtrer les CityFollowUp par thème et trier par date (descendant)
|
||||
$filteredFollowUps = $cityFollowUps->filter(function(CityFollowUp $followUp) use ($theme) {
|
||||
return $followUp->getName() === $theme;
|
||||
});
|
||||
|
||||
if ($filteredFollowUps->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Trier par date (du plus récent au plus ancien)
|
||||
$iterator = $filteredFollowUps->getIterator();
|
||||
$iterator->uasort(function(CityFollowUp $a, CityFollowUp $b) {
|
||||
return $b->getDate() <=> $a->getDate();
|
||||
});
|
||||
|
||||
// Récupérer la mesure la plus récente
|
||||
$latestFollowUp = $iterator->current();
|
||||
return $latestFollowUp->getMeasure();
|
||||
}
|
||||
|
||||
#[Route('/admin/osmose-issues-map/{inseeCode}', name: 'app_admin_osmose_issues_map')]
|
||||
public function osmoseIssuesMap(Request $request, string $inseeCode = null): Response
|
||||
{
|
||||
$this->actionLogger->log('admin/osmose_issues_map', [
|
||||
'insee_code' => $inseeCode
|
||||
]);
|
||||
|
||||
// Si aucun code INSEE n'est fourni, rediriger vers la liste des villes
|
||||
if (!$inseeCode) {
|
||||
$this->addFlash('info', 'Veuillez sélectionner une ville pour afficher les problèmes Osmose.');
|
||||
return $this->redirectToRoute('app_admin');
|
||||
}
|
||||
|
||||
// Récupérer la ville correspondante
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
$city = $statsRepo->findOneBy(['zone' => $inseeCode]);
|
||||
|
||||
if (!$city) {
|
||||
$this->addFlash('error', 'Ville non trouvée pour le code INSEE ' . $inseeCode);
|
||||
return $this->redirectToRoute('app_admin');
|
||||
}
|
||||
|
||||
// Récupérer le thème sélectionné (par défaut: tous)
|
||||
$theme = $request->query->get('theme', 'all');
|
||||
|
||||
// Récupérer tous les thèmes disponibles
|
||||
$themes = $this->followUpService->getFollowUpThemes();
|
||||
|
||||
// Récupérer les problèmes Osmose pour cette ville
|
||||
$osmoseIssues = $this->getOsmoseIssuesForCity($city, $theme);
|
||||
|
||||
return $this->render('admin/osmose_issues_map.html.twig', [
|
||||
'city' => $city,
|
||||
'theme' => $theme,
|
||||
'themes' => $themes,
|
||||
'osmoseIssues' => $osmoseIssues
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les problèmes Osmose pour une ville donnée
|
||||
*/
|
||||
private function getOsmoseIssuesForCity(Stats $city, string $theme = 'all'): array
|
||||
{
|
||||
$issues = [];
|
||||
|
||||
// Coordonnées de la ville
|
||||
$lat = $city->getLat();
|
||||
$lon = $city->getLon();
|
||||
|
||||
if (!$lat || !$lon) {
|
||||
return $issues;
|
||||
}
|
||||
|
||||
// Construire l'URL de l'API Osmose
|
||||
$bbox = $this->calculateBoundingBox($lat, $lon, 5); // 5km autour du centre de la ville
|
||||
$osmoseApiUrl = sprintf(
|
||||
'https://osmose.openstreetmap.fr/api/0.3/issues?bbox=%f,%f,%f,%f&item=xxxx&limit=500',
|
||||
$bbox['min_lon'],
|
||||
$bbox['min_lat'],
|
||||
$bbox['max_lon'],
|
||||
$bbox['max_lat']
|
||||
);
|
||||
|
||||
// Récupérer les items Osmose correspondant aux thèmes
|
||||
$itemIds = $this->getOsmoseItemIdsForTheme($theme);
|
||||
if (!empty($itemIds)) {
|
||||
$osmoseApiUrl = str_replace('xxxx', implode(',', $itemIds), $osmoseApiUrl);
|
||||
} else {
|
||||
$osmoseApiUrl = str_replace('&item=xxxx', '', $osmoseApiUrl);
|
||||
}
|
||||
|
||||
try {
|
||||
// Appeler l'API Osmose
|
||||
$response = file_get_contents($osmoseApiUrl);
|
||||
if ($response === false) {
|
||||
throw new \Exception('Échec de la récupération des données Osmose');
|
||||
}
|
||||
|
||||
$data = json_decode($response, true);
|
||||
if (isset($data['issues'])) {
|
||||
foreach ($data['issues'] as $issue) {
|
||||
// Vérifier si l'issue est dans les limites de la ville (approximativement)
|
||||
if ($this->isPointInCity($issue['lat'], $issue['lon'], $lat, $lon, 5)) {
|
||||
$issues[] = [
|
||||
'id' => $issue['id'],
|
||||
'title' => $issue['title'] ?? 'Problème sans titre',
|
||||
'subtitle' => $issue['subtitle'] ?? '',
|
||||
'lat' => $issue['lat'],
|
||||
'lon' => $issue['lon'],
|
||||
'item' => $issue['item'],
|
||||
'class' => $issue['class'],
|
||||
'level' => $issue['level'] ?? 2,
|
||||
'update_timestamp' => $issue['update_timestamp'] ?? null,
|
||||
'url' => sprintf('https://osmose.openstreetmap.fr/fr/issue/%s', $issue['uuid'])
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->actionLogger->log('error_osmose_api', [
|
||||
'insee_code' => $city->getZone(),
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
return $issues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule une bounding box autour d'un point
|
||||
*/
|
||||
private function calculateBoundingBox(string $lat, string $lon, int $distanceKm = 5): array
|
||||
{
|
||||
$lat = (float)$lat;
|
||||
$lon = (float)$lon;
|
||||
|
||||
// Approximation: 1 degré de latitude = 111 km
|
||||
$latDelta = $distanceKm / 111.0;
|
||||
|
||||
// Approximation: 1 degré de longitude = 111 * cos(latitude) km
|
||||
$lonDelta = $distanceKm / (111.0 * cos(deg2rad($lat)));
|
||||
|
||||
return [
|
||||
'min_lat' => $lat - $latDelta,
|
||||
'max_lat' => $lat + $latDelta,
|
||||
'min_lon' => $lon - $lonDelta,
|
||||
'max_lon' => $lon + $lonDelta
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un point est dans un rayon donné autour d'une ville
|
||||
*/
|
||||
private function isPointInCity(float $pointLat, float $pointLon, string $cityLat, string $cityLon, int $radiusKm = 5): bool
|
||||
{
|
||||
$cityLat = (float)$cityLat;
|
||||
$cityLon = (float)$cityLon;
|
||||
|
||||
// Calcul de la distance en km (formule de Haversine)
|
||||
$earthRadius = 6371; // Rayon de la Terre en km
|
||||
|
||||
$dLat = deg2rad($pointLat - $cityLat);
|
||||
$dLon = deg2rad($pointLon - $cityLon);
|
||||
|
||||
$a = sin($dLat/2) * sin($dLat/2) +
|
||||
cos(deg2rad($cityLat)) * cos(deg2rad($pointLat)) *
|
||||
sin($dLon/2) * sin($dLon/2);
|
||||
|
||||
$c = 2 * atan2(sqrt($a), sqrt(1-$a));
|
||||
$distance = $earthRadius * $c;
|
||||
|
||||
return $distance <= $radiusKm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les IDs des items Osmose correspondant à un thème
|
||||
*/
|
||||
private function getOsmoseItemIdsForTheme(string $theme): array
|
||||
{
|
||||
// Mapping des thèmes vers les items Osmose
|
||||
$themeToItemsMapping = [
|
||||
'places' => [8230, 8240, 8250, 8260], // Commerces et services
|
||||
'restaurants' => [8030, 8031, 8032], // Restaurants et cafés
|
||||
'hotels' => [8040, 8041, 8042], // Hébergements
|
||||
'tourism' => [8010, 8011, 8012, 8013], // Tourisme
|
||||
'leisure' => [8050, 8051, 8052], // Loisirs
|
||||
'healthcare' => [8060, 8061, 8062], // Santé
|
||||
'education' => [8070, 8071, 8072], // Éducation
|
||||
'transportation' => [4010, 4020, 4030, 4040], // Transport
|
||||
'amenities' => [8080, 8081, 8082], // Équipements
|
||||
// Si d'autres thèmes sont nécessaires, ajoutez-les ici
|
||||
];
|
||||
|
||||
// Si le thème est 'all' ou n'existe pas dans le mapping, retourner un tableau vide
|
||||
if ($theme === 'all' || !isset($themeToItemsMapping[$theme])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $themeToItemsMapping[$theme];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne un mapping des départements vers les régions
|
||||
*/
|
||||
private function getDepartmentToRegionMapping(): array
|
||||
{
|
||||
return [
|
||||
// Auvergne-Rhône-Alpes
|
||||
'01' => 'Auvergne-Rhône-Alpes', // Ain
|
||||
'03' => 'Auvergne-Rhône-Alpes', // Allier
|
||||
'07' => 'Auvergne-Rhône-Alpes', // Ardèche
|
||||
'15' => 'Auvergne-Rhône-Alpes', // Cantal
|
||||
'26' => 'Auvergne-Rhône-Alpes', // Drôme
|
||||
'38' => 'Auvergne-Rhône-Alpes', // Isère
|
||||
'42' => 'Auvergne-Rhône-Alpes', // Loire
|
||||
'43' => 'Auvergne-Rhône-Alpes', // Haute-Loire
|
||||
'63' => 'Auvergne-Rhône-Alpes', // Puy-de-Dôme
|
||||
'69' => 'Auvergne-Rhône-Alpes', // Rhône
|
||||
'73' => 'Auvergne-Rhône-Alpes', // Savoie
|
||||
'74' => 'Auvergne-Rhône-Alpes', // Haute-Savoie
|
||||
|
||||
// Bourgogne-Franche-Comté
|
||||
'21' => 'Bourgogne-Franche-Comté', // Côte-d'Or
|
||||
'25' => 'Bourgogne-Franche-Comté', // Doubs
|
||||
'39' => 'Bourgogne-Franche-Comté', // Jura
|
||||
'58' => 'Bourgogne-Franche-Comté', // Nièvre
|
||||
'70' => 'Bourgogne-Franche-Comté', // Haute-Saône
|
||||
'71' => 'Bourgogne-Franche-Comté', // Saône-et-Loire
|
||||
'89' => 'Bourgogne-Franche-Comté', // Yonne
|
||||
'90' => 'Bourgogne-Franche-Comté', // Territoire de Belfort
|
||||
|
||||
// Bretagne
|
||||
'22' => 'Bretagne', // Côtes-d'Armor
|
||||
'29' => 'Bretagne', // Finistère
|
||||
'35' => 'Bretagne', // Ille-et-Vilaine
|
||||
'56' => 'Bretagne', // Morbihan
|
||||
|
||||
// Centre-Val de Loire
|
||||
'18' => 'Centre-Val de Loire', // Cher
|
||||
'28' => 'Centre-Val de Loire', // Eure-et-Loir
|
||||
'36' => 'Centre-Val de Loire', // Indre
|
||||
'37' => 'Centre-Val de Loire', // Indre-et-Loire
|
||||
'41' => 'Centre-Val de Loire', // Loir-et-Cher
|
||||
'45' => 'Centre-Val de Loire', // Loiret
|
||||
|
||||
// Corse
|
||||
'2A' => 'Corse', // Corse-du-Sud
|
||||
'2B' => 'Corse', // Haute-Corse
|
||||
|
||||
// Grand Est
|
||||
'08' => 'Grand Est', // Ardennes
|
||||
'10' => 'Grand Est', // Aube
|
||||
'51' => 'Grand Est', // Marne
|
||||
'52' => 'Grand Est', // Haute-Marne
|
||||
'54' => 'Grand Est', // Meurthe-et-Moselle
|
||||
'55' => 'Grand Est', // Meuse
|
||||
'57' => 'Grand Est', // Moselle
|
||||
'67' => 'Grand Est', // Bas-Rhin
|
||||
'68' => 'Grand Est', // Haut-Rhin
|
||||
'88' => 'Grand Est', // Vosges
|
||||
|
||||
// Hauts-de-France
|
||||
'02' => 'Hauts-de-France', // Aisne
|
||||
'59' => 'Hauts-de-France', // Nord
|
||||
'60' => 'Hauts-de-France', // Oise
|
||||
'62' => 'Hauts-de-France', // Pas-de-Calais
|
||||
'80' => 'Hauts-de-France', // Somme
|
||||
|
||||
// Île-de-France
|
||||
'75' => 'Île-de-France', // Paris
|
||||
'77' => 'Île-de-France', // Seine-et-Marne
|
||||
'78' => 'Île-de-France', // Yvelines
|
||||
'91' => 'Île-de-France', // Essonne
|
||||
'92' => 'Île-de-France', // Hauts-de-Seine
|
||||
'93' => 'Île-de-France', // Seine-Saint-Denis
|
||||
'94' => 'Île-de-France', // Val-de-Marne
|
||||
'95' => 'Île-de-France', // Val-d'Oise
|
||||
|
||||
// Normandie
|
||||
'14' => 'Normandie', // Calvados
|
||||
'27' => 'Normandie', // Eure
|
||||
'50' => 'Normandie', // Manche
|
||||
'61' => 'Normandie', // Orne
|
||||
'76' => 'Normandie', // Seine-Maritime
|
||||
|
||||
// Nouvelle-Aquitaine
|
||||
'16' => 'Nouvelle-Aquitaine', // Charente
|
||||
'17' => 'Nouvelle-Aquitaine', // Charente-Maritime
|
||||
'19' => 'Nouvelle-Aquitaine', // Corrèze
|
||||
'23' => 'Nouvelle-Aquitaine', // Creuse
|
||||
'24' => 'Nouvelle-Aquitaine', // Dordogne
|
||||
'33' => 'Nouvelle-Aquitaine', // Gironde
|
||||
'40' => 'Nouvelle-Aquitaine', // Landes
|
||||
'47' => 'Nouvelle-Aquitaine', // Lot-et-Garonne
|
||||
'64' => 'Nouvelle-Aquitaine', // Pyrénées-Atlantiques
|
||||
'79' => 'Nouvelle-Aquitaine', // Deux-Sèvres
|
||||
'86' => 'Nouvelle-Aquitaine', // Vienne
|
||||
'87' => 'Nouvelle-Aquitaine', // Haute-Vienne
|
||||
|
||||
// Occitanie
|
||||
'09' => 'Occitanie', // Ariège
|
||||
'11' => 'Occitanie', // Aude
|
||||
'12' => 'Occitanie', // Aveyron
|
||||
'30' => 'Occitanie', // Gard
|
||||
'31' => 'Occitanie', // Haute-Garonne
|
||||
'32' => 'Occitanie', // Gers
|
||||
'34' => 'Occitanie', // Hérault
|
||||
'46' => 'Occitanie', // Lot
|
||||
'48' => 'Occitanie', // Lozère
|
||||
'65' => 'Occitanie', // Hautes-Pyrénées
|
||||
'66' => 'Occitanie', // Pyrénées-Orientales
|
||||
'81' => 'Occitanie', // Tarn
|
||||
'82' => 'Occitanie', // Tarn-et-Garonne
|
||||
|
||||
// Pays de la Loire
|
||||
'44' => 'Pays de la Loire', // Loire-Atlantique
|
||||
'49' => 'Pays de la Loire', // Maine-et-Loire
|
||||
'53' => 'Pays de la Loire', // Mayenne
|
||||
'72' => 'Pays de la Loire', // Sarthe
|
||||
'85' => 'Pays de la Loire', // Vendée
|
||||
|
||||
// Provence-Alpes-Côte d'Azur
|
||||
'04' => 'Provence-Alpes-Côte d\'Azur', // Alpes-de-Haute-Provence
|
||||
'05' => 'Provence-Alpes-Côte d\'Azur', // Hautes-Alpes
|
||||
'06' => 'Provence-Alpes-Côte d\'Azur', // Alpes-Maritimes
|
||||
'13' => 'Provence-Alpes-Côte d\'Azur', // Bouches-du-Rhône
|
||||
'83' => 'Provence-Alpes-Côte d\'Azur', // Var
|
||||
'84' => 'Provence-Alpes-Côte d\'Azur', // Vaucluse
|
||||
|
||||
// Départements d'outre-mer
|
||||
'971' => 'Guadeloupe',
|
||||
'972' => 'Martinique',
|
||||
'973' => 'Guyane',
|
||||
'974' => 'La Réunion',
|
||||
'976' => 'Mayotte'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complète les données manquantes d'un objet Stats (coordonnées, budget, etc.)
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue