getClientOriginalExtension()); $path = $file->getPathname(); $geojson = match ($ext) { 'geojson' => self::parseGeoJson($path), 'kml' => self::kmlToGeoJson($path), 'shp' => self::shapefileToGeoJson($path), 'zip' => self::handleZip($path), default => null, }; if (!$geojson) return null; return self::postProcess($geojson); } /* ======================= POST PROCESADO PRO ======================= */ private static function postProcess(array $geojson): array { $features = []; foreach ($geojson['features'] ?? [] as $feature) { if (!isset($feature['geometry'])) continue; $geometry = self::cleanGeometry($feature['geometry']); if (!$geometry) continue; $features[] = [ 'type' => 'Feature', 'geometry' => $geometry, 'properties' => self::normalizeProperties($feature['properties'] ?? []) ]; } return [ 'type' => 'FeatureCollection', 'features' => $features, 'bbox' => self::calculateBBox($features), 'centroid' => self::calculateCentroid($features) ]; } /* ======================= NORMALIZACIÓN ======================= */ private static function normalizeProperties(array $props): array { return array_merge([ 'name' => '', 'description' => '', ], $props); } /* ======================= GEOMETRY CLEAN ======================= */ private static function cleanGeometry(array $geom): ?array { if (!isset($geom['type'], $geom['coordinates'])) return null; if ($geom['type'] === 'Polygon') { $geom['coordinates'] = array_map(function ($ring) { if ($ring[0] !== end($ring)) { $ring[] = $ring[0]; } return $ring; }, $geom['coordinates']); } return $geom; } /* ======================= BBOX ======================= */ private static function calculateBBox(array $features): ?array { $coords = []; foreach ($features as $f) { $coords = array_merge($coords, self::flattenCoords($f['geometry']['coordinates'])); } if (empty($coords)) return null; $lons = array_column($coords, 0); $lats = array_column($coords, 1); return [ min($lons), min($lats), max($lons), max($lats) ]; } /* ======================= CENTROIDE SIMPLE ======================= */ private static function calculateCentroid(array $features): ?array { $coords = []; foreach ($features as $f) { $coords = array_merge($coords, self::flattenCoords($f['geometry']['coordinates'])); } if (empty($coords)) return null; $x = array_sum(array_column($coords, 0)) / count($coords); $y = array_sum(array_column($coords, 1)) / count($coords); return [$x, $y]; } private static function flattenCoords($coords): array { $result = []; $iterator = function ($c) use (&$result, &$iterator) { if (!is_array($c)) return; if (isset($c[0]) && isset($c[1]) && is_numeric($c[0])) { $result[] = [$c[0], $c[1]]; return; } foreach ($c as $item) { $iterator($item); } }; $iterator($coords); return $result; } /* ======================= GEOJSON ======================= */ private static function parseGeoJson($path): ?array { $data = json_decode(file_get_contents($path), true); return json_last_error() === JSON_ERROR_NONE ? $data : null; } /* ======================= KML (MEJORADO) ======================= */ private static function kmlToGeoJson($path): ?array { libxml_use_internal_errors(true); $xml = simplexml_load_file($path); if (!$xml) return null; $xml->registerXPathNamespace('kml', 'http://www.opengis.net/kml/2.2'); $placemarks = $xml->xpath('//kml:Placemark'); $features = []; foreach ($placemarks as $pm) { $geom = self::parseKmlGeometry($pm); if (!$geom) continue; $features[] = [ 'type' => 'Feature', 'geometry' => $geom, 'properties' => [ 'name' => (string)$pm->name, 'description' => (string)$pm->description ] ]; } return ['type' => 'FeatureCollection', 'features' => $features]; } private static function parseKmlGeometry($pm): ?array { if (isset($pm->MultiGeometry)) { $geoms = []; foreach ($pm->MultiGeometry->children() as $g) { $parsed = self::parseKmlGeometry($g); if ($parsed) $geoms[] = $parsed; } return [ 'type' => 'GeometryCollection', 'geometries' => $geoms ]; } if (isset($pm->Point)) { return [ 'type' => 'Point', 'coordinates' => self::parseKmlCoords((string)$pm->Point->coordinates)[0] ]; } if (isset($pm->LineString)) { return [ 'type' => 'LineString', 'coordinates' => self::parseKmlCoords((string)$pm->LineString->coordinates) ]; } if (isset($pm->Polygon)) { return [ 'type' => 'Polygon', 'coordinates' => [ self::parseKmlCoords( (string)$pm->Polygon->outerBoundaryIs->LinearRing->coordinates ) ] ]; } return null; } private static function parseKmlCoords(string $text): array { $coords = []; foreach (preg_split('/\s+/', trim($text)) as $pair) { $p = explode(',', $pair); if (count($p) >= 2) { $coords[] = [(float)$p[0], (float)$p[1]]; } } return $coords; } /* ======================= SHP REAL ======================= */ private static function shapefileToGeoJson($path): ?array { try { $reader = new ShapefileReader($path); $features = []; while ($record = $reader->fetchRecord()) { if ($record->isDeleted()) continue; $geom = json_decode($record->getGeometry()->toGeoJSON(), true); if (!$geom) continue; $features[] = [ 'type' => 'Feature', 'geometry' => $geom, 'properties' => $record->getDataArray() ]; } return ['type' => 'FeatureCollection', 'features' => $features]; } catch (\Exception $e) { Log::error($e->getMessage()); return null; } } /* ======================= ZIP LIMPIO ======================= */ private static function handleZip($zipPath): ?array { $zip = new \ZipArchive(); if ($zip->open($zipPath) !== true) return null; $dir = sys_get_temp_dir() . '/geo_' . uniqid(); mkdir($dir); $zip->extractTo($dir); $zip->close(); $result = null; foreach (scandir($dir) as $file) { $full = $dir . '/' . $file; $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if ($ext === 'shp') { $result = self::shapefileToGeoJson($full); break; } if ($ext === 'kml') { $result = self::kmlToGeoJson($full); break; } } self::deleteDir($dir); return $result; } private static function deleteDir($dir) { foreach (glob("$dir/*") as $file) { is_dir($file) ? self::deleteDir($file) : unlink($file); } rmdir($dir); } }