MotorSignal-App/lib/services/routes/route_provider.dart

147 lines
4.2 KiB
Dart

import '../map/map_provider_interface.dart';
import 'route_models.dart';
/// Result of a place/location search query.
class PlaceSearchResult {
final String name;
final String address;
final double latitude;
final double longitude;
final String? placeId;
const PlaceSearchResult({
required this.name,
this.address = '',
required this.latitude,
required this.longitude,
this.placeId,
});
AppLatLng get latLng => AppLatLng(latitude, longitude);
}
/// Result of calculating a route along ordered stops.
class RouteCalculationResult {
final double straightLineDistanceMeters;
final double? estimatedDistanceMeters;
final int? estimatedDurationSeconds;
final List<AppLatLng> geometry;
final bool isStraightLine;
final String providerName;
const RouteCalculationResult({
required this.straightLineDistanceMeters,
this.estimatedDistanceMeters,
this.estimatedDurationSeconds,
required this.geometry,
required this.isStraightLine,
required this.providerName,
});
/// Alias for geometry points.
List<AppLatLng> get points => geometry;
/// Total straight-line distance in meters.
double get totalDistanceMeters => straightLineDistanceMeters;
/// Alias for isStraightLine.
bool get isHonestStraightLine => isStraightLine;
/// Total distance in miles.
double get distanceMiles {
final meters = estimatedDistanceMeters ?? straightLineDistanceMeters;
return meters * 0.000621371;
}
/// Truthful distance description.
String get distanceLabel {
if (estimatedDistanceMeters != null) {
return '${distanceMiles.toStringAsFixed(1)} mi (Road Est.)';
}
return '${distanceMiles.toStringAsFixed(1)} mi (Straight-line)';
}
}
/// Abstract provider interface for calculating routes and searching places.
abstract class RouteProvider {
String get name;
/// Calculates route geometry and metrics between ordered stops.
Future<RouteCalculationResult> calculateRoute(List<RouteStop> stops);
/// Searches for places by textual query.
Future<List<PlaceSearchResult>> searchPlaces(
String query, {
AppLatLng? nearLocation,
});
}
/// Default geodesic straight-line preview implementation.
///
/// Truthfully connects stops with direct segments and calculates geodesic
/// distance without fabricating road paths or speculative turn ETAs.
class StraightLineRouteProvider implements RouteProvider {
const StraightLineRouteProvider();
@override
String get name => 'Straight-line Preview';
@override
Future<RouteCalculationResult> calculateRoute(List<RouteStop> stops) async {
final ordered = List<RouteStop>.from(stops);
ordered.sort((a, b) => a.order.compareTo(b.order));
final distanceMeters = MotorSignalRoute.calculateTotalDistanceMeters(ordered);
final points = ordered.map((s) => s.latLng).toList();
return RouteCalculationResult(
straightLineDistanceMeters: distanceMeters,
estimatedDistanceMeters: null,
estimatedDurationSeconds: null,
geometry: points,
isStraightLine: true,
providerName: name,
);
}
@override
Future<List<PlaceSearchResult>> searchPlaces(
String query, {
AppLatLng? nearLocation,
}) async {
final clean = query.trim();
if (clean.isEmpty) return const [];
// Support direct coordinate input (e.g. "34.5378, -83.9849")
final parts = clean.split(RegExp(r'[,\s]+'));
if (parts.length == 2) {
final lat = double.tryParse(parts[0]);
final lng = double.tryParse(parts[1]);
if (lat != null && lng != null && lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180) {
return [
PlaceSearchResult(
name: 'Coordinate Pin',
address: '${lat.toStringAsFixed(5)}, ${lng.toStringAsFixed(5)}',
latitude: lat,
longitude: lng,
),
];
}
}
// Default fallback: return query name centered at reference location if provided
if (nearLocation != null) {
return [
PlaceSearchResult(
name: clean,
address: 'Near Current Map Location',
latitude: nearLocation.latitude,
longitude: nearLocation.longitude,
),
];
}
return const [];
}
}