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> geometry; // [[lon, lat], ...] final List 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 json) { final geom = json['geometry'] as Map; 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)) .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 steps; RouteLeg({ required this.distance, required this.duration, required this.summary, required this.steps, }); factory RouteLeg.fromJson(Map json) { final steps = (json['steps'] as List?) ?.map((s) => RouteStep.fromJson(s as Map)) .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 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), ); } } class Maneuver { final String type; final String? modifier; final List location; // [lon, lat] final String instruction; Maneuver({ required this.type, this.modifier, required this.location, required this.instruction, }); factory Maneuver.fromJson(Map 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> 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)) .toList(); return routes; } } final routingRepositoryProvider = Provider((ref) { return RoutingRepository(ref.watch(apiClientProvider)); });