import 'dart:math' as math; import 'package:geolocator/geolocator.dart'; /// Utility Klasse für erweiterte Geo-Funktionen class GeoUtils { /// Private Konstruktor GeoUtils._(); /// Erdradius in Kilometern static const double earthRadiusKm = 6371.0; /// Konvertiert Grad zu Radiant static double degreesToRadians(double degrees) { return degrees * math.pi / 180.0; } /// Konvertiert Radiant zu Grad static double radiansToDegrees(double radians) { return radians * 180.0 / math.pi; } /// Berechnet die Distanz zwischen zwei Punkten mit der Haversine-Formel /// Ergebnis in Kilometern static double calculateDistanceKm({ required double startLat, required double startLng, required double endLat, required double endLng, }) { double dLat = degreesToRadians(endLat - startLat); double dLng = degreesToRadians(endLng - startLng); double a = math.sin(dLat / 2) * math.sin(dLat / 2) + math.cos(degreesToRadians(startLat)) * math.cos(degreesToRadians(endLat)) * math.sin(dLng / 2) * math.sin(dLng / 2); double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)); return earthRadiusKm * c; } /// Berechnet einen neuen Punkt basierend auf Startpunkt, Distanz und Richtung /// [distance] in Kilometern /// [bearing] in Grad (0 = Norden, 90 = Osten) static Map calculateDestination({ required double startLat, required double startLng, required double distance, required double bearing, }) { double lat1 = degreesToRadians(startLat); double lng1 = degreesToRadians(startLng); double brng = degreesToRadians(bearing); double d = distance / earthRadiusKm; double lat2 = math.asin( math.sin(lat1) * math.cos(d) + math.cos(lat1) * math.sin(d) * math.cos(brng), ); double lng2 = lng1 + math.atan2( math.sin(brng) * math.sin(d) * math.cos(lat1), math.cos(d) - math.sin(lat1) * math.sin(lat2), ); return { 'latitude': radiansToDegrees(lat2), 'longitude': radiansToDegrees(lng2), }; } /// Prüft ob ein Punkt innerhalb eines Kreises liegt /// [radius] in Kilometern static bool isPointInCircle({ required double pointLat, required double pointLng, required double centerLat, required double centerLng, required double radius, }) { double distance = calculateDistanceKm( startLat: pointLat, startLng: pointLng, endLat: centerLat, endLng: centerLng, ); return distance <= radius; } /// Berechnet die Richtung zwischen zwei Punkten /// Ergebnis in Grad (0-360, 0 = Norden) static double calculateBearing({ required double startLat, required double startLng, required double endLat, required double endLng, }) { double lat1 = degreesToRadians(startLat); double lat2 = degreesToRadians(endLat); double dLng = degreesToRadians(endLng - startLng); double y = math.sin(dLng) * math.cos(lat2); double x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLng); double bearing = radiansToDegrees(math.atan2(y, x)); return (bearing + 360) % 360; } /// Konvertiert Geschwindigkeit von m/s zu km/h static double mpsToKmh(double mps) { return mps * 3.6; } /// Konvertiert Geschwindigkeit von km/h zu m/s static double kmhToMps(double kmh) { return kmh / 3.6; } /// Formatiert Koordinaten als String static String formatCoordinates(double lat, double lng, {int precision = 6}) { return '${lat.toStringAsFixed(precision)}, ${lng.toStringAsFixed(precision)}'; } /// Formatiert Koordinaten als DMS (Degrees, Minutes, Seconds) static String formatCoordinatesDMS(double lat, double lng) { String latDMS = _decimalToDMS(lat.abs(), lat >= 0 ? 'N' : 'S'); String lngDMS = _decimalToDMS(lng.abs(), lng >= 0 ? 'E' : 'W'); return '$latDMS, $lngDMS'; } /// Hilfsfunktion für DMS Konvertierung static String _decimalToDMS(double decimal, String direction) { int degrees = decimal.floor(); double minutesDecimal = (decimal - degrees) * 60; int minutes = minutesDecimal.floor(); double seconds = (minutesDecimal - minutes) * 60; return '$degrees°$minutes\'${seconds.toStringAsFixed(2)}"$direction'; } /// Berechnet die Mittelpunkt zwischen mehreren Positionen static Map? calculateCentroid(List positions) { if (positions.isEmpty) return null; double x = 0, y = 0, z = 0; for (Position position in positions) { double lat = degreesToRadians(position.latitude); double lng = degreesToRadians(position.longitude); x += math.cos(lat) * math.cos(lng); y += math.cos(lat) * math.sin(lng); z += math.sin(lat); } int count = positions.length; x /= count; y /= count; z /= count; double centralLng = math.atan2(y, x); double centralSqrt = math.sqrt(x * x + y * y); double centralLat = math.atan2(z, centralSqrt); return { 'latitude': radiansToDegrees(centralLat), 'longitude': radiansToDegrees(centralLng), }; } /// Berechnet das Bounding Box für eine Liste von Positionen static Map? calculateBoundingBox(List positions) { if (positions.isEmpty) return null; double minLat = positions.first.latitude; double maxLat = positions.first.latitude; double minLng = positions.first.longitude; double maxLng = positions.first.longitude; for (Position position in positions) { if (position.latitude < minLat) minLat = position.latitude; if (position.latitude > maxLat) maxLat = position.latitude; if (position.longitude < minLng) minLng = position.longitude; if (position.longitude > maxLng) maxLng = position.longitude; } return { 'minLatitude': minLat, 'maxLatitude': maxLat, 'minLongitude': minLng, 'maxLongitude': maxLng, }; } /// Validiert Koordinaten static bool isValidCoordinate(double latitude, double longitude) { return latitude >= -90 && latitude <= 90 && longitude >= -180 && longitude <= 180; } /// Berechnet die Geschwindigkeit zwischen zwei Positionen mit Zeitstempel static double? calculateSpeed(Position position1, Position position2) { double distance = Geolocator.distanceBetween( position1.latitude, position1.longitude, position2.latitude, position2.longitude, ); int timeDifference = position2.timestamp .difference(position1.timestamp) .inMilliseconds; if (timeDifference <= 0) return null; // Geschwindigkeit in m/s return distance / (timeDifference / 1000); } /// Glättet GPS-Koordinaten mit einem einfachen Moving Average Filter static List smoothTrack(List positions, {int windowSize = 5}) { if (positions.length <= windowSize) return positions; List smoothed = []; for (int i = 0; i < positions.length; i++) { int start = math.max(0, i - windowSize ~/ 2); int end = math.min(positions.length - 1, i + windowSize ~/ 2); double avgLat = 0; double avgLng = 0; int count = 0; for (int j = start; j <= end; j++) { avgLat += positions[j].latitude; avgLng += positions[j].longitude; count++; } avgLat /= count; avgLng /= count; smoothed.add(Position( latitude: avgLat, longitude: avgLng, timestamp: positions[i].timestamp, accuracy: positions[i].accuracy, altitude: positions[i].altitude, altitudeAccuracy: positions[i].altitudeAccuracy, heading: positions[i].heading, headingAccuracy: positions[i].headingAccuracy, speed: positions[i].speed, speedAccuracy: positions[i].speedAccuracy, )); } return smoothed; } }