261 lines
7.8 KiB
Dart
261 lines
7.8 KiB
Dart
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<String, double> 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<String, double>? calculateCentroid(List<Position> 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<String, double>? calculateBoundingBox(List<Position> 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<Position> smoothTrack(List<Position> positions, {int windowSize = 5}) {
|
|
if (positions.length <= windowSize) return positions;
|
|
|
|
List<Position> 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;
|
|
}
|
|
} |