23 KiB
Оффлайн-карта с треком по WebSocket
Стек: Python (FastAPI) + WebSocket + React (Leaflet + Tauri) Цель: хранить карту полностью локально на устройстве и в реальном времени отображать движение по координатам GNSS.
1. Цели и общие требования
1.1. Функциональные цели
- Отображать карту местности без доступа к интернету.
- Получать от устройства GNSS координаты в реальном времени.
- Передавать координаты в клиентское приложение через WebSocket, без периодических запросов.
- В React-приложении:
- показывать текущую позицию на карте.
- рисовать трек движения (историю точек).
- при необходимости в будущем — отображать дополнительные слои (контуры поля, границы, маршруты).
1.2. Нефункциональные требования
- Полный оффлайн:
- ни карта, ни координаты не должны требовать внешних сервисов.
- все файлы тайлов карты находятся локально на диске устройства.
- Устойчивость:
- временные потери GNSS-сигнала не должны ломать интерфейс.
- разрыв WebSocket-соединения не должен ломать отображение данных с фронта.
- Расширяемость:
- добавлять новые типы данных (например, скорость, курс, высоту).
- подключать реальные GNSS-устройства вместо генераторов.
- добавлять слои на карту: полигоны, точки, маршруты.
- Простота развёртывания:
- backend и frontend должны запускаться стандартными командами.
- конфигурация (путь к карте, параметры GNSS) выносится в настройки.
2. Архитектура решения
2.1. Высокоуровневая схема
Система логически делится на три части:
- Сервис получения GNSS-данных (внутри backend)
- модуль в Python, который:
- подключается к приёмнику GNSS.
- читает сырые данные.
- преобразует их в нормализованный формат (широта, долгота, метки времени).
- модуль в Python, который:
- Backend-сервер (FastAPI)
- HTTP-интерфейсы:
- выдача тайлов карты по пути вида
/tiles/zoom/x/y.png. - вспомогательные эндпоинты (health-check, конфигурация и т.п.).
- выдача тайлов карты по пути вида
- WebSocket-интерфейс:
- постоянное соединение с клиентом.
- отправка координат в режиме “push”.
- работа с файловой системой:
- поиск нужных тайлов.
- выдача заглушки при отсутствии тайла.
- HTTP-интерфейсы:
- Клиент (React + Leaflet)
- модуль карты:
- инициализацирует Leaflet-карту.
- использует локальный URL тайлов (через backend).
- модуль трека:
- поддерживает подключение по WebSocket.
- хранит массив полученных точек.
- обновляет отображение карты при получении новых данных.
- модуль карты:
Связи:
- GNSS → модуль GNSS backend’а → WebSocket → React → Leaflet.
- React → HTTP (через Leaflet) → backend → файловая система с тайлами.
2.2. Логические компоненты
Backend:
- Модуль инициализации приложения FastAPI.
- Модуль выдачи тайлов:
- логика построения пути к тайлу по
{zoom}/{x}/{y}. - проверка наличия файла.
- выдача или оригинального тайла, или заглушки.
- логика построения пути к тайлу по
- Модуль GNSS:
- абстракция источника координат.
- возможность переключить данный источник:
- на реальное устройство.
- Модуль WebSocket:
- управление подключениями клиентов.
- цикл отправки координат.
- обработка разрывов соединения и ошибок. Frontend:
- Модуль подключения WebSocket:
- открытие соединения.
- обработка входящих сообщений с координатами.
- восстановление соединения при разрыве.
- Модуль управления треком:
- добавление точки в список.
- Модуль карты:
- инициализация Leaflet-карты.
- настройка источника тайлов на локальный backend.
- отображение маркера последней точки.
3. Оффлайн-карта: концепция и хранение
3.1. Формат карты
В начальной версии планируется использовать растровые тайлы:
- карта заранее разбивается на набор изображений (PNG или JPEG).
- тайлы организованы в структуре каталогов по схеме:
- уровень масштабирования (zoom).
- координаты тайла по X и Y. Преимущества:
- Leaflet “из коробки” умеет работать с тайлами такого формата.
- не требуется дополнительный сервер картографических данных — достаточно отдавать файлы по HTTP.
- относительно просто генерировать карту для нужного региона с помощью внешних инструментов (QGIS, GDAL и др.).
3.2. Структура хранения тайлов
На уровне файловой системы тайлы лежат в диапазоне, например:
tiles/10/567/321.png— тайл уровня зума 10, координаты X=567, Y=321.tiles/11/1134/643.png— тот же район на другом зуме.
Backend ничего не знает о том, как эти тайлы были созданы, он:
- получает значение zoom, x и y из URL.
- строит путь к файлу.
- проверяет наличие файла.
- отдаёт файл, если он есть.
- если файлы нет — отдаёт заранее подготовленную картинку-заглушку.
Таким образом, карта является полностью статичной, а логика backend — максимально простой.
3.3. Генерация и обновление карты
Это отдельный процесс, который выполняется вне основной системы:
- Выбирается необходимый регион (поля, районы полётов).
- В GIS-инструменте формируется растровая подложка.
- Эта подложка нарезается в тайлы нужных уровней масштабирования.
- Готовый набор тайлов копируется на устройство в папку
tiles/.
В дальнейшем можно:
- обновлять карту, подменяя содержимое
tiles/. - хранить несколько наборов карт (например, по сезонам) и включать нужный через конфиг.
4. Работа с координатами и GNSS
4.1. Источник данных
Фактически координаты приходят либо:
- от реального GNSS-приёмника, подключённого по:
- последовательному порту.
- USB.
- сетевому протоколу.
- либо от программного симулятора, который генерирует “виртуальный маршрут”.
Задача backend — предоставить единый интерфейс, не зависящий от конкретного железа.
4.2. Нормализация координат
Сырые данные могут быть в формате NMEA или другом специфичном виде. Модуль GNSS:
- парсит входные данные.
- приводит координаты к стандартной системе:
- широта (latitude) в градусах.
- долгота (longitude) в градусах.
- опционально — высота, скорость, курс.
- добавляет метку времени (например, UNIX timestamp или ISO-строку).
- формирует структуру, удобную для отправки на фронт.
Важно, чтобы координаты были в той системе, которую ожидает Leaflet (обычно WGS84).
4.3. Частота обновления
- Модуль GNSS определяет:
- как часто появляются новые точки.
- как часто обновлять данные на фронте:
- возможен вариант “как пришло — так отправили”.
- либо накопление точек и отправка пачками (например, раз в секунду).
Частота обновления — важный параметр, влияющий на:
- плавность трека.
- нагрузку на канал связи.
- нагрузку на фронт.
5. Потоки данных и жизненный цикл приложения
5.1. Запуск backend
- При старте FastAPI:
- инициализирует конфигурацию (настройки путей, порты, параметры GNSS).
- проверяет наличие базовых директорий (
tiles/, заглушка тайла). - при необходимости пробует подключиться к GNSS-источнику.
- Запускается HTTP-сервер:
- обслуживает выдачу тайлов.
- отвечает на запросы.
5.2. Запуск frontend
- React-приложение загружается в оболочке Tauri (Бинарник)
- После инициализации:
- открывается WebSocket-соединение с backend.
- инициализируется карта:
- начальный центр (по умолчанию либо по первой полученной точке).
- указание backend-урла тайлов.
- При успешном подключении по WebSocket:
- приложение начинает получать координаты.
- каждая новая точка добавляется в список.
- карта обновляет положение маркера и линии трека.
5.3. Реакция на ошибки и разрывы
- Если WebSocket-соединение закрывается:
- фронт может попытаться переподключиться через интервалы.
- на карте можно оставить последнюю известную позицию.
- Если нет GNSS-данных:
- backend может отправлять специальные сообщения о состоянии (например, “нет сигнала”).
- фронт может отображать статичную карту без движения или соответствующее уведомление.
- Если тайл для карты отсутствует:
- backend отдаёт заглушку.
- на карте вместо участка появляется условный “серый” квадрат, но интерфейс остаётся рабочим.
6. Основные этапы разработки
Этап 1. Подготовка окружения и структуры проекта
- Создать общий репозиторий с двумя основными директориями:
backendиfrontend. - Настроить базовую систему сборки и запуска:
- для backend — запуск FastAPI через Uvicorn.
- для frontend — сборщик (Vite + Tauri).
Этап 2. Скелет backend-приложения
- Инициализировать приложение FastAPI.
- Добавить endpoint для проверки, что сервер поднялся.
- Настроить CORS так, чтобы frontend мог связаться с backend в локальной разработке
На этом этапе можно убедиться, что базовая связка “frontend - backend по HTTP” работает.
Этап 3. Механизм выдачи тайлов
- Определить директорию хранения тайлов (
tiles/). - Реализовать логику извлечения тайла по запросу вида
/tiles/{zoom}/{x}/{y}.png. - Добавить заглушку для отсутствующих тайлов.
- Прогнать простой тест: вручную запросить существующий и несуществующий тайл и убедиться, что:
- первый отдаётся корректно.
- второй отдаёт заглушку.
Этап 4. Проработка GNSS-абстракции
- Определить интерфейс модуля “источник координат”:
- формат выдаваемых данных.
- тип возвращаемой структуры (координаты + время).
- На этом этапе использовать симулятор вместо реального устройства:
- сделать виртуальную траекторию (линия, круг, восьмёрка и т.п.).
- выдавать новые точки с заданным интервалом.
- Важно: модуль должен быть реализован так, чтобы позже его можно было легко заменить реализацией, работающей с реальным железом.
Этап 5. Добавление WebSocket-эндпоинта в backend
- Создать WebSocket-обработчик:
- принимать подключение от клиента.
- при подключении запускать цикл, в котором:
- берётся следующая точка из GNSS-источника.
- отправляется на клиента.
- Обработать возможные сценарии:
- разрыв соединения клиентом.
- ошибки в процессе отправки.
- отключение GNSS-источника.
На этом этапе backend уже умеет:
- выдавать тайлы.
- в реальном времени отдавать поток координат любому подключенному клиенту по WebSocket.
Этап 6. Скелет frontend-приложения
- Инициализировать React-приложение.
- Подготовить базовую структуру компонентов:
- основной компонент приложения.
- компонент карты.
- будущий модуль для WebSocket.
- Настроить запуск фронта и убедиться, что базовый “каркас” работает.
Этап 7. Интеграция Leaflet в frontend
- Подключить Leaflet и его React-обёртку.
- Создать компонент карты, который:
- инициализирует карту с некоторым стартовым центром.
- использует URL
http://localhost:8000/tiles/{z}/{x}/{y}.pngв качестве источника тайлов.
- Проверить:
- что при старте приложение отображает карту (в рамках подготовленной области).
- что при перемещении и масштабировании запросы к тайлам уходят на backend.
На этом этапе карта уже отображается локально, без участия внешних сервисов.
Этап 8. Подключение WebSocket на frontend
- Создать модуль, который:
- открывает WebSocket-соединение с backend’ом по адресу
ws://localhost:8000/ws/track. - слушает сообщения и добавляет новые координаты в локальное состояние приложения.
- открывает WebSocket-соединение с backend’ом по адресу
- Реализовать простую логику:
- хранить массив точек.
- по каждой новой точке:
- добавлять её в массив.
- обновлять центр карты.
- Связать модуль WebSocket с компонентом карты:
- передавать массив точек как пропсы.
- отображать:
- маркер последней точки.
- линию, соединяющую все точки (трек).
Этап 9. Обработка ошибок и повышение устойчивости
- Добавить обработку ошибок WebSocket на frontend:
- логировать причины разрыва.
- реализовать простую стратегию повторного подключения (например, попытка подключиться раз в несколько секунд при разрыве).
- На backend:
- в явном виде логировать ошибки GNSS и WebSocket.
- при необходимости — реализовать таймауты и защиту от зависаний.
Этап 10. Подключение реального GNSS-устройства
- Вместо симулятора реализовать модуль чтения реальных данных:
- добавить конфиг с параметрами подключения: порт, скорость, протокол.
- реализовать парсинг данных.
- Убедиться, что внешний интерфейс модуля GNSS не изменился:
- по-прежнему выдаются координаты в ожидаемом формате.
- Протестировать работу системы “в поле”:
- убедиться, что карта и трек корректно отражают реальное движение.
Этап 11. Оптимизация и подготовка к эксплуатации
- Оптимизация производительности:
- ограничение длины трека, если ожидается очень длинное использование без перезапуска.
- настройка частоты обновления координат.
- Упаковка приложения:
- вариант 1: backend + frontend как отдельные пакеты.
- вариант 2: объединение фронта и бека в один пакет.
- Документация по развёртыванию:
- пошаговое руководство по установке зависимостей.
- инструкции по замене тайлов карты.
- инструкции по смене GNSS-источника.
7. Возможные дальнейшие расширения
После того как базовый функционал будет устойчиво работать, его можно расширять:
- Отображение контуров полей
- загрузка контуров из локальных файлов (например, GeoJSON).
- визуализация границ поля поверх карты.
- Планирование маршрута
- формирование линии проходов (полос) по ширине захвата.
- привязка трека к этим линиям (показ отклонения).
- Запись и экспорт данных
- сохранение трека в файл (например, GeoJSON, GPX).
- экспорт для дальнейшего анализа.
- Конфигурация через UI
- настройка масштабов карты, уровней тайлов.
- переключение наборов карт (разные сезоны, регионы).
8. Итоговая сводка
Суть решения: на устройстве устанавливается локальный сервер на Python (FastAPI), который одновременно:
- отдаёт статические файлы тайлов карты с диска,
- и по WebSocket непрерывно передаёт координаты из приёмника ГЛОНАСС.
React-приложение с Leaflet открывает эту локальную карту (через HTTP) и подписывается на WebSocket, чтобы в реальном времени обновлять положение маркера и трек движения. Всё — от карты до координат — живёт локально, не зависит от интернета и может работать в любых условиях, где есть только устройство, приёмник и локальная сеть (или даже один компьютер).