25-10-11/1
This commit is contained in:
145
lib/controller/home/navigation/route_matcher_worker.dart
Normal file
145
lib/controller/home/navigation/route_matcher_worker.dart
Normal file
@@ -0,0 +1,145 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user