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