Aprendé cómo importar archivos shapefile a una base de datos PostgreSQL con PostGIS usando Laravel. Te mostramos cómo validar los archivos, generar registros relacionados y cargar geometrías usando un servicio Laravel reutilizable, sin depender de formularios ni interfaces.
En este artículo te muestro cómo podés subir archivos Shapefile (SHP) y guardarlos en una base de datos PostgreSQL con soporte PostGIS, utilizando Laravel 12 y PHP 8.4.
postgis
habilitada.shp2pgsql
disponible en el servidor (parte del paquete postgis
).geometry
y un campo para identificar el archivo que subió los datos (ej: file_id
).Un shapefile necesita al menos tres archivos para poder procesarse:
.shp
(geometría).shx
(índices).dbf
(atributos)El sistema debe verificar que estén todos presentes antes de importar.
A continuación, te dejo un servicio Laravel que:
shp2pgsql
para convertir el shapefile en SQL.file_id
.namespace App\Services;
use App\Models\File;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
class ShapeImportService
{
private array $requiredExtensions = ['shp', 'shx', 'dbf'];
/**
* Importa un shapefile completo y lo guarda en la base de datos.
*/
public function importShape(array $files, int|string $userId): File
{
$this->validateFiles($files);
$filename = $this->getShpFilename($files);
// Registrar el archivo en la base
$fileModel = File::create([
'user_id' => $userId,
'filename' => $filename,
]);
$fileId = $fileModel->id;
$dir = storage_path("app/shapes/{$fileId}");
mkdir($dir, 0777, true);
// Guardar todos los archivos en disco
foreach ($files as $file) {
$file->move($dir, $file->getClientOriginalName());
}
$shpPath = $dir . '/' . $filename;
$sqlPath = $dir . '/import.sql';
$tempTable = 'temp_shape_' . Str::uuid()->toString();
// Ejecutar shp2pgsql para convertir a SQL con geometría
$command = [
'shp2pgsql',
'-s', '4326',
'-g', 'geom',
$shpPath,
$tempTable,
];
$process = new Process($command);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
file_put_contents($sqlPath, $process->getOutput());
DB::transaction(function () use ($sqlPath, $tempTable, $fileId) {
// Crear tabla temporal
DB::unprepared(file_get_contents($sqlPath));
// Insertar en la tabla final mapeando los campos y la geometría
DB::insert("
INSERT INTO your_table_name (
file_id,
-- otros campos...
geom,
created_at,
updated_at
)
SELECT
?,
-- otros campos...
geom,
now(),
now()
FROM {$tempTable}
", [$fileId]);
// Eliminar tabla temporal
DB::unprepared("DROP TABLE IF EXISTS {$tempTable}");
});
return $fileModel;
}
/**
* Extrae el nombre del archivo .shp del array de archivos subidos.
*/
private function getShpFilename(array $files): string
{
foreach ($files as $file) {
if (str_ends_with($file->getClientOriginalName(), '.shp')) {
return $file->getClientOriginalName();
}
}
throw new \RuntimeException('No se encontró archivo .shp');
}
/**
* Verifica que estén presentes los archivos .shp, .shx y .dbf
*/
private function validateFiles(array $files): void
{
$found = collect($files)->map(fn(UploadedFile $f) => strtolower($f->getClientOriginalExtension()));
$missing = collect($this->requiredExtensions)
->diff($found);
if ($missing->isNotEmpty()) {
throw new \RuntimeException('Faltan archivos del shapefile: ' . $missing->implode(', '));
}
}
}
Me dedico a crear soluciones web eficientes y a compartir mi conocimiento con la comunidad de desarrolladores.