From 9782352909c164c126aa9e6dd7515e7beffc6f8d Mon Sep 17 00:00:00 2001 From: ALPR Watch Date: Mon, 1 Dec 2025 21:48:25 +0100 Subject: [PATCH] Start to change navigation to alprwatch API This only covers the success path. Known todos: * failure modes in routing * developer documentation * feature flags --- lib/services/routing_service.dart | 130 +++++++++++------------------- lib/state/navigation_state.dart | 9 +-- 2 files changed, 50 insertions(+), 89 deletions(-) diff --git a/lib/services/routing_service.dart b/lib/services/routing_service.dart index 71afcc5..62eaa03 100644 --- a/lib/services/routing_service.dart +++ b/lib/services/routing_service.dart @@ -21,73 +21,81 @@ class RouteResult { } class RoutingService { - static const String _baseUrl = 'https://router.project-osrm.org'; + static const String _baseUrl = 'https://alprwatch.org/api/v1/deflock/directions'; static const String _userAgent = 'DeFlock/1.0 (OSM surveillance mapping app)'; static const Duration _timeout = Duration(seconds: 15); - /// Calculate route between two points using OSRM + /// Calculate route between two points using alprwatch Future calculateRoute({ required LatLng start, required LatLng end, - String profile = 'driving', // driving, walking, cycling }) async { debugPrint('[RoutingService] Calculating route from $start to $end'); - // OSRM uses lng,lat order (opposite of LatLng) - final startCoord = '${start.longitude},${start.latitude}'; - final endCoord = '${end.longitude},${end.latitude}'; + final uri = Uri.parse('$_baseUrl'); + final params = { + 'start': { + 'longitude': start.longitude, + 'latitude': start.latitude + }, + 'end': { + 'longitude': end.longitude, + 'latitude': end.latitude + }, + 'enabled_profiles': [ // revise to be dynamic based on user input + { + 'id': 'generic-ALPR', + 'name': 'ALPR', + 'tags': { + 'surveillance:type': 'ALPR' + } + } + ] + }; - final uri = Uri.parse('$_baseUrl/route/v1/$profile/$startCoord;$endCoord') - .replace(queryParameters: { - 'overview': 'full', // Get full geometry - 'geometries': 'polyline', // Use polyline encoding (more compact) - 'steps': 'false', // Don't need turn-by-turn for now - }); - - debugPrint('[RoutingService] OSRM request: $uri'); + debugPrint('[RoutingService] alprwatch request: $uri $params'); try { - final response = await http.get( + final response = await http.post( uri, headers: { 'User-Agent': _userAgent, + 'Content-Type': 'application/json' }, + body: json.encode(params) ).timeout(_timeout); - + if (response.statusCode != 200) { throw RoutingException('HTTP ${response.statusCode}: ${response.reasonPhrase}'); } final data = json.decode(response.body) as Map; + debugPrint('[RoutingService] alprwatch response data: $data'); - // Check OSRM response status - final code = data['code'] as String?; - if (code != 'Ok') { - final message = data['message'] as String? ?? 'Unknown routing error'; - throw RoutingException('OSRM error ($code): $message'); + // Check alprwatch response status + final ok = data['ok'] as bool? ?? false; + if ( ! ok ) { + final code = data['error']['code'] as String? ?? 'Unknown routing error code'; + final message = data['error']['message'] as String? ?? 'Unknown routing error'; + throw RoutingException('alprwatch error ($code): $message'); } - final routes = data['routes'] as List?; - if (routes == null || routes.isEmpty) { + final route = data['result']['route'] as Map?; + if (route == null) { throw RoutingException('No route found between these points'); } - - final route = routes[0] as Map; - final geometry = route['geometry'] as String?; + + final waypoints = (route['coordinates'] as List?) + ?.map((inner) { + final pair = inner as List; + if (pair.length != 2) return null; + final lng = (pair[0] as num).toDouble(); + final lat = (pair[1] as num).toDouble(); + return LatLng(lat, lng); + }).whereType().toList() ?? []; final distance = (route['distance'] as num?)?.toDouble() ?? 0.0; final duration = (route['duration'] as num?)?.toDouble() ?? 0.0; - if (geometry == null) { - throw RoutingException('Route geometry missing from response'); - } - - // Decode polyline geometry to waypoints - final waypoints = _decodePolyline(geometry); - - if (waypoints.isEmpty) { - throw RoutingException('Failed to decode route geometry'); - } - final result = RouteResult( waypoints: waypoints, distanceMeters: distance, @@ -106,52 +114,6 @@ class RoutingService { } } } - - /// Decode OSRM polyline geometry to LatLng waypoints - List _decodePolyline(String encoded) { - try { - final List points = []; - int index = 0; - int lat = 0; - int lng = 0; - - while (index < encoded.length) { - int b; - int shift = 0; - int result = 0; - - // Decode latitude - do { - b = encoded.codeUnitAt(index++) - 63; - result |= (b & 0x1f) << shift; - shift += 5; - } while (b >= 0x20); - - final deltaLat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); - lat += deltaLat; - - shift = 0; - result = 0; - - // Decode longitude - do { - b = encoded.codeUnitAt(index++) - 63; - result |= (b & 0x1f) << shift; - shift += 5; - } while (b >= 0x20); - - final deltaLng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); - lng += deltaLng; - - points.add(LatLng(lat / 1E5, lng / 1E5)); - } - - return points; - } catch (e) { - debugPrint('[RoutingService] Manual polyline decoding failed: $e'); - return []; - } - } } class RoutingException implements Exception { @@ -161,4 +123,4 @@ class RoutingException implements Exception { @override String toString() => 'RoutingException: $message'; -} \ No newline at end of file +} diff --git a/lib/state/navigation_state.dart b/lib/state/navigation_state.dart index cea90d1..e94c7cb 100644 --- a/lib/state/navigation_state.dart +++ b/lib/state/navigation_state.dart @@ -221,11 +221,11 @@ class NavigationState extends ChangeNotifier { _calculateRoute(); } - /// Calculate route using OSRM + /// Calculate route using alprwatch void _calculateRoute() { if (_routeStart == null || _routeEnd == null) return; - debugPrint('[NavigationState] Calculating route with OSRM...'); + debugPrint('[NavigationState] Calculating route with alprwatch...'); _isCalculating = true; _routingError = null; notifyListeners(); @@ -233,7 +233,6 @@ class NavigationState extends ChangeNotifier { _routingService.calculateRoute( start: _routeStart!, end: _routeEnd!, - profile: 'driving', // Could make this configurable later ).then((routeResult) { if (!_isCalculating) return; // Canceled while calculating @@ -243,7 +242,7 @@ class NavigationState extends ChangeNotifier { _showingOverview = true; _provisionalPinLocation = null; // Hide provisional pin - debugPrint('[NavigationState] OSRM route calculated: ${routeResult.toString()}'); + debugPrint('[NavigationState] alprwatch route calculated: ${routeResult.toString()}'); notifyListeners(); }).catchError((error) { @@ -348,4 +347,4 @@ class NavigationState extends ChangeNotifier { notifyListeners(); } } -} \ No newline at end of file +}