addressesDb = \AddressesDatabase::getInstance(); LogService::info('[AddressService] Connexion à la base d\'adresses réussie'); } catch (\Exception $e) { // Si la connexion échoue, on continue sans la base d'adresses LogService::error('[AddressService] Connexion à la base d\'adresses impossible', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); $this->addressesDb = null; } $this->mainDb = \Database::getInstance(); $this->buildingService = new BuildingService(); } /** * Vérifie si la connexion à la base d'adresses est active * * @return bool */ public function isConnected(): bool { return $this->addressesDb !== null; } /** * Détermine le département de l'entité courante * * @param int|null $entityId ID de l'entité * @return string|null Code département (ex: "22", "23") */ private function getDepartmentForEntity(?int $entityId = null): ?string { if (!$entityId) { $entityId = $_SESSION['entity_id'] ?? null; } if (!$entityId) { return null; } try { $query = "SELECT departement FROM entites WHERE id = :entity_id"; $stmt = $this->mainDb->prepare($query); $stmt->execute(['entity_id' => $entityId]); $result = $stmt->fetch(); return $result ? $result['departement'] : null; } catch (\Exception $e) { return null; } } /** * Récupère toutes les adresses contenues dans un polygone défini par des coordonnées * Gère automatiquement les secteurs multi-départements * * @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...] * @param int|null $entityId ID de l'entité (pour déterminer le département principal) * @return array Array des adresses trouvées */ public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array { // Si pas de connexion à la base d'adresses, retourner un tableau vide if (!$this->addressesDb) { LogService::error('[AddressService] Pas de connexion à la base d\'adresses externe', [ 'entity_id' => $entityId ]); return []; } LogService::info('[AddressService] Début recherche adresses', [ 'entity_id' => $entityId, 'nb_coordinates' => count($coordinates) ]); if (count($coordinates) < 3) { throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points"); } // D'abord, déterminer tous les départements touchés par ce secteur $boundaryService = new DepartmentBoundaryService(); $departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates); if (empty($departmentsTouched)) { // Si aucun département n'est trouvé par analyse spatiale, // chercher d'abord dans le département de l'entité et ses limitrophes $entityDept = $this->getDepartmentForEntity($entityId); LogService::info('[AddressService] Département de l\'entité', [ 'departement' => $entityDept ]); if (!$entityDept) { LogService::error('[AddressService] Impossible de déterminer le département de l\'entité', [ 'entity_id' => $entityId ]); throw new RuntimeException("Impossible de déterminer le département"); } // Obtenir les départements prioritaires (entité + limitrophes) $priorityDepts = $boundaryService->getPriorityDepartments($entityDept); // Log pour debug LogService::warning('[AddressService] Aucun département trouvé par analyse spatiale', [ 'departements_prioritaires' => implode(', ', $priorityDepts) ]); // Utiliser les départements prioritaires pour la recherche $departmentsTouched = []; foreach ($priorityDepts as $deptCode) { $departmentsTouched[] = ['code_dept' => $deptCode]; } } // Créer le polygone SQL à partir des coordonnées $polygonPoints = []; foreach ($coordinates as $coord) { if (!isset($coord[0]) || !isset($coord[1])) { throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude"); } $polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude } // Fermer le polygone $polygonPoints[] = $polygonPoints[0]; $polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))'; // Collecter les adresses de tous les départements touchés $allAddresses = []; foreach ($departmentsTouched as $dept) { $deptCode = $dept['code_dept']; $tableName = "cp" . $deptCode; try { // Requête pour récupérer les adresses dans le polygone pour ce département $sql = "SELECT id, numero, rue as voie, cp as code_postal, ville as commune, gps_lat as latitude, gps_lng as longitude, x, y, code_insee, nom_ld, ville_acheminement, rue_afnor, source, certification, :dept_code as departement FROM `$tableName` WHERE ST_Contains( ST_GeomFromText(:polygon, 4326), POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))) ) AND gps_lat != '' AND gps_lng != ''"; $stmt = $this->addressesDb->prepare($sql); $stmt->execute([ 'polygon' => $polygonString, 'dept_code' => $deptCode ]); $addresses = $stmt->fetchAll(); // Ajouter les adresses à la collection globale foreach ($addresses as $address) { $allAddresses[] = $address; } // Log pour debug LogService::info('[AddressService] Recherche dans table', [ 'table' => $tableName, 'departement' => $deptCode, 'nb_adresses' => count($addresses) ]); } catch (PDOException $e) { // Log l'erreur mais continue avec les autres départements LogService::error('[AddressService] Erreur SQL', [ 'table' => $tableName, 'departement' => $deptCode, 'error' => $e->getMessage(), 'code' => $e->getCode() ]); } } LogService::info('[AddressService] Fin recherche adresses', [ 'total_adresses' => count($allAddresses) ]); return $allAddresses; } /** * Enrichit les adresses avec les données bâtiments depuis la base 'batiments' * * Pour chaque adresse trouvée, cette méthode cherche si un bâtiment existe * et ajoute les métadonnées (nb_log, residence, fk_habitat, etc.) * * @param array $addresses Liste d'adresses depuis getAddressesInPolygon() * @param int|null $entityId ID de l'entité (pour logs) * @return array Adresses enrichies avec données bâtiment */ public function enrichAddressesWithBuildings(array $addresses, ?int $entityId = null): array { if (empty($addresses)) { return []; } LogService::info('[AddressService] Début enrichissement avec bâtiments', [ 'entity_id' => $entityId, 'nb_addresses' => count($addresses) ]); try { $enrichedAddresses = $this->buildingService->enrichAddresses($addresses); // Compter les immeubles vs maisons $nbImmeubles = 0; $nbMaisons = 0; foreach ($enrichedAddresses as $addr) { if (isset($addr['fk_habitat']) && $addr['fk_habitat'] == 2) { $nbImmeubles++; } else { $nbMaisons++; } } LogService::info('[AddressService] Fin enrichissement avec bâtiments', [ 'total_adresses' => count($enrichedAddresses), 'nb_immeubles' => $nbImmeubles, 'nb_maisons' => $nbMaisons ]); return $enrichedAddresses; } catch (\Exception $e) { LogService::error('[AddressService] Erreur lors de l\'enrichissement', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); // En cas d'erreur, retourner les adresses sans enrichissement return $addresses; } } /** * Récupère les adresses dans un rayon autour d'un point * * @param float $latitude Latitude du centre * @param float $longitude Longitude du centre * @param float $radiusMeters Rayon en mètres * @param int|null $entityId ID de l'entité (pour déterminer le département) * @return array Array des adresses trouvées */ public function getAddressesInRadius(float $latitude, float $longitude, float $radiusMeters, ?int $entityId = null): array { // Déterminer le département $dept = $this->getDepartmentForEntity($entityId); if (!$dept) { throw new RuntimeException("Impossible de déterminer le département de l'entité"); } // Nom de la table selon le département $tableName = "cp" . $dept; try { // Utiliser ST_Distance_Sphere pour calculer la distance en mètres $sql = "SELECT id, numero, rue as voie, cp as code_postal, ville as commune, gps_lat as latitude, gps_lng as longitude, ST_Distance_Sphere( POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))), ST_GeomFromText(:point, 4326) ) as distance FROM `$tableName` WHERE ST_Distance_Sphere( POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))), ST_GeomFromText(:point, 4326) ) <= :radius AND gps_lat != '' AND gps_lng != '' ORDER BY distance"; $point = "POINT($longitude $latitude)"; $stmt = $this->addressesDb->prepare($sql); $stmt->execute([ 'point' => $point, 'radius' => $radiusMeters ]); return $stmt->fetchAll(); } catch (PDOException $e) { throw new RuntimeException("Erreur lors de la récupération des adresses dans la table $tableName : " . $e->getMessage()); } } /** * Compte le nombre d'adresses dans un polygone * Gère automatiquement les secteurs multi-départements * * @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...] * @param int|null $entityId ID de l'entité (pour déterminer le département principal) * @return int Nombre d'adresses */ public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int { // Si pas de connexion à la base d'adresses, retourner 0 if (!$this->addressesDb) { return 0; } if (count($coordinates) < 3) { throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points"); } // D'abord, déterminer tous les départements touchés par ce secteur $boundaryService = new DepartmentBoundaryService(); $departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates); if (empty($departmentsTouched)) { // Si aucun département n'est trouvé, utiliser le département de l'entité $dept = $this->getDepartmentForEntity($entityId); if (!$dept) { throw new RuntimeException("Impossible de déterminer le département"); } $departmentsTouched = [['code_dept' => $dept]]; } // Créer le polygone SQL à partir des coordonnées $polygonPoints = []; foreach ($coordinates as $coord) { if (!isset($coord[0]) || !isset($coord[1])) { throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude"); } $polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude } // Fermer le polygone $polygonPoints[] = $polygonPoints[0]; $polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))'; // Compter les adresses dans tous les départements touchés $totalCount = 0; foreach ($departmentsTouched as $dept) { $deptCode = $dept['code_dept']; $tableName = "cp" . $deptCode; try { $sql = "SELECT COUNT(*) as count FROM `$tableName` WHERE ST_Contains( ST_GeomFromText(:polygon, 4326), POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))) ) AND gps_lat != '' AND gps_lng != ''"; $stmt = $this->addressesDb->prepare($sql); $stmt->execute(['polygon' => $polygonString]); $result = $stmt->fetch(); $deptCount = (int)$result['count']; $totalCount += $deptCount; } catch (PDOException $e) { // Log l'erreur mais continue avec les autres départements error_log("Erreur de comptage pour le département $deptCode : " . $e->getMessage()); } } return $totalCount; } }