import React, { useState, useEffect } from 'react'; import { Wind, MapPin, Search, AlertTriangle, CheckCircle, XCircle, Loader2, Clock, CalendarDays, Navigation, Crosshair, Info, ChevronDown, ChevronUp, RefreshCw, Sunrise, Sunset, Camera, ShieldAlert, ExternalLink, Activity, Compass } from 'lucide-react'; export default function App() { const [city, setCity] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [currentWeather, setCurrentWeather] = useState(null); const [hourlyForecast, setHourlyForecast] = useState([]); const [dailyForecast, setDailyForecast] = useState([]); const [sunData, setSunData] = useState(null); const [kpIndex, setKpIndex] = useState(null); const [lastUpdated, setLastUpdated] = useState(null); const [loading, setLoading] = useState(false); const [gpsLoading, setGpsLoading] = useState(false); const [error, setError] = useState(''); const [showLegend, setShowLegend] = useState(false); const [currentLocation, setCurrentLocation] = useState({ lat: null, lon: null, name: '' }); const LIMIT_SAFE = 20; const LIMIT_WARNING = 35; const FAVORITES = [ { name: 'Grömitz', lat: 54.1493, lon: 10.9572 }, { name: 'St. Peter-Ording', lat: 54.3045, lon: 8.6366 }, { name: 'Niederkassel', lat: 50.8258, lon: 7.0097 } ]; const fetchAllData = async (lat, lon, locationName) => { setLoading(true); setError(''); try { const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}¤t=wind_speed_10m,wind_direction_10m,wind_gusts_10m&hourly=wind_speed_10m,wind_gusts_10m&daily=wind_speed_10m_max,wind_gusts_10m_max,sunrise,sunset&wind_speed_unit=kmh&timezone=Europe%2FBerlin&forecast_days=4`; const weatherResponse = await fetch(weatherUrl); const data = await weatherResponse.json(); setCity(locationName); setCurrentWeather(data.current); setLastUpdated(new Date()); setCurrentLocation({ lat, lon, name: locationName }); const todaySunrise = new Date(data.daily.sunrise[0]); const todaySunset = new Date(data.daily.sunset[0]); const morningGoldenStart = todaySunrise; const morningGoldenEnd = new Date(todaySunrise.getTime() + 60 * 60 * 1000); const eveningGoldenStart = new Date(todaySunset.getTime() - 60 * 60 * 1000); const eveningGoldenEnd = todaySunset; setSunData({ sunrise: todaySunrise, sunset: todaySunset, goldenMorning: { start: morningGoldenStart, end: morningGoldenEnd }, goldenEvening: { start: eveningGoldenStart, end: eveningGoldenEnd } }); const now = new Date(); const currentHourIndex = data.hourly.time.findIndex(timeStr => new Date(timeStr) > now) - 1; const safeIndex = currentHourIndex >= 0 ? currentHourIndex : 0; const nextHours = []; for (let i = safeIndex + 1; i <= safeIndex + 5; i++) { if (data.hourly.time[i]) { nextHours.push({ time: new Date(data.hourly.time[i]), wind: data.hourly.wind_speed_10m[i], gusts: data.hourly.wind_gusts_10m[i] }); } } setHourlyForecast(nextHours); const nextDays = []; for (let i = 1; i <= 3; i++) { if (data.daily.time[i]) { nextDays.push({ date: new Date(data.daily.time[i]), wind_max: data.daily.wind_speed_10m_max[i], gusts_max: data.daily.wind_gusts_10m_max[i] }); } } setDailyForecast(nextDays); try { const kpResponse = await fetch('https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json'); if (kpResponse.ok) { const kpData = await kpResponse.json(); const latestEntry = kpData[kpData.length - 1]; if (latestEntry && latestEntry.length > 1) { const kpVal = parseFloat(latestEntry[1]); setKpIndex(kpVal); } } } catch (kpError) { setKpIndex(null); } } catch (err) { setError('Fehler beim Abrufen der Wetterdaten.'); } finally { setLoading(false); } }; const fetchWindDataByName = async (locationName) => { setLoading(true); setError(''); try { const geoResponse = await fetch(`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(locationName)}&count=1&language=de&format=json`); const geoData = await geoResponse.json(); if (!geoData.results || geoData.results.length === 0) { throw new Error('Ort nicht gefunden.'); } const { latitude, longitude, name } = geoData.results[0]; await fetchAllData(latitude, longitude, name); } catch (err) { setError(err.message || 'Fehler bei der Ortssuche.'); setLoading(false); } }; const handleGPSLocation = () => { setGpsLoading(true); setError(''); if (!navigator.geolocation) { setError('Dein Browser unterstützt keine Standorterkennung.'); setGpsLoading(false); return; } navigator.geolocation.getCurrentPosition( async (position) => { const { latitude, longitude } = position.coords; try { const geoResponse = await fetch(`https://api.bigdatacloud.net/data/reverse-geocode-client?latitude=${latitude}&longitude=${longitude}&localityLanguage=de`); const geoData = await geoResponse.json(); const locationName = geoData.city || geoData.locality || "Mein Standort"; await fetchAllData(latitude, longitude, locationName); } catch(e) { await fetchAllData(latitude, longitude, "Mein Standort"); } setGpsLoading(false); }, (geoError) => { let errorMsg = 'GPS Fehler.'; if (geoError.code === 1) errorMsg = 'GPS Zugriff verweigert.'; if (geoError.code === 2) errorMsg = 'GPS Position nicht verfügbar.'; if (geoError.code === 3) errorMsg = 'GPS Zeitüberschreitung.'; setError(errorMsg); setGpsLoading(false); }, { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); }; const handleRefresh = () => { if (currentLocation.lat && currentLocation.lon) { fetchAllData(currentLocation.lat, currentLocation.lon, currentLocation.name); } }; useEffect(() => { fetchAllData(FAVORITES[0].lat, FAVORITES[0].lon, FAVORITES[0].name); }, []); const handleSearch = (e) => { e.preventDefault(); if (searchQuery.trim() !== '') { fetchWindDataByName(searchQuery); } }; const evaluateFlightConditions = (windSpeed, gusts) => { const maxWind = Math.max(windSpeed || 0, gusts || 0); if (maxWind <= LIMIT_SAFE) { return { status: 'green', color: 'text-green-500', bg: 'bg-green-100', border: 'border-green-200', gradient: 'from-green-400 to-emerald-600', Icon: CheckCircle, label: 'Perfekt' }; } else if (maxWind <= LIMIT_WARNING) { return { status: 'yellow', color: 'text-yellow-600', bg: 'bg-yellow-100', border: 'border-yellow-200', gradient: 'from-yellow-400 to-orange-500', Icon: AlertTriangle, label: 'Vorsicht' }; } else { return { status: 'red', color: 'text-red-600', bg: 'bg-red-100', border: 'border-red-200', gradient: 'from-red-500 to-rose-700', Icon: XCircle, label: 'Gefahr' }; } }; const getKpStatus = (kp) => { if (kp === null) return null; if (kp < 4) return { color: 'text-green-300', text: 'Normal (GPS Gut)' }; if (kp < 5) return { color: 'text-yellow-300', text: 'Erhöht (Vorsicht)' }; return { color: 'text-red-300', text: 'Warnung (GPS Risiko)' }; }; // Hilfsfunktion für Windrichtungs-Text (z.B. N, NO, O...) const getWindDirectionText = (deg) => { const directions = ['N', 'NO', 'O', 'SO', 'S', 'SW', 'W', 'NW']; const val = Math.floor((deg / 45) + 0.5); return directions[(val % 8)]; }; const formatTime = (dateObj) => { return dateObj.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }); }; return (

Drohnen Wetter

setSearchQuery(e.target.value)} placeholder="Ort suchen..." className="w-full px-4 py-3 pl-11 rounded-2xl border border-slate-200 shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white" />
{FAVORITES.map((fav, idx) => ( ))}
{error && (

{error}

)} {currentWeather && !error && (() => { const liveStatus = evaluateFlightConditions(currentWeather.wind_speed_10m, currentWeather.wind_gusts_10m); const LiveIcon = liveStatus.Icon; const kpStatus = getKpStatus(kpIndex); return (
{/* 1. HAUPT-WIDGET (Inkl. KP-Index & Windrose) */}
{city}

{liveStatus.label}

{/* Wind & Böen Grid */}
Wind
{currentWeather.wind_speed_10m} km/h
Böen (Max)
{currentWeather.wind_gusts_10m || '--'} km/h
{/* Untere Leiste des Widgets: Windrose & KP-Index */}
{/* Visuelle Windrose */}
N
Windrichtung {getWindDirectionText(currentWeather.wind_direction_10m)} ({currentWeather.wind_direction_10m}°)
{/* KP-Index Integration */}
KP-Index {kpIndex !== null ? kpStatus.text : 'Lädt...'}
= 5 ? 'text-red-400' : 'text-green-300'} />
{/* Aktualisieren Info */}

Stand: {lastUpdated ? lastUpdated.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit', second: '2-digit' }) : '--:--'} Uhr

{/* STÜNDLICHE VORHERSAGE */}

Nächste Stunden

{hourlyForecast.map((hour, idx) => { const status = evaluateFlightConditions(hour.wind, hour.gusts); const Icon = status.Icon; return (
{hour.time.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}

{Math.max(hour.wind, hour.gusts || 0).toFixed(0)}

km/h

); })}
{/* TAGES VORHERSAGE */}

Nächste 3 Tage (Böen-Max.)

{dailyForecast.map((day, idx) => { const status = evaluateFlightConditions(day.wind_max, day.gusts_max); const Icon = status.Icon; const dayName = idx === 0 ? "Morgen" : day.date.toLocaleDateString('de-DE', { weekday: 'long' }); return (

{dayName}

{day.date.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })}

{Math.max(day.wind_max, day.gusts_max).toFixed(0)} km/h

{status.label}

); })}
{/* SONNE & GOLDEN HOUR */} {sunData && (

Sonne & Golden Hour (Heute)

Aufgang

{formatTime(sunData.sunrise)}

Golden Hour {formatTime(sunData.goldenMorning.start)} - {formatTime(sunData.goldenMorning.end)}
Untergang

{formatTime(sunData.sunset)}

Golden Hour {formatTime(sunData.goldenEvening.start)} - {formatTime(sunData.goldenEvening.end)}
)} {/* DIPUL FLUGZONEN CHECK */}

Darf ich hier fliegen?

Prüfe offizielle Flugverbotszonen.

dipul
{/* LEGENDE */}
{showLegend && (

Grün: 0 - 20 km/h

Optimales Flugwetter. Sicher für alle gängigen Kameradrohnen (z.B. DJI Mini).

Gelb: 21 - 35 km/h

Flug möglich, aber erhöhter Akkuverbrauch. Gegenlenken erforderlich. Warnungen beachten.

Rot: über 35 km/h

Nicht fliegen! Starke Abdriftgefahr bei Böen.

Hinweis: Es wird immer der höhere Wert (Wind oder Böe) zur Bewertung herangezogen.

)}
); })()}
); }