Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 39 additions & 50 deletions lib/services/routing_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:deflock_router_client/api.dart' as router;

import '../app_state.dart';
import '../dev_config.dart';
Expand All @@ -12,13 +13,13 @@ class RouteResult {
final List<LatLng> waypoints;
final double distanceMeters;
final double durationSeconds;

const RouteResult({
required this.waypoints,
required this.distanceMeters,
required this.durationSeconds,
});

@override
String toString() {
return 'RouteResult(waypoints: ${waypoints.length}, distance: ${(distanceMeters/1000).toStringAsFixed(1)}km, duration: ${(durationSeconds/60).toStringAsFixed(1)}min)';
Expand All @@ -44,40 +45,29 @@ class RoutingService {
final avoidanceDistance = prefs.getInt('navigation_avoidance_distance') ?? 250;

final enabledProfiles = AppState.instance.enabledProfiles.map((p) {
final full = p.toJson();
final tags = Map<String, String>.from(full['tags'] as Map);
final tags = Map<String, String>.from(p.tags);
tags.removeWhere((key, value) => value.isEmpty);
return {
'id': full['id'],
'name': full['name'],
'tags': tags,
};
return router.NodeProfile(id: p.id, name: p.name, tags: tags);
}).toList();


final request = router.DirectionsRequest(
start: router.Coordinate(latitude: start.latitude, longitude: start.longitude),
end: router.Coordinate(latitude: end.latitude, longitude: end.longitude),
avoidanceDistance: avoidanceDistance,
enabledProfiles: enabledProfiles,
showExclusionZone: false,
);

final uri = Uri.parse(_baseUrl);
final params = {
'start': {
'longitude': start.longitude,
'latitude': start.latitude
},
'end': {
'longitude': end.longitude,
'latitude': end.latitude
},
'avoidance_distance': avoidanceDistance,
'enabled_profiles': enabledProfiles,
'show_exclusion_zone': false, // for debugging: if true, returns a GeoJSON Feature MultiPolygon showing what areas are avoided in calculating the route
};

debugPrint('[RoutingService] alprwatch request: $uri $params');

debugPrint('[RoutingService] alprwatch request: $uri ${request.toJson()}');

try {
final response = await _client.post(
uri,
headers: {
'Content-Type': 'application/json'
},
body: json.encode(params)
body: json.encode(request.toJson())
).timeout(kNavigationRoutingTimeout);

if (response.statusCode != 200) {
Expand All @@ -93,42 +83,41 @@ class RoutingService {
}
throw RoutingException('HTTP ${response.statusCode}: ${response.reasonPhrase}');
}

final data = json.decode(response.body) as Map<String, dynamic>;
debugPrint('[RoutingService] alprwatch response data: $data');

// Check alprwatch response status
final ok = data['ok'] as bool? ?? false;
if ( ! ok ) {
final message = data['error'] as String? ?? 'Unknown routing error';
throw RoutingException('alprwatch error: $message');
}

final route = data['result']['route'] as Map<String, dynamic>?;
if (route == null) {

final resultData = data['result'] as Map<String, dynamic>?;
if (resultData == null) {
throw RoutingException('No result in response');
}

final directionsResult = router.DirectionsResult.fromJson(resultData);
if (directionsResult == null) {
throw RoutingException('No route found between these points');
}

final waypoints = (route['coordinates'] as List<dynamic>?)
?.map((inner) {
final pair = inner as List<dynamic>;
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<LatLng>().toList() ?? [];
final distance = (route['distance'] as num?)?.toDouble() ?? 0.0;
final duration = (route['duration'] as num?)?.toDouble() ?? 0.0;


final routeGeometry = directionsResult.route;
final waypoints = routeGeometry.coordinates.map((pair) {
return LatLng(pair[1], pair[0]); // [lon, lat] -> LatLng(lat, lon)
}).toList();
Comment on lines +108 to +110
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pair[1] / pair[0] assumes every coordinate entry has at least 2 elements. Previously malformed pairs were filtered out; now a short/invalid pair will throw (RangeError) and get wrapped as a misleading "Network error". Add validation (e.g., length check) and throw a RoutingException with a parse/invalid response message when coordinates are missing or malformed.

Suggested change
final waypoints = routeGeometry.coordinates.map((pair) {
return LatLng(pair[1], pair[0]); // [lon, lat] -> LatLng(lat, lon)
}).toList();
final waypoints = <LatLng>[];
for (final pair in routeGeometry.coordinates) {
if (pair is! List || pair.length < 2) {
throw const RoutingException(
'Invalid routing response: coordinate pair is missing latitude/longitude');
}
final lon = pair[0];
final lat = pair[1];
if (lon is! num || lat is! num) {
throw const RoutingException(
'Invalid routing response: coordinate values are not numeric');
}
waypoints.add(LatLng(lat.toDouble(), lon.toDouble())); // [lon, lat] -> LatLng(lat, lon)
}

Copilot uses AI. Check for mistakes.

final result = RouteResult(
waypoints: waypoints,
distanceMeters: distance,
durationSeconds: duration,
distanceMeters: routeGeometry.distance,
durationSeconds: routeGeometry.duration,
);

debugPrint('[RoutingService] Route calculated: $result');
return result;

} catch (e) {
debugPrint('[RoutingService] Route calculation failed: $e');
if (e is RoutingException) {
Expand All @@ -142,9 +131,9 @@ class RoutingService {

class RoutingException implements Exception {
final String message;

const RoutingException(this.message);

@override
String toString() => 'RoutingException: $message';
}
7 changes: 7 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.11"
deflock_router_client:
dependency: "direct main"
description:
path: "../FlockHopper/packages/deflock_router_client"
relative: true
source: path
version: "1.0.0"
desktop_webview_window:
dependency: transitive
description:
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ dependencies:
package_info_plus: ^8.0.0
csv: ^6.0.0
collection: ^1.18.0
deflock_router_client:
path: ../FlockHopper/packages/deflock_router_client
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a path: dependency that points outside this repository will fail in CI/release workflows (they only actions/checkout this repo, then run flutter pub get). Consider switching to a git: dependency (pinned ref/tag) or updating the GitHub Actions workflows to also checkout FlockHopper into ../FlockHopper so flutter pub get can resolve this package.

Suggested change
path: ../FlockHopper/packages/deflock_router_client
git:
url: https://github.com/FlockHopper/FlockHopper.git
path: packages/deflock_router_client
ref: main

Copilot uses AI. Check for mistakes.

dev_dependencies:
flutter_test:
Expand Down
Loading