Files
doormile_milderapp/lib/helpers/miler_router.dart
2026-08-11 13:16:33 +05:30

99 lines
3.8 KiB
Dart

import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:latlong2/latlong.dart' show LatLng;
// ── Routing ─────────────────────────────────────────────────────────────────
/// A driving route between two points, from OSRM's public router.
///
/// Replaces the Directions API call, which needed the revoked key. Same shape
/// of answer — an ordered list of coordinates — so the callers' route drawing,
/// distance table and camera fitting are untouched.
class MilerRouter {
static const String _host = 'https://router.project-osrm.org';
/// Sent with every request. OSRM's demo server, like OSM's tile server,
/// blocks anonymous clients first.
static const String _agent = 'com.doormile.partner';
/// The road geometry from [start] to [end], or an empty list.
///
/// Never throws: a route screen with no line is a degraded screen, but a
/// route screen that crashed is a rider standing in the street with nothing.
/// Callers fall back to the direct line on an empty answer.
static Future<List<LatLng>> route(LatLng start, LatLng end) async {
final uri = Uri.parse(
'$_host/route/v1/driving/'
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}'
'?overview=full&geometries=geojson',
);
try {
final res = await http
.get(uri, headers: const {'User-Agent': _agent})
.timeout(const Duration(seconds: 8));
if (res.statusCode != 200) {
debugPrint(
'[MAP] OSRM ${res.statusCode} — falling back to a direct line.',
);
return const [];
}
final body = jsonDecode(res.body) as Map<String, dynamic>;
if (body['code'] != 'Ok') {
debugPrint('[MAP] OSRM said ${body['code']} — direct line instead.');
return const [];
}
final routes = body['routes'] as List<dynamic>?;
if (routes == null || routes.isEmpty) return const [];
final coords =
(routes.first as Map<String, dynamic>)['geometry']['coordinates']
as List<dynamic>;
// GeoJSON is [longitude, latitude] — the opposite order to everything
// else in this app, and a silent way to end up routing through the sea.
return [
for (final c in coords)
LatLng((c[1] as num).toDouble(), (c[0] as num).toDouble()),
];
} catch (e) {
debugPrint('[MAP] OSRM unreachable ($e) — direct line instead.');
return const [];
}
}
/// Road distance in metres between two points, or null when the router
/// cannot answer.
///
/// Replaces a direct call to the Google Directions API in
/// `PickupsController`, which was reading the same revoked key and therefore
/// silently falling back to straight-line distance on every trip — the number
/// riders are *paid* on.
static Future<double?> roadDistanceMetres(LatLng start, LatLng end) async {
final uri = Uri.parse(
'$_host/route/v1/driving/'
'${start.longitude},${start.latitude};${end.longitude},${end.latitude}'
'?overview=false',
);
try {
final res = await http
.get(uri, headers: const {'User-Agent': _agent})
.timeout(const Duration(seconds: 6));
if (res.statusCode != 200) return null;
final body = jsonDecode(res.body) as Map<String, dynamic>;
if (body['code'] != 'Ok') return null;
final routes = body['routes'] as List<dynamic>?;
if (routes == null || routes.isEmpty) return null;
final d = (routes.first as Map<String, dynamic>)['distance'];
return d is num ? d.toDouble() : null;
} catch (e) {
debugPrint('[ROUTE] OSRM distance unavailable ($e)');
return null;
}
}
}