diff --git a/assets/app.js b/assets/app.js index e81c6af1..40604d5c 100644 --- a/assets/app.js +++ b/assets/app.js @@ -244,7 +244,7 @@ document.addEventListener('DOMContentLoaded', () => { } } if (insee) { - window.location.href = `/admin/labourer/${insee}`; + window.location.href = `/add-city-without-labourage/${insee}`; } else { alert('Veuillez sélectionner une ville valide.'); } diff --git a/assets/utils.js b/assets/utils.js index 610c29b9..1abee1e9 100644 --- a/assets/utils.js +++ b/assets/utils.js @@ -184,7 +184,7 @@ export function enableLabourageForm() { form.action = getLabourerUrl(result_search); // Changer le texte du bouton et le désactiver - labourageBtn.innerHTML = ' Labourage de ' + result_search.name + '...'; + labourageBtn.innerHTML = ' Ajout de ' + result_search.name + '...'; labourageBtn.disabled = true; // Soumettre le formulaire @@ -281,7 +281,7 @@ export function setupCitySearch(inputId, suggestionListId, onSelect) { export function getLabourerUrl(obj) { if (obj && obj.insee) { - return `/admin/labourer/${obj.insee}`; + return `/add-city-without-labourage/${obj.insee}`; } return '#'; } @@ -290,7 +290,7 @@ export function handleAddCityFormSubmit(event) { event.preventDefault(); const zipCode = document.getElementById('selectedZipCode').value; if (zipCode && zipCode.match(/^\d{5}$/)) { - window.location.href = `/admin/labourer/${zipCode}`; + window.location.href = `/add-city-without-labourage/${zipCode}`; } else { alert('Veuillez sélectionner une ville valide avec un code postal.'); } diff --git a/counting_osm_objects/.gitignore b/counting_osm_objects/.gitignore index 0a0fae49..5fb9335d 100644 --- a/counting_osm_objects/.gitignore +++ b/counting_osm_objects/.gitignore @@ -4,6 +4,8 @@ polygons/*.poly test_data/* test_temp/* test_results/* +temp/* +resultats/* osm_config.txt __pycache__ secrets.sh diff --git a/counting_osm_objects/README.md b/counting_osm_objects/README.md index 7a526f1b..f38a03d7 100644 --- a/counting_osm_objects/README.md +++ b/counting_osm_objects/README.md @@ -5,6 +5,9 @@ administratives françaises. Pour fonctionner vous aurez besoin du fichier historisé de la france, pour cela connectez vous à geofabrik avec votre compte osm (oui c'est relou). +## TODO + +faire une extraction json de toutes les zones insee de france et réaliser des mesures pour tous les thèmes. ## Scripts disponibles @@ -146,6 +149,89 @@ END Aucune installation spécifique n'est nécessaire pour ces scripts. Assurez-vous simplement que les dépendances sont installées. +### analyze_city_polygons.py + +Ce script analyse les polygones de villes et génère des fichiers JSON d'analyse. Il: + +1. Parcourt tous les fichiers de polygones dans le dossier "polygons" +2. Pour chaque polygone, vérifie si un fichier d'analyse JSON existe déjà +3. Si non, utilise loop_thematics_history_in_zone_to_counts.py pour extraire les données +4. Sauvegarde les résultats dans un fichier JSON avec une analyse de complétion +5. Ajoute une date de création à chaque analyse + +#### Utilisation + +```bash +python analyze_city_polygons.py [--force] [--single CODE_INSEE] +``` + +Options: +- `--force` ou `-f`: Force la recréation des analyses existantes +- `--single` ou `-s`: Traite uniquement le polygone spécifié (code INSEE) + +#### Exemples + +Analyser tous les polygones disponibles: + +```bash +python analyze_city_polygons.py +``` + +Analyser uniquement la commune avec le code INSEE 59140: + +```bash +python analyze_city_polygons.py --single 59140 +``` + +Forcer la recréation des analyses existantes: + +```bash +python analyze_city_polygons.py --force +``` + +#### Format de sortie + +Les fichiers d'analyse sont au format JSON et sont sauvegardés dans le dossier `city_analysis`. Chaque fichier contient: + +- Une section `themes` avec des données pour chaque thématique (nombre d'objets, pourcentage de complétion, etc.) +- Une section `metadata` avec des informations sur l'analyse (code INSEE, date de création, statistiques globales) + +Exemple de contenu: + +```json +{ + "themes": { + "borne-de-recharge": { + "date": "2025-08-21", + "zone": "commune_59140", + "theme": "borne-de-recharge", + "nombre_total": 0, + "nombre_avec_operator": 0, + "nombre_avec_capacity": 0, + "pourcentage_completion": 0 + }, + "borne-incendie": { + "date": "2025-08-21", + "zone": "commune_59140", + "theme": "borne-incendie", + "nombre_total": 0, + "nombre_avec_ref": 0, + "nombre_avec_colour": 0, + "pourcentage_completion": 0 + } + }, + "metadata": { + "insee_code": "59140", + "creation_date": "2025-08-21 11:12:20", + "polygon_file": "commune_59140.poly", + "osm_data_file": "france-latest.osm.pbf", + "total_objects": 679, + "average_completion": 0.44, + "theme_count": 7 + } +} +``` + ## Licence Ces scripts sont distribués sous la même licence que le projet Osmose-Backend. \ No newline at end of file diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php index c7d0d1ff..8c5f8653 100644 --- a/src/Controller/PublicController.php +++ b/src/Controller/PublicController.php @@ -859,6 +859,84 @@ class PublicController extends AbstractController 'controller_name' => 'PublicController', ]); } + + /** + * Ajoute une ville sans déclencher le labourage et redirige vers la page thématique des lieux + */ + #[Route('/add-city-without-labourage/{insee_code}', name: 'app_public_add_city_without_labourage')] + public function addCityWithoutLabourage(string $insee_code): Response + { + $this->actionLogger->log('add_city_without_labourage', ['insee_code' => $insee_code]); + + // Vérifier si le code INSEE est valide (composé uniquement de chiffres) + if (!ctype_digit($insee_code) || $insee_code == 'undefined' || $insee_code == '') { + $this->addFlash('error', 'Code INSEE invalide : il doit être composé uniquement de chiffres.'); + $this->actionLogger->log('ERROR_add_city_without_labourage_bad_insee', ['insee_code' => $insee_code]); + return $this->redirectToRoute('app_public_index'); + } + + // Récupérer ou créer l'objet Stats + $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); + if (!$stats) { + $stats = new Stats(); + $stats->setZone($insee_code); + $stats->setKind('request'); // Set the kind to 'request' as it's created from a user request + } + + // Compléter le nom si manquant + if (!$stats->getName()) { + $cityName = $this->motocultrice->get_city_osm_from_zip_code($insee_code); + if ($cityName) { + $stats->setName($cityName); + } + } + + // Compléter la population si manquante + if (!$stats->getPopulation()) { + try { + $apiUrl = 'https://geo.api.gouv.fr/communes/' . $insee_code; + $response = @file_get_contents($apiUrl); + if ($response !== false) { + $data = json_decode($response, true); + if (isset($data['population'])) { + $stats->setPopulation((int)$data['population']); + } + } + } catch (\Exception $e) { + // Ignorer les erreurs + } + } + + // Compléter les lieux d'intérêt si manquants (lat/lon) + if (!$stats->getLat() || !$stats->getLon()) { + // On tente de récupérer le centre de la ville via l'API geo.gouv.fr + try { + $apiUrl = 'https://geo.api.gouv.fr/communes/' . $insee_code . '?fields=centre'; + $response = @file_get_contents($apiUrl); + if ($response !== false) { + $data = json_decode($response, true); + if (isset($data['centre']['coordinates']) && count($data['centre']['coordinates']) === 2) { + $stats->setLon((string)$data['centre']['coordinates'][0]); + $stats->setLat((string)$data['centre']['coordinates'][1]); + } + } + } catch (\Exception $e) { + // Ignorer les erreurs + } + } + + // Sauvegarder l'objet Stats + $stats->computeCompletionPercent(); + $this->entityManager->persist($stats); + $this->entityManager->flush(); + + // Rediriger vers la page thématique des lieux + return $this->redirectToRoute('admin_followup_theme_graph', [ + 'insee_code' => $insee_code, + 'theme' => 'places' + ]); + + } #[Route('/stats/{insee_code}/followup-graph/{theme}', name: 'app_public_followup_graph', requirements: ['insee_code' => '\\d+', 'theme' => '[a-zA-Z0-9_]+'])] public function publicFollowupGraph(string $insee_code, string $theme): Response @@ -875,6 +953,37 @@ class PublicController extends AbstractController return $this->redirectToRoute('app_public_index'); } + // Vérifier si des mesures ont été enregistrées aujourd'hui + $today = new \DateTime(); + $today->setTime(0, 0, 0); // Début de la journée + + $hasRecentMeasurements = false; + foreach ($stats->getCityFollowUps() as $fu) { + if ($fu->getName() === $theme . '_count' || $fu->getName() === $theme . '_completion') { + $measureDate = clone $fu->getDate(); + $measureDate->setTime(0, 0, 0); + if ($measureDate >= $today) { + $hasRecentMeasurements = true; + break; + } + } + } + + // Si aucune mesure récente n'existe, générer de nouvelles mesures + if (!$hasRecentMeasurements) { + $this->actionLogger->log('generate_measurements_on_graph_view', [ + 'insee_code' => $insee_code, + 'theme' => $theme + ]); + + // Générer les mesures pour tous les thèmes + $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager, true); + $this->entityManager->flush(); + + // Re-fetch the Stats entity to ensure it's managed by the EntityManager + $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); + } + // Récupérer toutes les données de followup pour ce thème $followups = $stats->getCityFollowUps(); $countData = [];