Реальное время отслеживания расстояния в Flutter с Geolocator

Реальное время отслеживания расстояния в Flutter с Geolocator

FlutterPulse

Эта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Мобильные приложения, отслеживающие движение, физические активности или навигацию, требуют надежных возможностей расчета расстояния. В этом…

Мобильные приложения, отслеживающие движение, физические активности или навигацию, требуют надежных возможностей расчета расстояния. В этом посте мы рассмотрим, как реализовать точное отслеживание расстояния в Flutter с использованием плагина Geolocator. Мы создадим надежную систему расчета расстояния, которая обрабатывает разрешения, фильтрует GPS-шум и предоставляет надежные измерения для реальных приложений.

Пакет Geolocator — это плагин Flutter, который обеспечивает легкий доступ к специфичным для платформы сервисам геолокации. Он обрабатывает сложности запроса разрешений, доступа к данным о местоположении и расчета расстояний между географическими координатами.

Сначала добавьте зависимость в файл pubspec.yaml

geolocator: ^13.0.2

Создание сервиса расчета расстояния

Давайте создадим комплексный сервис, который обрабатывает:

  • Разрешения на местоположение
  • Реальное отслеживание расстояния
  • Фильтрацию GPS-шума
  • Чистую обработку ресурсов

Вот наша реализация:

import 'dart:async';
import 'package:geolocator/geolocator.dart';

class DistanceCalculation {
  Position? _lastPosition;
  double _totalDistance = 0.0; // Distance in KM
  StreamSubscription<Position>? _positionStream;
  bool _isTracking = false;

  // Stream controller to emit permission status updates
  final _permissionStatusController = StreamController<bool>.broadcast();
  Stream<bool> get permissionStatusStream => _permissionStatusController.stream;

  DistanceCalculation() {
    // Check permission on initialization
    checkPermission();
  }

  // Dispose method to clean up resources
  void dispose() {
    _positionStream?.cancel();
    _permissionStatusController.close();
  }

  // Check and request location permission
  Future<bool> checkPermission() async {
    bool serviceEnabled;
    LocationPermission permission;

    // Test if location services are enabled
    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      _permissionStatusController.add(false);
      return false;
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        _permissionStatusController.add(false);
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      _permissionStatusController.add(false);
      return false;
    }

    _permissionStatusController.add(true);
    return true;
  }

  // Check if tracking is currently active
  bool get isTracking => _isTracking;

  // Get current total distance in KM
  double get currentDistance => _totalDistance;

  // Start tracking distance
  Future<bool> startTracking() async {
    // First check for permission
    bool hasPermission = await checkPermission();
    if (!hasPermission) {
      return false;
    }

    // Reset tracking values
    _totalDistance = 0.0;
    _lastPosition = null;
    _isTracking = true;

    // Configure location settings for better accuracy
    LocationSettings locationSettings = const LocationSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 5, // Updates every 5 meters for better accuracy
    );

    // Start position stream
    _positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen((Position position) {
      if (_lastPosition != null) {
        // Calculate distance between points
        double distanceInMeters = Geolocator.distanceBetween(
          _lastPosition!.latitude, _lastPosition!.longitude,
          position.latitude, position.longitude,
        );

        // Filter out small movements (likely GPS jitter)
        if (distanceInMeters > 2) {
          _totalDistance += distanceInMeters / 1000; // Convert meters to KM
        }
      }

      _lastPosition = position;
    });

    return true;
  }

  // Stop tracking and return total distance in KM
  double stopTracking() {
    _positionStream?.cancel();
    _positionStream = null;
    _isTracking = false;

    return _totalDistance;
  }

  // Reset tracking without stopping
  void resetDistance() {
    _totalDistance = 0.0;
    _lastPosition = null;
  }

  // Get last known position
  Position? get lastPosition => _lastPosition;
}

Ключевые функции, объясненные

1. Обработка разрешений

Метод checkPermission() управляет всеми аспектами разрешений на геолокацию:

  • Проверяет, включены ли сервисы геолокации
  • Проверяет текущий статус разрешений
  • Запрашивает разрешения при необходимости
  • Возвращает и транслирует статус разрешений

Этот комплексный подход гарантирует, что ваше приложение правильно обрабатывает доступ к геолокации на разных платформах.

2. Алгоритм расчета расстояния

Наш расчет расстояния происходит в слушателе потока позиций:

double distanceInMeters = Geolocator.distanceBetween(
  _lastPosition!.latitude, _lastPosition!.longitude,
  position.latitude, position.longitude,
);

