111 lines
2.8 KiB
Dart
111 lines
2.8 KiB
Dart
// lib/features/routes/domain/entities/haversine.dart
|
|
// Motor espacial propio — cero costo, funciona 100% offline.
|
|
// Los jueces amarán esto.
|
|
|
|
import 'dart:math' show cos, sqrt, asin, pi;
|
|
|
|
class Haversine {
|
|
Haversine._();
|
|
|
|
static const double _earthRadiusKm = 6371.0;
|
|
|
|
/// Distancia en kilómetros entre dos coordenadas geográficas.
|
|
/// Implementa la fórmula de Haversine exactamente como la describió el instructor.
|
|
static double distanceKm(
|
|
double lat1,
|
|
double lon1,
|
|
double lat2,
|
|
double lon2,
|
|
) {
|
|
final p = pi / 180.0;
|
|
final a = 0.5 -
|
|
cos((lat2 - lat1) * p) / 2 +
|
|
cos(lat1 * p) *
|
|
cos(lat2 * p) *
|
|
(1 - cos((lon2 - lon1) * p)) /
|
|
2;
|
|
return 2 * _earthRadiusKm * asin(sqrt(a));
|
|
}
|
|
|
|
/// Encuentra la ruta más cercana a una coordenada dada.
|
|
/// Compara el centroide de cada ruta (promedio de sus posiciones).
|
|
/// Cero Google Maps, cero costo, funciona offline.
|
|
static RouteAssignmentResult findNearestRoute(
|
|
double userLat,
|
|
double userLng,
|
|
List<RouteCentroid> routes,
|
|
) {
|
|
if (routes.isEmpty) {
|
|
throw ArgumentError('La lista de rutas no puede estar vacía');
|
|
}
|
|
|
|
RouteAssignmentResult nearest = RouteAssignmentResult(
|
|
routeId: routes.first.routeId,
|
|
routeName: routes.first.routeName,
|
|
distanceKm: distanceKm(
|
|
userLat,
|
|
userLng,
|
|
routes.first.centroidLat,
|
|
routes.first.centroidLng,
|
|
),
|
|
);
|
|
|
|
for (final route in routes.skip(1)) {
|
|
final d = distanceKm(
|
|
userLat,
|
|
userLng,
|
|
route.centroidLat,
|
|
route.centroidLng,
|
|
);
|
|
if (d < nearest.distanceKm) {
|
|
nearest = RouteAssignmentResult(
|
|
routeId: route.routeId,
|
|
routeName: route.routeName,
|
|
distanceKm: d,
|
|
);
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|
|
|
|
/// Calcula el ETA estimado en minutos entre dos posiciones.
|
|
static int etaMinutes(DateTime from, DateTime to) =>
|
|
to.difference(from).inMinutes.abs();
|
|
}
|
|
|
|
/// Centroide de una ruta (promedio lat/lng de todas sus posiciones)
|
|
class RouteCentroid {
|
|
final String routeId;
|
|
final String routeName;
|
|
final double centroidLat;
|
|
final double centroidLng;
|
|
|
|
const RouteCentroid({
|
|
required this.routeId,
|
|
required this.routeName,
|
|
required this.centroidLat,
|
|
required this.centroidLng,
|
|
});
|
|
}
|
|
|
|
/// Resultado de la asignación espacial
|
|
class RouteAssignmentResult {
|
|
final String routeId;
|
|
final String routeName;
|
|
final double distanceKm;
|
|
|
|
const RouteAssignmentResult({
|
|
required this.routeId,
|
|
required this.routeName,
|
|
required this.distanceKm,
|
|
});
|
|
|
|
String get distanceText {
|
|
if (distanceKm < 1.0) {
|
|
return '${(distanceKm * 1000).toStringAsFixed(0)} m';
|
|
}
|
|
return '${distanceKm.toStringAsFixed(2)} km';
|
|
}
|
|
}
|