feat(windkarte): add per-area geo export + dynamic favicon
Deploy / verify (push) Successful in 1m1s
Deploy / deploy (push) Successful in 1m8s

- /api/vorranggebiete.geojson + .kml endpoints (filter via ?id=W-XX)
- Download-Buttons in WindAreaPanel pro selektiertes Gebiet
- Hover-Tooltip auf Polygon zeigt Fläche, geplante Anlagen, Höhe, Gemeinden
- Favicon + apple-touch-icon dynamisch aus data.logoUrl (CMS-Logo)
- alte windrad-PNGs in static/favicons/ entfernt
- WindkarteBlock list-fetch: _limit statt limit (RustyCMS-Param)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-20 12:14:58 +02:00
parent 61d2093d0b
commit a3f545d727
38 changed files with 220 additions and 80 deletions
+38
View File
@@ -45,6 +45,19 @@
if (!c) return null;
return `https://www.openstreetmap.org/?mlat=${c[0].toFixed(5)}&mlon=${c[1].toFixed(5)}#map=14/${c[0].toFixed(5)}/${c[1].toFixed(5)}`;
}
const exportInfo = $derived(
area
? [
`Gebiet ${area.gebiets_nr}${area.bezeichnung ? ` ${area.bezeichnung}` : ''}`,
area.flaeche_ha != null ? `Fläche: ${area.flaeche_ha.toFixed(1)} ha` : null,
area.anlagen_geplant != null ? `Geplante Anlagen: ${area.anlagen_geplant}` : null,
area.hoehe_max_m != null ? `Max. Nabenhöhe: ${area.hoehe_max_m} m` : null,
]
.filter(Boolean)
.join('\n')
: ''
);
</script>
{#if area}
@@ -155,6 +168,31 @@
</a>
{/if}
</div>
<!-- Export-Downloads -->
<div>
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-stein-500">Geodaten-Export ({area.gebiets_nr})</p>
<div class="flex gap-2">
<a
href="/api/vorranggebiete.geojson?id={encodeURIComponent(area.gebiets_nr)}"
download
title={`GeoJSON herunterladen\n${exportInfo}`}
class="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-stein-200 px-2 py-2 text-xs font-medium text-stein-600 no-underline hover:bg-stein-50 transition-colors whitespace-nowrap"
>
<Icon icon="mdi:download" class="size-3.5 shrink-0" />
GeoJSON
</a>
<a
href="/api/vorranggebiete.kml?id={encodeURIComponent(area.gebiets_nr)}"
download
title={`KML herunterladen\n${exportInfo}`}
class="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-stein-200 px-2 py-2 text-xs font-medium text-stein-600 no-underline hover:bg-stein-50 transition-colors whitespace-nowrap"
>
<Icon icon="mdi:download" class="size-3.5 shrink-0" />
KML
</a>
</div>
</div>
</div>
</div>
{/if}
+16 -2
View File
@@ -257,8 +257,22 @@
});
// Hover tooltip
const hoverLabel = area.bezeichnung ?? p.name ?? "";
layer.bindTooltip(hoverLabel, { sticky: true, className: "windkarte-tooltip" });
const nrLabel = area.gebiets_nr ?? p.name ?? "";
const title = area.bezeichnung
? `${nrLabel} ${area.bezeichnung}`
: nrLabel;
const lines: string[] = [];
if (area.flaeche_ha != null) lines.push(`Fläche: ${area.flaeche_ha.toFixed(1)} ha`);
if (area.anlagen_geplant != null) lines.push(`Geplante Anlagen: ${area.anlagen_geplant}`);
if (area.hoehe_max_m != null) lines.push(`Max. Nabenhöhe: ${area.hoehe_max_m} m`);
if (area.gemeinden && area.gemeinden.length > 0) {
lines.push(`Gemeinden: ${area.gemeinden.slice(0, 3).join(', ')}${area.gemeinden.length > 3 ? '…' : ''}`);
}
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
const tooltipHtml =
`<div class="font-semibold">${esc(title)}</div>` +
(lines.length ? `<div class="mt-0.5 text-xs opacity-90">${lines.map(esc).join('<br/>')}</div>` : '');
layer.bindTooltip(tooltipHtml, { sticky: true, className: "windkarte-tooltip" });
// Permanent label via divIcon marker at polygon center
const nr = area.gebiets_nr ?? p.name;
@@ -76,7 +76,7 @@ let areas = $state<WindArea[]>([]);
);
areas = results.filter((r): r is WindArea => r !== null);
} else {
const res = await fetch(`${CMS_BASE}/api/content/wind_area?limit=100`, {
const res = await fetch(`${CMS_BASE}/api/content/wind_area?_limit=100`, {
signal: AbortSignal.timeout(6_000),
});
if (res.ok) {