99 lines
3.8 KiB
Dart
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;
|
|
}
|
|
}
|
|
}
|