Метод distanceBetween использует формулу Хаверсина, которая вычисляет кратчайшее расстояние между двумя точками на сфере (Земля). Это точнее, чем более простые методы вычисления, особенно для больших расстояний.

3. Фильтрация GPS-шума

Чтения GPS могут быть шумными, особенно в статическом состоянии. Наша реализация включает фильтр дребезга:

// Фильтрация малых движений (вероятно, дребезг GPS)
if (distanceInMeters > 2) {
  _totalDistance += distanceInMeters / 1000;
}

Игнорируя движения менее 2 метров, мы предотвращаем накопление мелких колебаний, которые искусственно увеличивают общую дистанцию.

4. Оптимизированные настройки местоположения

Мы настраиваем службу местоположения в соответствии с нашими потребностями:

LocationSettings locationSettings = const LocationSettings(
  accuracy: LocationAccuracy.high,
  distanceFilter: 5, // Обновления каждые 5 метров
);
  • LocationAccuracy.high: Предоставляет более точные данные о позиции
  • distanceFilter: 5: Получает обновления только при перемещении пользователя на 5 метров или более, снижая потребление энергии при сохранении разумной точности

Использование сервиса расчета расстояния

Реализация Singleton

Для приложений, которым необходимо получать доступ к расчету расстояния из нескольких мест, реализуйте шаблон Singleton:

class DistanceCalculation {
  static final DistanceCalculation _instance = DistanceCalculation._internal();

  factory DistanceCalculation() {
    return _instance;
  }

  DistanceCalculation._internal() {
    checkPermission();
  }

  // Остальная часть реализации остается прежней
}

Это гарантирует, что при любом вызове DistanceCalculation(), вы получаете тот же экземпляр, сохраняя состояние по всему приложению.

Базовое использование

// Инициализация и начало отслеживания
final distanceTracker = DistanceCalculation();
await distanceTracker.startTracking();

// Позже, получение текущего расстояния
double currentKm = distanceTracker.currentDistance;

// При завершении, остановка отслеживания и получение итогового расстояния
double totalKm = distanceTracker.stopTracking();

// Освобождение ресурсов после завершения
distanceTracker.dispose();

Отображение обновлений в реальном времени

class _MyAppState extends State<MyApp> {
  final DistanceCalculation _distanceTracker = DistanceCalculation();
  Timer? _updateTimer;
  double _currentDistance = 0.0;

  @override
  void initState() {
    super.initState();
    _startTracking();

    // Обновление интерфейса каждый секунду
    _updateTimer = Timer.periodic(Duration(seconds: 1), (_) {
      setState(() {
        _currentDistance = _distanceTracker.currentDistance;
      });
    });
  }

  Future<void> _startTracking() async {
    await _distanceTracker.startTracking();
  }

  @override
  void dispose() {
    _updateTimer?.cancel();
    _distanceTracker.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Text(
          'Distance traveled: ${_currentDistance.toStringAsFixed(2)} km',
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

Распространенные проблемы и решения

1. Выполнение в фоновом режиме

Чтобы продолжить отслеживание, когда приложение работает в фоновом режиме, необходимо:

  • Настроить разрешения на фоновое определение местоположения в файлах манифеста приложения
  • Использовать фоновую службу или изоляцию на Android
  • Реализовать фоновые режимы на iOS

2. Оптимизация батареи

Отслеживание местоположения может сильно расходовать заряд батареи. Рассмотрите следующие варианты:

  • Настройку distanceFilter в зависимости от типа активности
  • Снижение точности для менее критичных приложений
  • Реализацию адаптивных скоростей выборки

3. Различия между платформами

Учитывайте, что у iOS и Android разные модели разрешений и поведение:

  • iOS требует явных строк разрешений в Info.plist
  • Android требует разрешений в манифесте и потенциально обработки разрешений во время выполнения

Пакет Geolocator предоставляет мощную основу для отслеживания расстояний в приложениях Flutter. Наша реализация расширяет его возможностями управления разрешениями, фильтрации джиттера и чистого управления ресурсами.

Помните, что точное расчет расстояния — это не просто суммирование GPS-точек. Это требует тщательного управления разрешениями, аккуратной фильтрации шумов и правильного управления потоком позиций. С учетом этих моментов вы можете создавать приложения с поддержкой местоположения, которые обеспечивают надежные измерения расстояний для навигации, фитнес-трекинга или любой функциональности, основанной на движении.


Report Page