geometria(); if ($geometry === null) { throw new RuntimeException('La geometria salvata per questa piantina non è leggibile: ridisegna il poligono.'); } [$immagine, $estensione] = $this->immagine($geometry); return $this->archivia($mappa, $immagine, $estensione); } /** * Solo il rendering, senza toccare niente: serve all'anteprima della * pubblicazione, che non deve cambiare la piantina finché non si conferma. * * @return array{0: string, 1: string} contenuto e estensione ('jpg'|'png') */ public function immagine(array $geometry): array { $formato = config('piantine.formato') === 'png' ? 'png' : 'jpeg'; try { $response = Http::timeout((int) config('piantine.renderer_timeout')) ->post(rtrim((string) config('piantine.renderer_url'), '/') . '/render', [ 'geometry' => $geometry, 'marginRatio' => (float) config('piantine.margin_ratio'), 'maxSide' => (int) config('piantine.max_side'), 'aspect' => (float) config('piantine.aspetto'), 'strokeWidth' => (float) config('piantine.tratto'), 'strokeOpacity' => (float) config('piantine.tratto_opacita'), 'format' => $formato, 'quality' => (int) config('piantine.qualita'), ]); } catch (ConnectionException $e) { // Container fermo, nome non risolto o tempo scaduto: il messaggio // di cURL in inglese non dice niente a chi sta pubblicando. throw new RuntimeException('Il servizio che disegna le mappe non risponde (container map-renderer). Riprova fra qualche minuto; se continua, va controllato il server.', 0, $e); } if (! $response->successful()) { $dettaglio = $response->json('error') ?? ('HTTP ' . $response->status()); throw new RuntimeException('Il renderer delle mappe ha risposto con un errore: ' . $dettaglio); } $immagine = $response->body(); if (! $this->immagineValida($immagine, $formato)) { throw new RuntimeException('Il renderer delle mappe non ha restituito un\'immagine valida.'); } return [$immagine, $formato === 'png' ? 'png' : 'jpg']; } /** * Salva l'immagine come quella della piantina, rimuovendo la precedente. * * Il nome cambia a ogni rendering: nginx e Cloudflare tengono in cache i * file di /storage per giorni, e con un nome fisso il link mostrava * l'immagine vecchia anche dopo averla rigenerata. */ public function archivia(TerritorioMappa $mappa, string $immagine, string $estensione): string { $disco = Storage::disk('public'); $path = 'territori-mappe/piantina-' . $mappa->id . '-' . now()->format('YmdHis') . '.' . $estensione; if (! $disco->put($path, $immagine)) { throw new RuntimeException('Non è stato possibile salvare l\'immagine della piantina sul disco.'); } if ($mappa->image_path && $mappa->image_path !== $path) { $disco->delete($mappa->image_path); } $mappa->update([ 'image_path' => $path, 'rendered_at' => now(), ]); return $path; } /** I primi byte bastano a distinguere un'immagine da una pagina d'errore. */ private function immagineValida(string $dati, string $formato): bool { if ($dati === '') { return false; } return $formato === 'png' ? str_starts_with($dati, "\x89PNG") : str_starts_with($dati, "\xFF\xD8\xFF"); } }