История о том, как боль от «телепортов» в GPS‑треках, платных зон и неудобного создания файлов для Garmin привела к рождению pet‑проект а, который немного вышел из-под контроля. Разбор стека, подводных камней и немного партизанского кодинга.

Всем привет! Меня зовут Александр, я разработчик и, как многие в IT-сообществе, увлеченный велосипедист. Strava для меня — основной инструмент для отслеживания прогресса, но с годами я стал все чаще натыкаться на мелкие «но», которые мешали полноценному анализу.
Знакомы эти боли?
«Телепорты» в GPX‑треках. Проехал туннель или попал в «мертвую» зону GPS, и вот на треке уже красуется прямая линия на пару километров, которая убивает всю статистику.
Анализ по зонам. Strava предоставляет его только по платной подписке. А если у тебя свои, кастомные зоны, рассчитанные тренером? Увы.
Live Segments на Garmin. Чтобы гоняться с «призраком» (своим PR или KOM'ом) на сегменте, нужно вручную скачивать его с сайта, конвертировать и загружать на устройство. Утомительно.
В какой‑то момент количество этих «но» превысило критическую массу, и во мне проснулся разработчик. Я решил: «А что, если сделать инструмент, который решит все эти проблемы для меня?». Так и родился Peakline.
Сегодня я хочу рассказать, как из этой идеи вырос полноценный веб‑сервис, с какими техническими вызовами я столкнулся и что получилось в итоге.

Технологический стек: просто, быстро, асинхронно
Для такого проекта не хотелось тащить тяжелые фреймворки. Выбор пал на связку, которая позволяет быстро разрабатывать и легко поддерживать сервис в одиночку:
Бэкенд: FastAPI на Python. Асинхронность «из коробки» идеальна для работы с внешними API, а Pydantic — для валидации данных.
Фронтенд: Vanilla JS. Никаких React или Vue. Для такого интерфейса достаточно «чистого» JS, Chart.js для графиков и Leaflet для карт. Шаблоны рендерятся на сервере с помощью Jinja2.
База данных: SQLite. Да‑да, для pet‑проекта, который может и не взлететь, это идеальный выбор. Никаких забот с настройкой и администрированием.
Работа с API: AIOHTTP для асинхронных запросов к Strava и requests для редких синхронных задач.
Киллер-фичи и как они устроены "под капотом"
1. FIT-генератор Pro-уровня: Гонка с целью
Это была одна из главных задач. Я хотел нажать одну кнопку и получить готовый.fit файл для Garmin, где в качестве соперника будет не просто абстрактный лидер, а конкретная цель.

Проблема: Формат FIT — это бинарный протокол. Просто так его не сгенерируешь.
Решение: Я нашел отличную, хоть и не очень известную, библиотеку fit‑segment‑encoder. Она берет на себя всю грязную работу по упаковке данных в бинарный формат. Моя задача свелась к тому, чтобы правильно подготовить данные.
Вот как это работает:
Пользователь нажимает «Скачать FIT» и в модальном окне выбирает цель: KOM/QOM, свой PR или вводит время вручную (например, 5m30s).
Бэкенд получает детали сегмента через Strava API, включая трек (потоки latlng, distance, altitude) и время KOM/PR.
Затем для каждой точки трека рассчитывается «идеальное» время прохождения, исходя из выбранной цели и общей дистанции (пока что линейно).
Все эти данные (координаты, высота, дистанция, время лидера) скармливаются FitSegmentEncoder.
Библиотека генерирует байты, которые я отдаю пользователю с правильным MIME‑типом application/vnd.ant.fit.

2. Визуальный GPX Fixer
«Телепорты» в треках — моя личная боль. Я решил, что просто чинить их мало, нужно показывать, что именно было исправлено.
Проблема: Как найти «прыжок» в треке и как наглядно это показать?
Решение:
Бэкенд: Использую библиотеку gpxpy для парсинга загруженного GPX. Прохожу по всем точкам трека и вычисляю расстояние между соседними. Если point.distance_2d(last_point) > threshold_meters, считаю это «прыжком» и начинаю новый сегмент в GPX‑файле.
Фронтенд: После обработки бэкенд возвращает не только исправленный файл, но и два полилайна (закодированных в polyline): оригинальный и исправленный. На фронте я просто рисую на карте Leaflet два слоя: красный (сломанный трек) и зеленый (исправленный). Сразу видно, какие «петли» и «прыжки» были удалены.

3. Интерактивная связка "Карта-График"
На странице анализа тренировки хотелось не просто смотреть на графики мощности или пульса, но и понимать, где именно на маршруте был тот или иной пик.
Проблема: Как синхронизировать положение курсора на графике с маркером на карте?
Решение: Это магия JavaScript.
Chart.js предоставляет колбэк onHover. Он срабатывает при наведении мыши на график и передает информацию об активных элементах.
Из этого события я получаю dataIndex — индекс точки данных, на которую наведен курсор.
У меня уже есть массив с GPS-координатами (latlng stream), полученный от Strava API. Я просто беру из этого массива координаты с тем же индексом: const coords = latlngStream[dataIndex].
Остается только обновить позицию заранее созданного маркера на карте Leaflet (trackMarker.setLatLng(coords)) и добавить его на карту, если он еще не там. Когда курсор уходит с графика, я просто убираю маркер с карты.
Эффект получился лучше чем я полагал.
4. Детальный анализ активностей и кастомные зоны
Strava предоставляет отличный базовый анализ, но если вы хотите копать глубже, начинаются нюансы. Например, зоны мощности и пульса рассчитываются по стандартным формулам. А если у вас есть данные с лабораторного теста или вы используете методику, отличную от стандартной?
Решение:
Кастомные зоны: Я добавил в профиле пользователя страницу настроек, где можно вручную задать границы для каждой из 5 зон пульса и 7 зон мощности. Эти данные сохраняются в нашей базе SQLite.
-
Умный расчет: Когда вы открываете страницу анализа тренировки, бэкенд делает следующее:
Запрашивает у Strava все доступные потоки данных (streams) для активности: пульс, мощность, скорость, каденс и т.д.
Проверяет, задал ли пользователь свои кастомные зоны.
Если да — время в зонах рассчитывается на сервере с помощью Python (numpy отлично подходит для быстрых вычислений по массивам данных).
Если нет — мы используем данные, которые отдает Strava API (если у пользователя есть подписка) или считаем по стандартным процентам от FTP/Max HR.
Это дает пользователю полный контроль над анализом, а мне — интересный вызов по работе с "сырыми" данными.

5. Segment Hunter: Поиск новых вызовов
Иногда хочется найти новые интересные сегменты для тренировок рядом с домом, но интерфейс Strava для этого не всегда удобен.
Решение: Я сделал интерактивную карту "Охотника за сегментами".
Пользователь перемещает карту в нужную область.
При нажатии на кнопку «Найти» фронтенд отправляет на бэкенд координаты видимой области карты (bounds).
Бэкенд использует эндпоинт Strava API /segments/explore, который возвращает сегменты в заданном прямоугольнике.
Найденные сегменты отображаются на карте иконками. При наведении на иконку подсвечивается трек сегмента, а при клике — открывается попап с его статистикой и кнопками для скачивания FIT‑файла или перехода на Strava.
Просто, но очень эффективно для планирования новых маршрутов.

6. Экспериментальные функции: ищем создателя сегмента
А теперь немного о функциях «для своих». В Strava нет официального способа узнать, кто создал тот или иной сегмент. Иногда это полезно, чтобы понять логику создателя или просто из любопытства. Я решил, что эту информацию можно достать, но доступ к ней будет не для всех.

Проблема: Официальное API V3 не отдает ID создателя сегмента.
Решение: «Партизанский» подход. Я обнаружил, что старые, недокументированные эндпоинты, которые используются внутренним фронтендом Strava, все еще работают, если отправить запрос с правильными Cookie. Достать их можно из своего браузера.

Как это работает в Peakline:
«Секретный клуб»: Чтобы получить доступ к этой и другим экспериментальным фичам, пользователь должен привязать свой Telegram‑аккаунт к профилю Peakline. Это мой способ ограничить доступ к «серым» функциям и дать их только самым заинтересованным пользователям.
Пул аккаунтов: На сервере у меня есть пул из нескольких «технических» аккаунтов Strava, для каждого из которых сохранен Cookie заголовок.
Запрос: Когда пользователь из «клуба» запрашивает создателя сегмента, бэкенд берет куки из пула и делает синхронный requests‑запрос на старый эндпоинт (/athlete/segments/{id}/history).
Отказоустойчивость: Если один аккаунт из пула забанят или его сессия истечет, система автоматически переключится на следующий. Это обеспечивает относительную стабильность функции.
Что в итоге?
В результате этих изысканий родился проект Peakline (ссылка активная, можно заходить и пробовать).
Что уже работает:
Личный кабинет с полной статистикой и последними активностями.
Анализ сегментов с интерактивной картой и профилем высот.
Скачивание FIT и GPX файлов для Garmin с выбором цели.
GPX Fixer для исправления «телепортов».
Segment Hunter для поиска новых вызовов на карте.
Ручная настройка зон мощности и пульса для точного анализа.
Планы на будущее и обратная связь
Проект только в начале пути. В планах добавить более глубокий AI‑анализ и много других мелочей. Этим проектом я занимаюсь в свободное время, но он вырастет благодаря отзывам сообщества.
Буду рад, если вы попробуете Peakline и поделитесь своими впечатлениями, идеями или сообщениями об ошибках прямо в комментариях к этой статье. Спасибо за внимание, Хабр!
Комментарии (10)
pnmv
20.06.2025 17:24Порадовала подсветка кода, на снимках экрана. Цветовая схема - для настоящих красноглазиков.
megalloid
20.06.2025 17:24Правда качество работы GPS сейчас "малость" расстраивает...
cyberscoper Автор
20.06.2025 17:24В целом? или конкретно нюансы в работе моего "фиксера"
Kwisatz
20.06.2025 17:24во многих городах, например в Казани GPS просто бесполезен во всем центре, например.
По статье: очень круто, вы кросавчег)
cyberscoper Автор
20.06.2025 17:24Благодарю.
Я тут и за фронт и за бэк у проекта есть достаточное количество проблем но они решаемы.
А вот с GPS - мало что поделать это грустно
cmyser
20.06.2025 17:24Тут бы локал first идеально зашел
Чисто с телефона данные получать о перемещениях
Знаю одну возможность сделать это в браузере
GAG
Вижу, что пока ещё не всё переведено на русский, но уже радует, что нет искусственных достижений по количеству пользователей и обработанной информации.
Выглядит правдоподобно:
Our Community in Numbers
Unique Visitors
246
Happy Athletes
7
Activities Analyzed
26
FIT Files Created
4
cyberscoper Автор
Да, это верно вот параллельно добавляю перевод.
cyberscoper Автор
так то лучше выглядит :)