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((ref) { return LocationService(); }); final locationStreamProvider = StreamProvider((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 requestPermissions() async { final status = await Permission.locationWhenInUse.request(); if (status.isGranted) { return true; } return false; } Stream 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, ); } }