maps/mobile/lib/features/routing/data/routing_repository.dart
2026-03-30 09:22:16 +02:00

173 lines
4.4 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/api/api_client.dart';
/// A single route from the OSRM response.
class RouteData {
final double distance; // meters
final double duration; // seconds
final List<List<double>> geometry; // [[lon, lat], ...]
final List<RouteLeg> legs;
RouteData({
required this.distance,
required this.duration,
required this.geometry,
required this.legs,
});
String get distanceFormatted {
if (distance < 1000) {
return '${distance.round()} m';
}
return '${(distance / 1000).toStringAsFixed(1)} km';
}
String get durationFormatted {
final minutes = (duration / 60).round();
if (minutes < 60) {
return '$minutes min';
}
final hours = minutes ~/ 60;
final remainingMin = minutes % 60;
return '${hours}h ${remainingMin}min';
}
factory RouteData.fromJson(Map<String, dynamic> json) {
final geom = json['geometry'] as Map<String, dynamic>;
final coords = (geom['coordinates'] as List)
.map((c) => [(c[0] as num).toDouble(), (c[1] as num).toDouble()])
.toList();
final legs = (json['legs'] as List)
.map((l) => RouteLeg.fromJson(l as Map<String, dynamic>))
.toList();
return RouteData(
distance: (json['distance'] as num).toDouble(),
duration: (json['duration'] as num).toDouble(),
geometry: coords,
legs: legs,
);
}
}
class RouteLeg {
final double distance;
final double duration;
final String summary;
final List<RouteStep> steps;
RouteLeg({
required this.distance,
required this.duration,
required this.summary,
required this.steps,
});
factory RouteLeg.fromJson(Map<String, dynamic> json) {
final steps = (json['steps'] as List?)
?.map((s) => RouteStep.fromJson(s as Map<String, dynamic>))
.toList() ??
[];
return RouteLeg(
distance: (json['distance'] as num).toDouble(),
duration: (json['duration'] as num).toDouble(),
summary: json['summary'] as String? ?? '',
steps: steps,
);
}
}
class RouteStep {
final double distance;
final double duration;
final String name;
final Maneuver maneuver;
RouteStep({
required this.distance,
required this.duration,
required this.name,
required this.maneuver,
});
factory RouteStep.fromJson(Map<String, dynamic> json) {
return RouteStep(
distance: (json['distance'] as num).toDouble(),
duration: (json['duration'] as num).toDouble(),
name: json['name'] as String? ?? '',
maneuver:
Maneuver.fromJson(json['maneuver'] as Map<String, dynamic>),
);
}
}
class Maneuver {
final String type;
final String? modifier;
final List<double> location; // [lon, lat]
final String instruction;
Maneuver({
required this.type,
this.modifier,
required this.location,
required this.instruction,
});
factory Maneuver.fromJson(Map<String, dynamic> json) {
final loc = (json['location'] as List)
.map((e) => (e as num).toDouble())
.toList();
return Maneuver(
type: json['type'] as String,
modifier: json['modifier'] as String?,
location: loc,
instruction: json['instruction'] as String? ?? '',
);
}
}
class RoutingRepository {
final ApiClient _apiClient;
RoutingRepository(this._apiClient);
/// Calculate a route via GET /api/route/{profile}/{coordinates}.
/// [origin] and [destination] are [lat, lon] pairs.
Future<List<RouteData>> getRoute({
required String profile,
required double originLat,
required double originLon,
required double destLat,
required double destLon,
}) async {
// OSRM coordinates format: lon,lat;lon,lat
final coordinates =
'$originLon,$originLat;$destLon,$destLat';
final data = await _apiClient.get(
'/api/route/$profile/$coordinates',
queryParameters: {
'alternatives': 2,
'steps': true,
'geometries': 'geojson',
},
);
final code = data['code'] as String?;
if (code != 'Ok') {
throw Exception('Routing failed: $code');
}
final routes = (data['routes'] as List)
.map((r) => RouteData.fromJson(r as Map<String, dynamic>))
.toList();
return routes;
}
}
final routingRepositoryProvider = Provider<RoutingRepository>((ref) {
return RoutingRepository(ref.watch(apiClientProvider));
});