database->fetch('SELECT TOP 1 NAZEV, GEO FROM SBOR ORDER BY ID'); if ($radek === null) { return null; } try { $geo = Json::decode((string) $radek->GEO, forceArrays: true); } catch (JsonException) { $geo = []; } return [ 'nazev' => (string) $radek->NAZEV, 'bodu' => is_array($geo) ? $this->pocetBodu($geo) : 0, ]; } /** * GeoJSON geometrie jediné uložené hranice sboru. */ public function getGeoJson(): ?string { $geo = $this->database->fetchField('SELECT TOP 1 GEO FROM SBOR ORDER BY ID'); if (!is_string($geo) || trim($geo) === '') { return null; } return $geo; } public function nahradZKml(string $kml): void { $nazev = $this->nazevZKml($kml); $geo = Json::encode($this->geometrieZKml($kml)); $id = $this->database->fetchField('SELECT TOP 1 ID FROM SBOR ORDER BY ID'); if ($id === null) { $this->database->query('INSERT INTO SBOR (NAZEV, GEO) VALUES (?, ?)', $nazev, $geo); return; } $this->database->query('UPDATE SBOR SET NAZEV = ?, GEO = ? WHERE ID = ?', $nazev, $geo, $id); $this->database->query('DELETE FROM SBOR WHERE ID <> ?', $id); } private function nazevZKml(string $kml): string { $xml = $this->nactiKml($kml); $nazvy = $xml->xpath('//k:Document/k:name'); if ($nazvy === false || $nazvy === []) { $nazvy = $xml->xpath('//k:Placemark/k:name'); } $nazev = trim((string) ($nazvy[0] ?? '')); if ($nazev === '') { throw new \InvalidArgumentException('V souboru KML chybí název sboru.'); } if (mb_strlen($nazev) > 50) { throw new \InvalidArgumentException('Název sboru v souboru je delší než 50 znaků.'); } return $nazev; } /** * @return array{type: string, coordinates: array} */ private function geometrieZKml(string $kml): array { $xml = $this->nactiKml($kml); $uzly = $xml->xpath('//k:Polygon/k:outerBoundaryIs/k:LinearRing/k:coordinates'); if ($uzly === false || $uzly === []) { throw new \InvalidArgumentException('V souboru KML není polygon s hranicí.'); } $prstence = []; foreach ($uzly as $uzel) { $prstence[] = $this->prstenec((string) $uzel); } if (count($prstence) === 1) { return [ 'type' => 'Polygon', 'coordinates' => [$prstence[0]], ]; } return [ 'type' => 'MultiPolygon', 'coordinates' => array_map( static fn(array $prstenec): array => [$prstenec], $prstence, ), ]; } private function nactiKml(string $kml): SimpleXMLElement { $kml = trim($kml); if ($kml === '' || !str_contains($kml, 'registerXPathNamespace('k', 'http://www.opengis.net/kml/2.2'); return $xml; } /** * @return list */ private function prstenec(string $text): array { $kruh = []; foreach (preg_split('/\s+/', trim($text)) ?: [] as $token) { if ($token === '') { continue; } $casti = explode(',', $token); if (count($casti) < 2 || !is_numeric($casti[0]) || !is_numeric($casti[1])) { throw new \InvalidArgumentException('Bod hranice v KML nemá souřadnice.'); } $lng = round((float) $casti[0], 7); $lat = round((float) $casti[1], 7); if ($lng < 12.0 || $lng > 19.0 || $lat < 48.0 || $lat > 52.0) { throw new \InvalidArgumentException('Bod hranice sboru je mimo oblast obvodů.'); } $kruh[] = [$lng, $lat]; } if (count($kruh) > 1 && $kruh[0] === $kruh[count($kruh) - 1]) { array_pop($kruh); } if (count($kruh) < 3) { throw new \InvalidArgumentException('Hranice sboru musí mít aspoň tři body.'); } if (count($kruh) > 50000) { throw new \InvalidArgumentException('Hranice sboru má příliš mnoho bodů.'); } $kruh[] = $kruh[0]; return $kruh; } /** * @param array $geo */ private function pocetBodu(array $geo): int { $prstence = []; if (($geo['type'] ?? '') === 'Polygon') { $prstence[] = $geo['coordinates'][0] ?? []; } elseif (($geo['type'] ?? '') === 'MultiPolygon') { foreach ($geo['coordinates'] ?? [] as $polygon) { $prstence[] = $polygon[0] ?? []; } } $pocet = 0; foreach ($prstence as $kruh) { if (!is_array($kruh)) { continue; } $delka = count($kruh); if ($delka > 1 && $kruh[0] === $kruh[$delka - 1]) { $delka--; } $pocet += $delka; } return $pocet; } }