From 39d7d70c8bfe485cda379715f751c830408f6edf Mon Sep 17 00:00:00 2001 From: Peter Meier Date: Wed, 13 May 2026 09:20:32 +0200 Subject: [PATCH] =?UTF-8?q?feat(windkarte):=20Leaflet-Block=20f=C3=BCr=20W?= =?UTF-8?q?indvorranggebiete=20SWT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neuer CMS-Block `wind_map` — platzierbar auf jeder Seite per Row-Content. Fetcht wind_area-Einträge + GeoJSON-Asset client-side, rendert Leaflet-Karte (CartoDB Light) mit farbcodierten Polygonen nach Planungsstatus. Klick auf Gebiet öffnet Slide-in-Panel mit Kenndaten, Gemeinden, Stellungnahme-Link und Puffer-Ringen (200/600/1000 m via Turf). Erweiterbar für 2. Entwurf 2026. Co-Authored-By: Claude Sonnet 4.6 --- package-lock.json | 166 +++++++++++++++++- package.json | 3 + src/lib/block-types.ts | 8 + src/lib/components/BlockRenderer.svelte | 4 + src/lib/components/WindAreaPanel.svelte | 126 +++++++++++++ src/lib/components/WindkarteMap.svelte | 151 ++++++++++++++++ .../components/blocks/WindkarteBlock.svelte | 95 ++++++++++ src/lib/iconify-mdi-subset.generated.json | 9 +- src/lib/windkarte.ts | 13 ++ 9 files changed, 570 insertions(+), 5 deletions(-) create mode 100644 src/lib/components/WindAreaPanel.svelte create mode 100644 src/lib/components/WindkarteMap.svelte create mode 100644 src/lib/components/blocks/WindkarteBlock.svelte create mode 100644 src/lib/windkarte.ts diff --git a/package-lock.json b/package-lock.json index 3734325..fe8259d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,11 @@ "@fontsource-variable/rubik": "^5.2.8", "@fontsource/inter": "^5.2.8", "@iconify/svelte": "^5.2.1", + "@turf/buffer": "^7.3.5", + "@types/leaflet": "^1.9.21", "blurhash": "^2.0.5", "class-variance-authority": "^0.7.1", + "leaflet": "^1.9.4", "marked": "^17.0.2", "sanitize-html": "^2.13.1", "sharp": "^0.34.5" @@ -2136,6 +2139,121 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@turf/bbox": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-7.3.5.tgz", + "integrity": "sha512-oG1ya/HtBjAIg4TimbWx+nOYPbY0bCvt82Bq8tm6sBw3qqtbOyRSfDz79Sq90TnH7DXJprJ1qnVGKNtZ6jemfw==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.5", + "@turf/meta": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-7.3.5.tgz", + "integrity": "sha512-TGls3nYtWzviKHT00XVBfHKa7Z2oIZKqiHN7R0xErGwMSAR7YhxVROhxq/iyIsWZjl1SlPwweZZIxWILQuxpZA==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.5", + "@turf/center": "7.3.5", + "@turf/helpers": "7.3.5", + "@turf/jsts": "^2.7.1", + "@turf/meta": "7.3.5", + "@turf/projection": "7.3.5", + "@types/geojson": "^7946.0.10", + "d3-geo": "1.7.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.3.5.tgz", + "integrity": "sha512-eub5/Kfdmn89ZqwCONHI7astmTDEtN5M6+JfOkgoSyhKKFhUJYNxUyH1F/vCtIP7j1K369Vs4L9TYiuGapvIKQ==", + "license": "MIT", + "dependencies": { + "@turf/bbox": "7.3.5", + "@turf/helpers": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-7.3.5.tgz", + "integrity": "sha512-qfIaHj3410QEcTpiCRnTzhq8YrUp2gWrUIPLBAEFykopNxJkq1du1VrRzvuAo36ap2UV7KppkS6wGNypbcxswQ==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-7.3.5.tgz", + "integrity": "sha512-E/NMGV5MwbjjP7AJXBtsanC3yY8N2MQ87IGdIgkB2ji5AtBpwnH4L3gEqpYN4RlCJJWbLbzO91BbKv2waUd0eg==", + "license": "MIT", + "dependencies": { + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/jsts": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@turf/jsts/-/jsts-2.7.2.tgz", + "integrity": "sha512-zAezGlwWHPyU0zxwcX2wQY3RkRpwuoBmhhNE9HY9kWhFDkCxZ3aWK5URKwa/SWKJbj9aztO+8vtdiBA28KVJFg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "dependencies": { + "jsts": "2.7.1" + } + }, + "node_modules/@turf/meta": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-7.3.5.tgz", + "integrity": "sha512-r+ohqxoyqeigFB0oFrQx/YEHIkOKqcKpCjvZkvZs7Tkv+IFco5MezAd2zd4rzK+0DfFgDP3KpJc7HqrYjvEjhg==", + "license": "MIT", + "dependencies": { + "@turf/helpers": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-7.3.5.tgz", + "integrity": "sha512-G4bejYKT0vCQZryMhEoS9aLmP7ThDg6nb3zi3wPzELiTrGNOd2YgkWVheQDGCk4hcqEIWZc9fI2alaRSSkkLVw==", + "license": "MIT", + "dependencies": { + "@turf/clone": "7.3.5", + "@turf/helpers": "7.3.5", + "@turf/meta": "7.3.5", + "@types/geojson": "^7946.0.10", + "tslib": "^2.8.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -2149,6 +2267,21 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.21", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { "version": "22.19.15", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", @@ -2570,6 +2703,21 @@ "node": ">= 8" } }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3200,6 +3348,15 @@ "dev": true, "license": "MIT" }, + "node_modules/jsts": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/jsts/-/jsts-2.7.1.tgz", + "integrity": "sha512-x2wSZHEBK20CY+Wy+BPE7MrFQHW6sIsdaGUMEqmGAio+3gFzQaBYPwLRonUfQf9Ak8pBieqj9tUofX1+WtAEIg==", + "license": "(EDL-1.0 OR EPL-1.0)", + "engines": { + "node": ">= 12" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -3210,6 +3367,12 @@ "node": ">=6" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", @@ -4369,8 +4532,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/type-fest": { "version": "4.41.0", diff --git a/package.json b/package.json index 8ef60d9..daa9c62 100644 --- a/package.json +++ b/package.json @@ -41,8 +41,11 @@ "@fontsource-variable/rubik": "^5.2.8", "@fontsource/inter": "^5.2.8", "@iconify/svelte": "^5.2.1", + "@turf/buffer": "^7.3.5", + "@types/leaflet": "^1.9.21", "blurhash": "^2.0.5", "class-variance-authority": "^0.7.1", + "leaflet": "^1.9.4", "marked": "^17.0.2", "sanitize-html": "^2.13.1", "sharp": "^0.34.5" diff --git a/src/lib/block-types.ts b/src/lib/block-types.ts index 9da5a52..36ccb12 100644 --- a/src/lib/block-types.ts +++ b/src/lib/block-types.ts @@ -421,3 +421,11 @@ export type CalendarBlockData = Omit< items?: (string | CalendarItemData)[]; layout?: BlockLayout; }; + +export interface WindMapBlockData { + _type?: "wind_map"; + _slug?: string; + name?: string; + title?: string; + subtitle?: string; +} diff --git a/src/lib/components/BlockRenderer.svelte b/src/lib/components/BlockRenderer.svelte index 9c5f742..eab6f93 100644 --- a/src/lib/components/BlockRenderer.svelte +++ b/src/lib/components/BlockRenderer.svelte @@ -18,6 +18,7 @@ import OpnFormBlock from "./blocks/OpnFormBlock.svelte"; import DeadlineBannerBlock from "./blocks/DeadlineBannerBlock.svelte"; import StrommixBlock from "./blocks/StrommixBlock.svelte"; + import WindkarteBlock from "./blocks/WindkarteBlock.svelte"; import type { Translations } from "$lib/translations"; import type { ResolvedBlock, @@ -40,6 +41,7 @@ OpnFormBlockData, DeadlineBannerBlockData, LiveStrommixBlockData, + WindMapBlockData, } from "$lib/block-types"; let { @@ -88,6 +90,8 @@ {:else if type === "live_strommix"} +{:else if type === "wind_map"} + {:else}
Unbekannter Block: {type} diff --git a/src/lib/components/WindAreaPanel.svelte b/src/lib/components/WindAreaPanel.svelte new file mode 100644 index 0000000..4ea71c8 --- /dev/null +++ b/src/lib/components/WindAreaPanel.svelte @@ -0,0 +1,126 @@ + + +{#if area} +
+ +
+
+
+ {area.name} + {#if area.status} + + {STATUS_LABEL[area.status] ?? area.status} + + {/if} +
+ {#if area.bezeichnung} +

{area.bezeichnung}

+ {/if} +
+ +
+ + +
+ +
+ {#if area.flaeche_ha != null} +
+

Fläche

+

{area.flaeche_ha.toFixed(1)} ha

+
+ {/if} + {#if area.anlagen_geplant != null} +
+

Geplante Anlagen

+

{area.anlagen_geplant}

+
+ {/if} + {#if area.hoehe_max_m != null} +
+

Max. Nabenhöhe

+

{area.hoehe_max_m} m

+
+ {/if} + {#if area.investor} +
+

Investor / Betreiber

+

{area.investor}

+
+ {/if} +
+ + + {#if area.gemeinden && area.gemeinden.length > 0} +
+

Betroffene Gemeinden

+
+ {#each area.gemeinden as g} + {g} + {/each} +
+
+ {/if} + + + {#if area.notizen} +
+

Hinweise

+

{area.notizen}

+
+ {/if} + + + {#if stellungnahmeHref(area.stellungnahme)} + + + Zur Stellungnahme-Vorlage + + {:else} +
+ + Stellungnahme-Vorlage noch nicht verknüpft +
+ {/if} +
+
+{/if} diff --git a/src/lib/components/WindkarteMap.svelte b/src/lib/components/WindkarteMap.svelte new file mode 100644 index 0000000..99e2b1c --- /dev/null +++ b/src/lib/components/WindkarteMap.svelte @@ -0,0 +1,151 @@ + + +
+ + diff --git a/src/lib/components/blocks/WindkarteBlock.svelte b/src/lib/components/blocks/WindkarteBlock.svelte new file mode 100644 index 0000000..a4435ea --- /dev/null +++ b/src/lib/components/blocks/WindkarteBlock.svelte @@ -0,0 +1,95 @@ + + +
+ {#if block.title || block.subtitle} +
+ {#if block.title} +

{block.title}

+ {/if} + {#if block.subtitle} +

{block.subtitle}

+ {/if} +
+ {/if} + +
+ {#if browser && MapComponent && mapReady} + {#await MapComponent then Map} + { selectedArea = area; }} + /> + {/await} + {:else} +
+ Karte wird geladen … +
+ {/if} + + +
+ { selectedArea = null; }} + /> +
+
+ + +
+ + + Rechtskräftig (2012) + + + + 2. Entwurf 2026 + + Geodaten: © GDI-Th, dl-de/by-2-0 +
+
diff --git a/src/lib/iconify-mdi-subset.generated.json b/src/lib/iconify-mdi-subset.generated.json index a93571e..a07fe49 100644 --- a/src/lib/iconify-mdi-subset.generated.json +++ b/src/lib/iconify-mdi-subset.generated.json @@ -66,6 +66,12 @@ "printer-outline": { "body": "" }, + "file-document-edit-outline": { + "body": "" + }, + "file-document-outline": { + "body": "" + }, "map-marker-outline": { "body": "" }, @@ -126,9 +132,6 @@ "home-outline": { "body": "" }, - "file-document-outline": { - "body": "" - }, "share-variant-outline": { "body": "" }, diff --git a/src/lib/windkarte.ts b/src/lib/windkarte.ts new file mode 100644 index 0000000..b1ee75b --- /dev/null +++ b/src/lib/windkarte.ts @@ -0,0 +1,13 @@ +export type WindArea = { + _slug: string; + name: string; + bezeichnung?: string; + status?: "rechtskraeftig" | "entwurf_2" | "entwurf_3"; + flaeche_ha?: number; + anlagen_geplant?: number; + hoehe_max_m?: number; + investor?: string; + gemeinden?: string[]; + stellungnahme?: { _slug: string; slug?: string } | string | null; + notizen?: string; +};