mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-09 17:02:46 +02:00
335 lines
No EOL
14 KiB
PHP
335 lines
No EOL
14 KiB
PHP
<?php
|
|
|
|
namespace App\Controller;
|
|
|
|
use App\Entity\Stats;
|
|
use App\Repository\StatsRepository;
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
|
use Symfony\Component\Routing\Annotation\Route;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
|
|
|
class ApiController extends AbstractController
|
|
{
|
|
#[Route('/api/v1/stats_geojson', name: 'api_stats_geojson', methods: ['GET'])]
|
|
public function statsGeojson(StatsRepository $statsRepository): JsonResponse
|
|
{
|
|
$statsList = $statsRepository->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);
|
|
}
|
|
}
|
|
}
|