146 lines
5.0 KiB
Dart
146 lines
5.0 KiB
Dart
// 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<String, dynamic>) {
|
|
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<String, dynamic> _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<String, dynamic> _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;
|