MotorSignal-App/lib/services/location_service.dart

108 lines
3.5 KiB
Dart

import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
final locationServiceProvider = Provider<LocationService>((ref) {
return LocationService();
});
final locationStreamProvider = StreamProvider<Position>((ref) {
final service = ref.watch(locationServiceProvider);
return service.getFilteredPositionStream();
});
class LocationService {
Position? _lastValidPosition;
double? _emaSpeed;
// Filtering thresholds
static const double _maxRealisticSpeed = 100.0; // roughly 223 MPH in m/s
static const double _maxAcceleration = 20.0; // m/s^2 (unrealistic jump)
static const double _speedEmaAlpha = 0.3; // Smoothing factor for speed
Future<bool> requestPermissions() async {
final status = await Permission.locationWhenInUse.request();
if (status.isGranted) {
return true;
}
return false;
}
Stream<Position> getFilteredPositionStream() {
final LocationSettings locationSettings = AndroidSettings(
accuracy: LocationAccuracy.bestForNavigation,
distanceFilter: 0,
intervalDuration: const Duration(milliseconds: 200), // 5Hz updates
);
return Geolocator.getPositionStream(locationSettings: locationSettings)
.where(_isValidPosition)
.map(_applyEmaFilter);
}
bool _isValidPosition(Position position) {
// Dynamic accuracy threshold: allow worse accuracy at higher speeds
final currentSpeed = _emaSpeed ?? position.speed;
final maxAcceptableAccuracy = currentSpeed > 20.0 ? 30.0 : 15.0;
if (position.accuracy > maxAcceptableAccuracy) {
return false; // Reject extremely poor accuracy
}
if (position.speed > _maxRealisticSpeed) {
return false; // Reject impossible speeds
}
if (_lastValidPosition != null) {
final timeDiff = (position.timestamp.difference(_lastValidPosition!.timestamp)).inMilliseconds / 1000.0;
if (timeDiff > 0) {
// Acceleration check
final accel = (position.speed - _lastValidPosition!.speed).abs() / timeDiff;
if (accel > _maxAcceleration) {
return false; // Reject impossible acceleration spikes
}
// Path consistency check (distance jump vs time)
final distanceJump = Geolocator.distanceBetween(
_lastValidPosition!.latitude, _lastValidPosition!.longitude,
position.latitude, position.longitude
);
// If distance jumped implies speed > max realistic speed + some buffer, reject
final impliedSpeed = distanceJump / timeDiff;
if (impliedSpeed > _maxRealisticSpeed * 1.5) {
return false;
}
}
}
_lastValidPosition = position;
return true;
}
Position _applyEmaFilter(Position position) {
if (_emaSpeed == null) {
_emaSpeed = position.speed;
} else {
_emaSpeed = _speedEmaAlpha * position.speed + (1 - _speedEmaAlpha) * _emaSpeed!;
}
// Return a new Position with the smoothed speed
return Position(
longitude: position.longitude,
latitude: position.latitude,
timestamp: position.timestamp,
accuracy: position.accuracy,
altitude: position.altitude,
heading: position.heading,
speed: _emaSpeed!,
speedAccuracy: position.speedAccuracy,
altitudeAccuracy: position.altitudeAccuracy,
headingAccuracy: position.headingAccuracy,
isMocked: position.isMocked,
floor: position.floor,
);
}
}