feat: Gestion des secteurs et migration v3.0.4+304
- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
263
api/scripts/import_departements_from_file.php
Normal file
263
api/scripts/import_departements_from_file.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
/**
|
||||
* Script d'import des contours départementaux depuis un fichier GeoJSON local
|
||||
*/
|
||||
|
||||
class DepartementContoursFileImporter {
|
||||
private PDO $db;
|
||||
private array $log = [];
|
||||
|
||||
public function __construct(PDO $db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la table existe
|
||||
*/
|
||||
public function tableExists(): bool {
|
||||
try {
|
||||
$sql = "SHOW TABLES LIKE 'x_departements_contours'";
|
||||
$stmt = $this->db->query($sql);
|
||||
return $stmt->rowCount() > 0;
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la table est vide
|
||||
*/
|
||||
private function isTableEmpty(): bool {
|
||||
try {
|
||||
$sql = "SELECT COUNT(*) as count FROM x_departements_contours";
|
||||
$stmt = $this->db->query($sql);
|
||||
$result = $stmt->fetch();
|
||||
return $result['count'] == 0;
|
||||
} catch (Exception $e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Importe les départements depuis un fichier GeoJSON
|
||||
*/
|
||||
public function importFromFile(string $filePath): array {
|
||||
$this->log[] = "Début de l'import depuis le fichier : $filePath";
|
||||
$this->log[] = "";
|
||||
|
||||
// Vérifier que le fichier existe
|
||||
if (!file_exists($filePath)) {
|
||||
$this->log[] = "✗ Fichier non trouvé : $filePath";
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
// Vérifier que la table existe
|
||||
if (!$this->tableExists()) {
|
||||
$this->log[] = "✗ La table x_departements_contours n'existe pas";
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
// Vérifier que la table est vide
|
||||
if (!$this->isTableEmpty()) {
|
||||
$this->log[] = "✗ La table x_departements_contours contient déjà des données";
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
// Lire le fichier GeoJSON
|
||||
$this->log[] = "Lecture du fichier GeoJSON...";
|
||||
$jsonContent = file_get_contents($filePath);
|
||||
|
||||
if ($jsonContent === false) {
|
||||
$this->log[] = "✗ Impossible de lire le fichier";
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
// Parser le JSON
|
||||
$geojson = json_decode($jsonContent, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$this->log[] = "✗ Erreur JSON : " . json_last_error_msg();
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
if (!isset($geojson['features']) || !is_array($geojson['features'])) {
|
||||
$this->log[] = "✗ Format GeoJSON invalide : pas de features";
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
$this->log[] = "✓ Fichier chargé : " . count($geojson['features']) . " départements trouvés";
|
||||
$this->log[] = "";
|
||||
|
||||
// Préparer la requête d'insertion
|
||||
$sql = "INSERT INTO x_departements_contours
|
||||
(code_dept, nom_dept, contour, bbox_min_lat, bbox_max_lat, bbox_min_lng, bbox_max_lng)
|
||||
VALUES
|
||||
(:code, :nom, ST_GeomFromText(:polygon, 4326), :min_lat, :max_lat, :min_lng, :max_lng)";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
|
||||
$success = 0;
|
||||
$errors = 0;
|
||||
|
||||
// Démarrer une transaction
|
||||
$this->db->beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($geojson['features'] as $feature) {
|
||||
// Extraire les informations
|
||||
$code = $feature['properties']['code'] ?? null;
|
||||
$nom = $feature['properties']['nom'] ?? null;
|
||||
$geometry = $feature['geometry'] ?? null;
|
||||
|
||||
if (!$code || !$nom || !$geometry) {
|
||||
$this->log[] = "✗ Données manquantes pour un département";
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convertir la géométrie en WKT
|
||||
$wktData = $this->geometryToWkt($geometry);
|
||||
|
||||
if (!$wktData) {
|
||||
$this->log[] = "✗ Conversion échouée pour $code ($nom)";
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$stmt->execute([
|
||||
'code' => $code,
|
||||
'nom' => $nom,
|
||||
'polygon' => $wktData['wkt'],
|
||||
'min_lat' => $wktData['bbox']['min_lat'],
|
||||
'max_lat' => $wktData['bbox']['max_lat'],
|
||||
'min_lng' => $wktData['bbox']['min_lng'],
|
||||
'max_lng' => $wktData['bbox']['max_lng']
|
||||
]);
|
||||
|
||||
$this->log[] = "✓ $code - $nom importé";
|
||||
$success++;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->log[] = "✗ Erreur SQL pour $code ($nom) : " . $e->getMessage();
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Valider ou annuler la transaction
|
||||
if ($success > 0) {
|
||||
$this->db->commit();
|
||||
$this->log[] = "";
|
||||
$this->log[] = "✓ Transaction validée";
|
||||
} else {
|
||||
$this->db->rollBack();
|
||||
$this->log[] = "";
|
||||
$this->log[] = "✗ Transaction annulée (aucun import réussi)";
|
||||
}
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->db->rollBack();
|
||||
$this->log[] = "";
|
||||
$this->log[] = "✗ Erreur fatale : " . $e->getMessage();
|
||||
$this->log[] = "✗ Transaction annulée";
|
||||
}
|
||||
|
||||
$this->log[] = "";
|
||||
$this->log[] = "Import terminé : $success réussis, $errors erreurs";
|
||||
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertit une géométrie GeoJSON en WKT
|
||||
*/
|
||||
private function geometryToWkt(array $geometry): ?array {
|
||||
$type = $geometry['type'] ?? null;
|
||||
$coordinates = $geometry['coordinates'] ?? null;
|
||||
|
||||
if (!$type || !$coordinates) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wkt = null;
|
||||
$allPoints = [];
|
||||
|
||||
switch ($type) {
|
||||
case 'Polygon':
|
||||
// Un seul polygone
|
||||
$ring = $coordinates[0]; // Anneau extérieur
|
||||
$points = [];
|
||||
foreach ($ring as $point) {
|
||||
$points[] = $point[0] . ' ' . $point[1];
|
||||
$allPoints[] = $point;
|
||||
}
|
||||
$wkt = 'POLYGON((' . implode(',', $points) . '))';
|
||||
break;
|
||||
|
||||
case 'MultiPolygon':
|
||||
// Plusieurs polygones
|
||||
$polygons = [];
|
||||
foreach ($coordinates as $polygon) {
|
||||
$ring = $polygon[0]; // Anneau extérieur du polygone
|
||||
$points = [];
|
||||
foreach ($ring as $point) {
|
||||
$points[] = $point[0] . ' ' . $point[1];
|
||||
$allPoints[] = $point;
|
||||
}
|
||||
$polygons[] = '((' . implode(',', $points) . '))';
|
||||
}
|
||||
$wkt = 'MULTIPOLYGON(' . implode(',', $polygons) . ')';
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$wkt || empty($allPoints)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculer la bounding box
|
||||
$lats = array_map(function($p) { return $p[1]; }, $allPoints);
|
||||
$lngs = array_map(function($p) { return $p[0]; }, $allPoints);
|
||||
|
||||
return [
|
||||
'wkt' => $wkt,
|
||||
'bbox' => [
|
||||
'min_lat' => min($lats),
|
||||
'max_lat' => max($lats),
|
||||
'min_lng' => min($lngs),
|
||||
'max_lng' => max($lngs)
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Si le script est exécuté directement
|
||||
if (php_sapi_name() === 'cli' && basename(__FILE__) === basename($_SERVER['PHP_SELF'] ?? __FILE__)) {
|
||||
require_once __DIR__ . '/../src/Config/AppConfig.php';
|
||||
require_once __DIR__ . '/../src/Core/Database.php';
|
||||
|
||||
// Chemin vers le fichier GeoJSON
|
||||
$filePath = __DIR__ . '/../docs/contour-des-departements.geojson';
|
||||
|
||||
// Vérifier les arguments
|
||||
if ($argc > 1) {
|
||||
$filePath = $argv[1];
|
||||
}
|
||||
|
||||
echo "Import des contours départementaux depuis un fichier\n";
|
||||
echo "==================================================\n\n";
|
||||
echo "Fichier : $filePath\n\n";
|
||||
|
||||
$appConfig = AppConfig::getInstance();
|
||||
Database::init($appConfig->getDatabaseConfig());
|
||||
$db = Database::getInstance();
|
||||
|
||||
$importer = new DepartementContoursFileImporter($db);
|
||||
$log = $importer->importFromFile($filePath);
|
||||
|
||||
foreach ($log as $line) {
|
||||
echo $line . "\n";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user