Flutter旅行追踪距离计算严重偏差问题排查与方案求助
问题描述
我正在实现一项位置追踪功能:
- 用户行进时每10秒捕获一次实时位置,经纬度存储到本地数据库
- 每10分钟调用API将数据同步到服务器
- 每10秒计算上一次捕获位置与最新位置的距离,累加得到总距离
测试时出现严重偏差:
- 实际行进约35公里,计算总距离约400公里
- 再次测试实际行进约15公里,计算总距离约150公里
求指出代码问题,或提供可靠的路径追踪与总距离计算替代方案。
现有代码
定时器实现
Timer.periodic(const Duration(seconds: 10), (timer) async { await CommonMethods.getCurrentLocationDateTime(); });
位置捕获逻辑
static Future getCurrentLocationDateTime() async { _result = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.bestForNavigation, ); if (_updatedLatitude == null && _updatedLongitude == null) { _previousLatitude = _result!.latitude; _previousLongitude = _result!.longitude; } else { _previousLatitude = _updatedLatitude; _previousLongitude = _updatedLongitude; } _updatedLatitude = _result!.latitude; _updatedLongitude = _result!.longitude; // Save data in a local variable Map<String, String> _currentLatLong = { "lat": _result!.latitude.toStringAsFixed(8), "long": _result!.longitude.toStringAsFixed(8), }; CommonEntities.travelHistoryLatLng.add(_currentLatLong); // Update previous and current location names await placemarkFromCoordinates(_updatedLatitude!, _updatedLongitude!) .then((List<Placemark> placemarks) { _previousLocationName = (_currentLocationName == "") ? placemarks[0].name! : _currentLocationName; _currentLocationName = placemarks[0].subLocality!; }).catchError((e) { debugPrint(e.toString()); }); // Check if device is not moved for more than 2 hours // if not moved then stop the timer if (CommonEntities.travelHistoryLatLng.toSet().length == 1) { CommonEntities.idealStateTravelTimeSpent += 10; if (CommonEntities.idealStateTravelTimeSpent >= 7200) { CommonEntities.idealStateTravelTimeSpent = 0; // BackgroundLocation.stopLocationService(); if (CommonEntities.travelTimer != null) { CommonEntities.travelTimer!.cancel(); } CommonEntities.isTravelTimerActive = false; CommonEntities.workEndTime = CommonEntities.travelTrackingDetails["current_date_time"]; } } else { CommonEntities.idealStateTravelTimeSpent = 0; } // Get Current date time of captured lat-longs _currentDateTime = DateTime.now(); _currentDateTimeString = _currentDateTime.year.toString() + "-" + _currentDateTime.month.toString() + "-" + _currentDateTime.day.toString() + " " + _currentDateTime.hour.toString() + ":" + _currentDateTime.minute.toString(); if (CommonEntities.workStartTime == "00:00") { CommonEntities.workStartTime = _currentDateTime.hour.toString() + ":" + _currentDateTime.minute.toString(); // LocalSharedPreferences().setWorkStartTime(); } // Update the total distance covered in 10 min updateDistanceTraveled(); print("Total Distance Covered: " + CommonEntities.travelTrackingDetails["total_distance_covered"]); // Call API to save travel data in every 10 minutes. if (CommonEntities.totalTravelsecondsCount == 600) { // Calculate the time before 10 min and for time interval in post params String _currentTime = _currentDateTime.hour.toString() + ':' + _currentDateTime.minute.toString(); DateTime _dateTimeBeforeTenMin = _currentDateTime.subtract(const Duration(minutes: 10)); String _timeBeforeTenMin = _dateTimeBeforeTenMin.hour.toString() + ':' + _dateTimeBeforeTenMin.minute.toString(); String _tempDistanceTravelled = CommonEntities.travelTrackingDetails["total_distance_covered"]; CommonEntities.travelTrackingDetails["total_distance_covered"] = ""; _distanceTravelled = 0.0; // Create post params value Map<String, dynamic> params = { 'lat_long': jsonEncode(CommonEntities.travelHistoryLatLng), 'date': _currentDateTime.day.toString() + '-' + _currentDateTime.month.toString() + '-' + _currentDateTime.year.toString() + ' ' + _currentDateTime.hour.toString() + ':' + _currentDateTime.minute.toString() + ':' + _currentDateTime.second.toString(), 'distance_covered': _tempDistanceTravelled, 'userID': await LocalSharedPreferences().getUserId(), 'time_interval': _timeBeforeTenMin + '-' + _currentTime, 'location_name': _currentLocationName, 'status': 'progress', }; // Call the API to save travel history of 10 min of server HomeProvider().saveTravelDetails(params); // await LocalSharedPreferences().saveTravelHistoryLatLng(); // Reset the timer of 10 min, clear lat-long history of 10 min CommonEntities.totalTravelsecondsCount = 0; CommonEntities.travelHistoryLatLng = []; } CommonEntities.totalTravelsecondsCount += 10; // Save latlong and date time // BOC: commented logging of lat-long as the same is not required in final app // by shubham.bansal2 AppLogs().logToFile( _result!.latitude.toString() + "-" + _result!.longitude.toString(), _currentDateTimeString); CommonEntities.travelTrackingDetails["updated_latitude"] = _updatedLatitude!.toStringAsFixed(8); CommonEntities.travelTrackingDetails["updated_longitude"] = _updatedLongitude!.toStringAsFixed(8); CommonEntities.travelTrackingDetails["updated_location_name"] = _currentLocationName; CommonEntities.travelTrackingDetails["previous_latitude"] = _previousLatitude!.toStringAsFixed(8); CommonEntities.travelTrackingDetails["previous_longitude"] = _previousLongitude!.toStringAsFixed(8); CommonEntities.travelTrackingDetails["previous_location_name"] = _previousLocationName; CommonEntities.travelTrackingDetails["total_distance_covered"] = _distanceTravelled.toStringAsFixed(8); CommonEntities.travelTrackingDetails["current_date_time"] = _currentDateTimeString; }
距离计算逻辑
static updateDistanceTraveled() { var p = 0.017453292519943295; var c = cos; var a = 0.5 - c((_updatedLatitude! - _previousLatitude!) * p) / 2 + c(_previousLatitude! * p) * c(_updatedLatitude! * p) * (1 - c((_updatedLongitude! - _previousLongitude!) * p)) / 2; _distanceTravelled = double.parse(CommonEntities.travelTrackingDetails["total_distance_covered"]); _distanceTravelled += (12742 * asin(sqrt(a))).toDouble(); CommonEntities.travelTrackingDetails["total_distance_covered"] = _distanceTravelled.toStringAsFixed(5); }
核心问题分析
定时器重复创建
这是导致距离偏差10倍左右的最可能原因:如果启动定时器的代码被多次执行,会创建多个Timer.periodic实例,每10秒重复执行多次位置捕获与距离计算,总距离会被累加N次(N为定时器数量),与测试结果完全匹配。静止状态判断逻辑错误
使用CommonEntities.travelHistoryLatLng.toSet().length == 1判断静止是无效的:Dart中Map是引用类型,即使两个Map内容完全一致,也会被视为不同元素,toSet().length永远不会等于1(除非仅添加了一个元素),导致静止判断失效。距离计算初始化异常
10分钟同步后,CommonEntities.travelTrackingDetails["total_distance_covered"]被设置为空字符串,下一次调用updateDistanceTraveled时,double.parse("")会抛出FormatException,破坏正常计算流程。自定义距离计算的潜在风险
手动实现的大圆距离公式虽然逻辑正确,但存在单位混淆、精度丢失的风险,不如官方库方法可靠。
修复方案
1. 确保定时器唯一
启动前检查是否已有活跃定时器,避免重复创建:
if (CommonEntities.isTravelTimerActive) return; CommonEntities.travelTimer = Timer.periodic(const Duration(seconds: 10), (timer) async { await CommonMethods.getCurrentLocationDateTime(); }); CommonEntities.isTravelTimerActive = true;
2. 修复静止状态判断
替换为基于实际距离的判断(使用Geolocator官方方法计算两点距离,单位为米):
// 计算当前与上一次位置的距离(单位:米) double currentDistance = Geolocator.distanceBetween( _previousLatitude!, _previousLongitude!, _updatedLatitude!, _updatedLongitude!, ); if (currentDistance < 5) { // 小于5米视为静止 CommonEntities.idealStateTravelTimeSpent += 10; if (CommonEntities.idealStateTravelTimeSpent >= 7200) { CommonEntities.idealStateTravelTimeSpent = 0; if (CommonEntities.travelTimer != null) { CommonEntities.travelTimer!.cancel(); } CommonEntities.isTravelTimerActive = false; CommonEntities.workEndTime = CommonEntities.travelTrackingDetails["current_date_time"]; } } else { CommonEntities.idealStateTravelTimeSpent = 0; }
3. 修复距离计算初始化问题
同步后将总距离重置为"0.0"而非空字符串:
// 替换原有的空字符串设置 CommonEntities.travelTrackingDetails["total_distance_covered"] = "0.0";
4. 替换自定义距离计算为官方方法
使用Geolocator的distanceBetween方法,避免手动实现的潜在问题:
static updateDistanceTraveled() { // 计算两点距离(单位:米) double distanceMeters = Geolocator.distanceBetween( _previousLatitude!, _previousLongitude!, _updatedLatitude!, _updatedLongitude!, ); // 转换为公里(根据需求调整单位) double distanceKm = distanceMeters / 1000; _distanceTravelled = double.parse(CommonEntities.travelTrackingDetails["total_distance_covered"]); _distanceTravelled += distanceKm; CommonEntities.travelTrackingDetails["total_distance_covered"] = _distanceTravelled.toStringAsFixed(5); }
5. 简化日期时间处理
使用intl包的DateFormat类,避免手动拼接的格式错误:
import 'package:intl/intl.dart'; // 格式化日期时间 _currentDateTime = DateTime.now(); _currentDateTimeString = DateFormat('yyyy-MM-dd HH:mm').format(_currentDateTime); // 同步API用的完整日期时间 String fullDateTime = DateFormat('dd-MM-yyyy HH:mm:ss').format(_currentDateTime);
替代方案
如果需要更可靠的路径追踪,可考虑以下优化:
- 使用位置流监听:替换定时调用
getCurrentPosition为Geolocator.getPositionStream,精准捕获位置变化,避免定时器时间误差。 - 过滤低精度位置:通过
Position.accuracy字段过滤精度差的位置数据,避免异常点导致距离偏差。 - 本地持久化存储:使用Hive、SQFlite等本地数据库存储位置数据,避免App后台被杀死后数据丢失。
内容的提问来源于stack exchange,提问作者Shubham Bansal

