findAll(); $features = []; foreach ($statsList as $stats) { // Calcul du barycentre des commerces de la zone $lat = null; $lon = null; $places = $stats->getPlaces(); $count = 0; $sumLat = 0; $sumLon = 0; foreach ($places as $place) { if ($place->getLat() && $place->getLon()) { $sumLat += $place->getLat(); $sumLon += $place->getLon(); $count++; } } if ($count > 0) { $lat = $sumLat / $count; $lon = $sumLon / $count; } $feature = [ 'type' => 'Feature', 'geometry' => $lat && $lon ? [ 'type' => 'Point', 'coordinates' => [$lon, $lat], ] : null, 'properties' => [ 'id' => $stats->getId(), 'name' => $stats->getName(), 'zone' => $stats->getZone(), 'completion_percent' => $stats->getCompletionPercent(), 'places_count' => $stats->getPlacesCount(), 'avec_horaires' => $stats->getAvecHoraires(), 'avec_adresse' => $stats->getAvecAdresse(), 'avec_site' => $stats->getAvecSite(), 'avec_accessibilite' => $stats->getAvecAccessibilite(), 'avec_note' => $stats->getAvecNote(), 'population' => $stats->getPopulation(), 'siren' => $stats->getSiren(), 'codeEpci' => $stats->getCodeEpci(), 'codesPostaux' => $stats->getCodesPostaux(), 'date_created' => $stats->getDateCreated() ? $stats->getDateCreated()->format('c') : null, 'date_modified' => $stats->getDateModified() ? $stats->getDateModified()->format('c') : null, ], ]; $features[] = $feature; } $geojson = [ 'type' => 'FeatureCollection', 'features' => $features, 'meta' => [ 'generated_at' => (new \DateTime())->format('c'), 'source' => 'https://osm-commerces.cipherbliss.com/api/v1/stats_geojson' ] ]; return new JsonResponse($geojson, Response::HTTP_OK, [ 'Content-Type' => 'application/geo+json' ]); } #[Route('/api/v1/stats/{insee}', name: 'api_stats_by_insee', methods: ['GET'])] public function statsByInsee(StatsRepository $statsRepository, string $insee): JsonResponse { $stats = $statsRepository->findOneBy(['zone' => $insee]); if (!$stats) { return new JsonResponse(['error' => 'Zone non trouvée'], Response::HTTP_NOT_FOUND); } $data = [ 'id' => $stats->getId(), 'name' => $stats->getName(), 'zone' => $stats->getZone(), 'completion_percent' => $stats->getCompletionPercent(), 'places_count' => $stats->getPlacesCount(), 'avec_horaires' => $stats->getAvecHoraires(), 'avec_adresse' => $stats->getAvecAdresse(), 'avec_site' => $stats->getAvecSite(), 'avec_accessibilite' => $stats->getAvecAccessibilite(), 'avec_note' => $stats->getAvecNote(), 'population' => $stats->getPopulation(), 'siren' => $stats->getSiren(), 'codeEpci' => $stats->getCodeEpci(), 'codesPostaux' => $stats->getCodesPostaux(), 'date_created' => $stats->getDateCreated() ? $stats->getDateCreated()->format('c') : null, 'date_modified' => $stats->getDateModified() ? $stats->getDateModified()->format('c') : null, ]; return new JsonResponse($data, Response::HTTP_OK); } #[Route('/api/v1/stats/{insee}/places', name: 'api_stats_places_by_insee', methods: ['GET'])] public function statsPlacesByInsee(StatsRepository $statsRepository, string $insee): JsonResponse { $stats = $statsRepository->findOneBy(['zone' => $insee]); if (!$stats) { return new JsonResponse(['error' => 'Zone non trouvée'], Response::HTTP_NOT_FOUND); } $features = []; foreach ($stats->getPlaces() as $place) { $lat = $place->getLat(); $lon = $place->getLon(); $feature = [ 'type' => 'Feature', 'geometry' => ($lat && $lon) ? [ 'type' => 'Point', 'coordinates' => [$lon, $lat], ] : null, 'properties' => [ 'id' => $place->getId(), 'name' => $place->getName(), 'main_tag' => $place->getMainTag(), 'osmId' => $place->getOsmId(), 'email' => $place->getEmail(), 'note' => $place->getNote(), 'zip_code' => $place->getZipCode(), 'siret' => $place->getSiret(), 'has_opening_hours' => $place->hasOpeningHours(), 'has_address' => $place->hasAddress(), 'has_website' => $place->hasWebsite(), 'has_wheelchair' => $place->hasWheelchair(), 'has_note' => $place->hasNote(), 'completion_percent' => $place->getCompletionPercentage(), ], ]; $features[] = $feature; } $geojson = [ 'type' => 'FeatureCollection', 'features' => $features, 'meta' => [ 'generated_at' => (new \DateTime())->format('c'), 'source' => 'https://osm-commerces.cipherbliss.com/api/v1/stats/by_insee/' . $insee . '/places' ] ]; return new JsonResponse($geojson, Response::HTTP_OK, [ 'Content-Type' => 'application/geo+json' ]); } #[Route('/api/v1/stats/by_insee/{insee}/places.csv', name: 'api_stats_places_csv_by_insee', methods: ['GET'])] public function statsPlacesCsvByInsee(StatsRepository $statsRepository, string $insee): StreamedResponse { $stats = $statsRepository->findOneBy(['zone' => $insee]); if (!$stats) { $response = new StreamedResponse(); $response->setCallback(function() { echo 'error\nZone non trouvée'; }); $response->headers->set('Content-Type', 'text/csv'); $response->setStatusCode(Response::HTTP_NOT_FOUND); return $response; } $response = new StreamedResponse(function() use ($stats) { $handle = fopen('php://output', 'w'); // En-têtes CSV fputcsv($handle, [ 'id', 'name', 'main_tag', 'osmId', 'email', 'note', 'zip_code', 'siret', 'lat', 'lon', 'has_opening_hours', 'has_address', 'has_website', 'has_wheelchair', 'has_note' ]); foreach ($stats->getPlaces() as $place) { fputcsv($handle, [ $place->getId(), $place->getName(), $place->getMainTag(), $place->getOsmId(), $place->getEmail(), $place->getNote(), $place->getZipCode(), $place->getSiret(), $place->getLat(), $place->getLon(), $place->hasOpeningHours(), $place->hasAddress(), $place->hasWebsite(), $place->hasWheelchair(), $place->hasNote(), $place->getCompletionPercentage(), ]); } fclose($handle); }); $response->headers->set('Content-Type', 'text/csv'); $response->headers->set('Content-Disposition', 'attachment; filename="places_'.$insee.'.csv"'); return $response; } #[Route('/api/v1/stats/export', name: 'api_stats_export', methods: ['GET'])] public function statsExport( StatsRepository $statsRepository, \Symfony\Component\HttpFoundation\Request $request ): JsonResponse { // Récupérer les paramètres de requête $zone = $request->query->get('zone'); $pretty = $request->query->getBoolean('pretty', false); $includeFollowups = $request->query->getBoolean('include_followups', true); $includePlaces = $request->query->getBoolean('include_places', false); try { // Construire la requête $qb = $statsRepository->createQueryBuilder('s'); if ($zone) { $qb->where('s.zone = :zone') ->setParameter('zone', $zone); } $stats = $qb->getQuery()->getResult(); if (empty($stats)) { return new JsonResponse([ 'error' => 'Aucun objet Stats trouvé', 'message' => $zone ? "Aucune zone trouvée pour le code INSEE: $zone" : 'Aucune donnée disponible' ], Response::HTTP_NOT_FOUND); } // Préparer les données pour l'export $exportData = []; foreach ($stats as $stat) { $statData = [ 'id' => $stat->getId(), 'zone' => $stat->getZone(), 'name' => $stat->getName(), 'dateCreated' => $stat->getDateCreated() ? $stat->getDateCreated()->format('Y-m-d H:i:s') : null, 'dateModified' => $stat->getDateModified() ? $stat->getDateModified()->format('Y-m-d H:i:s') : null, 'population' => $stat->getPopulation(), 'budgetAnnuel' => $stat->getBudgetAnnuel(), 'siren' => $stat->getSiren(), 'codeEpci' => $stat->getCodeEpci(), 'codesPostaux' => $stat->getCodesPostaux(), 'decomptes' => [ 'placesCount' => $stat->getPlacesCount(), 'avecHoraires' => $stat->getAvecHoraires(), 'avecAdresse' => $stat->getAvecAdresse(), 'avecSite' => $stat->getAvecSite(), 'avecAccessibilite' => $stat->getAvecAccessibilite(), 'avecNote' => $stat->getAvecNote(), 'completionPercent' => $stat->getCompletionPercent(), 'placesCountReal' => $stat->getPlaces()->count(), ], ]; // Ajouter les followups si demandé if ($includeFollowups) { $statData['followups'] = []; foreach ($stat->getCityFollowUps() as $followup) { $statData['followups'][] = [ 'name' => $followup->getName(), 'measure' => $followup->getMeasure(), 'date' => $followup->getDate()->format('Y-m-d H:i:s') ]; } } // Ajouter les lieux si demandé if ($includePlaces) { $statData['places'] = []; foreach ($stat->getPlaces() as $place) { $statData['places'][] = [ 'id' => $place->getId(), 'name' => $place->getName(), 'mainTag' => $place->getMainTag(), 'osmId' => $place->getOsmId(), 'osmKind' => $place->getOsmKind(), 'email' => $place->getEmail(), 'note' => $place->getNote(), 'zipCode' => $place->getZipCode(), 'siret' => $place->getSiret(), 'lat' => $place->getLat(), 'lon' => $place->getLon(), 'hasOpeningHours' => $place->hasOpeningHours(), 'hasAddress' => $place->hasAddress(), 'hasWebsite' => $place->hasWebsite(), 'hasWheelchair' => $place->hasWheelchair(), 'hasNote' => $place->hasNote(), 'completionPercentage' => $place->getCompletionPercentage(), ]; } } $exportData[] = $statData; } // Préparer le JSON $jsonOptions = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES; if ($pretty) { $jsonOptions |= JSON_PRETTY_PRINT; } $jsonContent = json_encode($exportData, $jsonOptions); if (json_last_error() !== JSON_ERROR_NONE) { return new JsonResponse([ 'error' => 'Erreur lors de l\'encodage JSON', 'message' => json_last_error_msg() ], Response::HTTP_INTERNAL_SERVER_ERROR); } // Retourner la réponse $response = new JsonResponse($exportData, Response::HTTP_OK); $response->headers->set('Content-Type', 'application/json'); $response->headers->set('Content-Disposition', 'attachment; filename="stats_export.json"'); // Ajouter des métadonnées dans les headers $response->headers->set('X-Export-Count', count($stats)); $response->headers->set('X-Export-Generated', (new \DateTime())->format('c')); if ($zone) { $response->headers->set('X-Export-Zone', $zone); } return $response; } catch (\Exception $e) { return new JsonResponse([ 'error' => 'Erreur lors de l\'export', 'message' => $e->getMessage() ], Response::HTTP_INTERNAL_SERVER_ERROR); } } }