// lib/controllers/navigation/route_matcher_worker.dart import 'dart:async'; import 'dart:isolate'; import 'dart:typed_data'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'dart:math'; /// Worker entrypoint (spawnUri/spawn). /// Messages: /// - init: {'type':'init','coords': Float64List} /// - match: {'type':'match','id': int, 'lat': double, 'lng': double, 'lastIndex': int, 'window': int} /// - dispose: {'type':'dispose'} /// /// Responses are sent back as Map via SendPort: /// - {'type':'ready'} /// - {'type':'matchResult','id': id, 'index': overallIndex, 'lat': lat, 'lng': lng, 'dist': meters} void routeMatcherIsolateEntry(SendPort sendPort) { final ReceivePort port = ReceivePort(); sendPort.send({'type': 'ready', 'port': port.sendPort}); Float64List? flat; // [lat,lng,lat,lng,...] int nPoints = 0; port.listen((dynamic message) { try { if (message is Map) { final type = message['type'] as String? ?? ''; if (type == 'init') { final data = message['coords'] as Float64List?; if (data != null) { flat = data; nPoints = flat!.length ~/ 2; sendPort.send({'type': 'inited', 'points': nPoints}); } else { sendPort.send({'type': 'error', 'message': 'init missing coords'}); } } else if (type == 'match') { if (flat == null) { sendPort.send({'type': 'error', 'message': 'not inited'}); return; } final int id = message['id'] as int; final double lat = (message['lat'] as num).toDouble(); final double lng = (message['lng'] as num).toDouble(); final int lastIndex = (message['lastIndex'] as int?) ?? 0; final int window = (message['window'] as int?) ?? 120; final result = _findClosestWindowInternal(flat!, lat, lng, lastIndex, window); sendPort.send({ 'type': 'matchResult', 'id': id, 'index': result['index'], 'lat': result['lat'], 'lng': result['lng'], 'dist': result['dist'] }); } else if (type == 'dispose') { port.close(); sendPort.send({'type': 'disposed'}); } else { sendPort.send({'type': 'error', 'message': 'unknown message type'}); } } } catch (e, st) { sendPort.send( {'type': 'error', 'message': e.toString(), 'stack': st.toString()}); } }); } /// Internal helper: projection on segments, windowed search. /// Returns Map {index, lat, lng, dist} Map _findClosestWindowInternal( Float64List flat, double lat, double lng, int lastIndex, int window) { final int n = flat.length ~/ 2; final int start = max(0, lastIndex - window); final int end = min(n - 1, lastIndex + window); double minDist = double.infinity; int bestIdx = lastIndex; double bestLat = flat[lastIndex * 2]; double bestLng = flat[lastIndex * 2 + 1]; for (int i = start; i < end; i++) { final double aLat = flat[i * 2]; final double aLng = flat[i * 2 + 1]; final double bLat = flat[(i + 1) * 2]; final double bLng = flat[(i + 1) * 2 + 1]; final proj = _closestPointOnSegmentLatLng(lat, lng, aLat, aLng, bLat, bLng); final double d = proj['dist'] as double; if (d < minDist) { minDist = d; bestLat = proj['lat'] as double; bestLng = proj['lng'] as double; // choose overall index: i or i+1 depending on t final double t = proj['t'] as double; bestIdx = i + (t > 0.5 ? 1 : 0); } } return {'index': bestIdx, 'lat': bestLat, 'lng': bestLng, 'dist': minDist}; } /// Projection math on geodetic points approximated in degrees (good for short distances). Map _closestPointOnSegmentLatLng( double px, double py, double ax, double ay, double bx, double by) { // Here px=lat, py=lng; ax=lat, ay=lng, etc. final double x0 = px; final double y0 = py; final double x1 = ax; final double y1 = ay; final double x2 = bx; final double y2 = by; final double dx = x2 - x1; final double dy = y2 - y1; double t = 0.0; final double len2 = dx * dx + dy * dy; if (len2 > 0) { t = ((x0 - x1) * dx + (y0 - y1) * dy) / len2; if (t < 0) t = 0; if (t > 1) t = 1; } final double projX = x1 + t * dx; final double projY = y1 + t * dy; final double distMeters = _haversineDistanceMeters(x0, y0, projX, projY); return {'lat': projX, 'lng': projY, 't': t, 'dist': distMeters}; } /// Haversine distance (meters) double _haversineDistanceMeters( double lat1, double lng1, double lat2, double lng2) { final double R = 6371000.0; final double dLat = _deg2rad(lat2 - lat1); final double dLon = _deg2rad(lng2 - lng1); final double a = sin(dLat / 2) * sin(dLat / 2) + cos(_deg2rad(lat1)) * cos(_deg2rad(lat2)) * sin(dLon / 2) * sin(dLon / 2); final double c = 2 * atan2(sqrt(a), sqrt(1 - a)); return R * c; } double _deg2rad(double deg) => deg * pi / 180.0;