649 lines
48 KiB
Markdown
649 lines
48 KiB
Markdown
# Находки по расчётной модели GPS (2026-09-07)
|
||
|
||
Получено при независимом разборе базы `baldav.db` (156 поездок, 366 тыс. точек)
|
||
сторонним инструментом `tools/track/`. Константы там взяты из `GPS.computeStats`,
|
||
поэтому расхождения означают разницу в модели, а не в настройках.
|
||
|
||
Проверка сходимости: дистанция расходится с базой на **0.2%**, калории по формуле
|
||
приложения -- на **4%** (трек 4 сентября). То есть база и формула воспроизводятся,
|
||
и нижеперечисленное -- не ошибки чтения.
|
||
|
||
Пункты про **нулевую отметку высоты** и **паузу на подъёме** сюда не включены:
|
||
они уже обсуждены отдельно.
|
||
|
||
Файл состоит из двух частей. **Первая -- находки:** измеренные расхождения,
|
||
каждое с замером и по возможности с контрольной проверкой. **Вторая --
|
||
предположения:** варианты отслеживания ветра, составленные по просьбе
|
||
пользователя, с вопросами к вам в конце. Смешивать их не стоит: первая часть
|
||
проверена на данных, вторая -- это перечень возможностей, а не рекомендация.
|
||
|
||
---
|
||
|
||
## 1. Потолок качения 0.008 занижен втрое
|
||
|
||
`crr_from_accel`: при `accel_rms > 7` возвращается 0.008 и выше не поднимается.
|
||
0.008 -- это разбитый асфальт. Реальные грунты дают втрое больше.
|
||
|
||
**Замер.** Трек 6 сентября (`id 1788683393199`), километры 36.5-38.5: 1996 м,
|
||
ровно (уклон −0.1%), `accel_rms` медиана **15.1**, скорость 13.8 км/ч.
|
||
Пользователь описал место как траву и тракторную колею с ямами.
|
||
Обратное решение уравнения мощности относительно Crr:
|
||
|
||
| если мощность была | требуемый Crr |
|
||
|---|---|
|
||
| 90 Вт | 0.0198 |
|
||
| 110 Вт | 0.0252 |
|
||
| 130 Вт | 0.0307 |
|
||
|
||
Справочные значения: плотный грунт 0.012, щебень 0.020, трава и песок 0.035.
|
||
Замер попадает ровно в диапазон, соответствующий описанию.
|
||
|
||
**Контрольная проверка обратным знаком.** Те же вычисления на километрах 9-11
|
||
(25 км/ч, тряска 5.2) дают Crr отрицательным при 90-110 Вт -- значит там
|
||
мощность была около 150 Вт. Сплиты на тех километрах показывают 148-172 Вт.
|
||
Метод не завышает и не занижает систематически.
|
||
|
||
**Предлагаемая шкала** (взята нижняя граница замера -- лучше недосчитать):
|
||
|
||
< 3 -> 0.004 гладкий асфальт (как сейчас)
|
||
3-7 -> 0.006 смешанное (как сейчас)
|
||
7-11 -> 0.008 разбитый асфальт (как сейчас)
|
||
11-15 -> 0.012 плотный грунт (новое)
|
||
> 15 -> 0.020 трава, колея, щебень (новое)
|
||
|
||
**Влияние на трек 6 сентября:** средняя мощность 89 -> 108 Вт,
|
||
механическая работа 1233 -> 1585 кДж. То есть на грунтах модель
|
||
недосчитывала примерно четверть работы.
|
||
|
||
## 2. `max(physics, MET)` никогда не складывает, и подъёмы от этого страдают
|
||
|
||
На одних и тех же отрезках трека 6 сентября:
|
||
|
||
| участки | физика | MET | MET/физика |
|
||
|---|---|---|---|
|
||
| вся поездка | 1318 | 1764 | 1.34 |
|
||
| равнина, \|уклон\| < 1% | 727 | 1344 | **1.85** |
|
||
| в горку круче 2% | 400 | 165 | **0.41** |
|
||
|
||
Комментарий в коде говорит про «~2x занижение физики» -- на равнине это верно
|
||
(1.85), но по поездке в целом всего 1.34, потому что на подъёмах физика
|
||
выигрывает с большим запасом.
|
||
|
||
Изъян в том, что оценки не складываются. Биологические накладные расходы
|
||
(размахивание ногами, равновесие, гашение тряски корпусом) никуда не деваются
|
||
на подъёме, но там берётся физика и табличная оценка отбрасывается целиком.
|
||
**Направление ошибки известно: подъёмы недосчитываются.**
|
||
|
||
Осмысленная альтернатива, если решите менять: не максимум, а
|
||
`физика/КПД + накладные`, где накладные оцениваются табличным минимумом
|
||
для медленной езды. Требует калибровки, поэтому просто как направление.
|
||
|
||
## 3. Ходьба с велосипедом засчитывается как езда
|
||
|
||
На односкоростном передача даёт точный признак: 40/18 на колесе 24" --
|
||
это 4.26 м за оборот шатунов. Скорость ниже **7.7 км/ч** означает каденс
|
||
ниже 30 об/мин, а на таком каденсе велосипед не едет, он падает. Значит
|
||
человек идёт и ведёт или несёт его.
|
||
|
||
На треке 6 сентября таких кусков **0.66 км за 8:44 в движении** -- пользователь
|
||
подтвердил, что нёс велосипед руками в горку. Участок: км 56.31, длина 289 м,
|
||
набор 47.8 м, **уклон 16.6%**, 4.3 км/ч.
|
||
|
||
Побочный эффект для статистики: если этот кусок не отделять, он склеивается
|
||
с последующим ездовым подъёмом (км 56.60, 624 м, 3.8%, 12.3 км/ч) в один
|
||
«подъём 915 м, 7.8%, 7.7 км/ч», которого в природе не было.
|
||
|
||
Признак применим только когда `bikes.gear_count` пусто или 1. На многоскоростном
|
||
низкая скорость ничего не доказывает.
|
||
|
||
**По акселерометру это не различается, проверено:** нести велосипед в гору 3.24,
|
||
ехать по гладкому асфальту 2.88, идти пешком (сессия walk) 3.98. `accel_rms`
|
||
показывает дорогу, а не походку.
|
||
|
||
## 4. Набор высоты расходится с независимым пересчётом в обе стороны
|
||
|
||
| трек | в базе | независимый пересчёт |
|
||
|---|---|---|
|
||
| 6 сентября | 328 м | 528 м |
|
||
| 4 сентября | 314 м | 264 м |
|
||
|
||
Знак разный, значит это не простое смещение. Без модели рельефа проверить
|
||
нечем: попытка построить карту высот по своим же 156 поездкам провалилась --
|
||
перекрытие маршрутов всего 26% ячеек, а разброс высоты между поездками
|
||
в одной и той же точке имеет медиану **10 метров**.
|
||
|
||
**Поправка после уточнения у пользователя.** Изначально я счёл пустую
|
||
`altitude_srtm_m` (заполнена в одной сессии из 156) недоработкой. Это не так:
|
||
поле заполняется вручную кнопкой «уточнить высоты», которая ходит в GPXZ
|
||
и подобные сервисы, и пользователь жмёт её редко. Полагаться на сервисы
|
||
в поле нельзя -- РЭБ и РКН глушат, устойчиво работает преимущественно
|
||
проводной интернет.
|
||
|
||
Отсюда следует не «починить SRTM», а вопрос по существу: **расхождение
|
||
в наборе высоты остаётся неразрешённым, и разрешить его без внешнего рельефа
|
||
нечем.** Если это важно -- имеет смысл думать в сторону предварительной
|
||
загрузки рельефа по району дома, по проводу, один раз. Если не важно --
|
||
достаточно знать, что цифра набора имеет точность порядка десятков процентов,
|
||
и не строить на ней выводов. На расчёт мощности это влияет слабо: там работает
|
||
уклон, а он считается по окну и ограничен сверху.
|
||
|
||
## 5. Пульса в точках нет, и это ожидаемо
|
||
|
||
`gps_points.heart_rate` равен нулю во всех 156 сессиях. **Это не дефект
|
||
записи:** пользователь потерял браслет и пульсометр не носит, писать в поле
|
||
попросту нечего. `ble_hr_data.json` и запись устройства в `profile`
|
||
остались от прежнего использования.
|
||
|
||
Упомянуто только затем, чтобы никто не искал ошибку там, где её нет,
|
||
и чтобы разбор треков не обещал анализ по пульсу.
|
||
|
||
## 6. Ветер вычисляется из `bearing_deg`
|
||
|
||
Побочная находка, в приложении такого нет. Ветер виден по тому, что на ровных
|
||
однотипных дорогах скорость зависит от курса. Берутся только точки с ровным
|
||
уклоном, невысокой тряской и в седле, считается медианная скорость по восьми
|
||
секторам курса, и подбирается ветер, при котором подразумеваемая мощность
|
||
во всех секторах одинакова.
|
||
|
||
Трек 6 сентября: **6.3 км/ч из направления 345°**. Разброс мощности
|
||
по направлениям падает с 33% до 11%. Пользователь независимо подтвердил,
|
||
что ветер часто дул в лицо.
|
||
|
||
Медианная скорость по курсу на этом треке -- 23.6 км/ч на юго-восток
|
||
против 15.7 км/ч на северо-запад, при одинаковых дорогах.
|
||
|
||
Второй, независимый метод -- по встречным проездам одного места (758 пар)
|
||
-- даёт 3.6 км/ч из 325°. Направление сходится в пределах 20°, сила --
|
||
в пределах множителя 1.8. Это и есть достижимая точность оценки ветра
|
||
без датчика.
|
||
|
||
Разбор вариантов отслеживания ветра, включая идею пользователя про голосовые
|
||
отметки, -- во второй части этого файла. Там же перечислены вопросы,
|
||
по которым пользователь просит ваше мнение.
|
||
|
||
## 7. Ловушка при отделении ходьбы: порог по длительности не нужен
|
||
|
||
Найдено после замечания пользователя, что встречные проезды одного места
|
||
случались и пешком.
|
||
|
||
Первая реализация признака ходьбы требовала, чтобы медленный кусок длился
|
||
дольше 30 секунд подряд. Это оказалось неверно. На треке 6 сентября всего
|
||
**14.7 минут** движения медленнее 7.7 км/ч, из них сплошными кусками --
|
||
только 8.7 минуты. Оставшиеся **6 минут** -- короткие эпизоды, и они попадали
|
||
в статистику как педалирование.
|
||
|
||
Видно это по корзине каденса: «0-40 об/мин» занимала 21 минуту, и **6.4 минуты
|
||
из неё приходилось на каденс ниже 30**, которого при езде не бывает вовсе.
|
||
После исправления корзина стала 9 минут, а средний каденс поднялся с 64 до 67.
|
||
|
||
**Правильное разделение двух вопросов:**
|
||
|
||
- *Исключать из статистики* нужно **любую** точку ниже порога, без оглядки
|
||
на длительность: на этой передаче ниже 30 об/мин не едут, и точка. Сюда же
|
||
попадут обычные торможения до нуля, но они короткие и маломощные, вреда нет.
|
||
- *Называть кусок ходьбой в отчёте* стоит только при длительности дольше
|
||
30 секунд подряд -- иначе отчёт будет объявлять ходьбой каждое торможение
|
||
перед ямой.
|
||
|
||
На оценку ветра эта примесь, к счастью, не повлияла: там отдельный порог
|
||
9 км/ч, и пешие точки не проходили. Проверено прямым сравнением -- с ними
|
||
3.5 км/ч из 326°, без них 3.6 км/ч из 325°.
|
||
|
||
|
||
---
|
||
---
|
||
|
||
# ЧАСТЬ ВТОРАЯ: предложения по отслеживанию ветра
|
||
|
||
Ниже -- **не находки, а предположения**, составленные по просьбе пользователя.
|
||
Это перечень способов с замерами там, где замеры удалось сделать.
|
||
Решение принимает сессия разработки.
|
||
|
||
## Зачем
|
||
|
||
Ветер -- крупнейший неучтённый источник ошибки в расчёте мощности на равнине.
|
||
На треке 6 сентября медианная скорость на одинаковых дорогах составила
|
||
**23.6 км/ч на юго-восток против 15.7 км/ч на северо-запад**. Разброс
|
||
подразумеваемой мощности по направлениям -- **33%**. Пока ветра в модели нет,
|
||
эти 33% списываются на «просел» и «разогнался».
|
||
|
||
## Ограничения, названные пользователем
|
||
|
||
- **Сетевые сервисы в поле недоступны.** РЭБ и РКН глушат, устойчиво работает
|
||
преимущественно проводной интернет. Любая схема, которой нужна сеть во время
|
||
поездки, нерабочая.
|
||
- **Метеостанция меряет в другой точке.** Стандартное измерение -- на высоте
|
||
10 м в открытом месте, усреднение за 10 минут. Велосипедист едет на высоте
|
||
1.5 м среди лесополос, домов и насыпей, где ветер и слабее, и порывистее.
|
||
Среднее по области пользователю не поможет.
|
||
|
||
Оба возражения справедливы и отсекают наивное «дёргать погодный API».
|
||
|
||
## Вариант 1. Считать ветер из самого трека (без железа и без сети)
|
||
|
||
Реализовано и проверено в `tools/track/track.py`. Два независимых метода.
|
||
|
||
**1а. Подбор по секторам курса.** Берутся только точки на ровном участке,
|
||
с невысокой тряской, в седле. Медианная скорость считается по восьми секторам
|
||
направления. Подбирается такой ветер, при котором подразумеваемая мощность
|
||
во всех секторах одинакова.
|
||
|
||
**1б. Встречные проезды одного места.** Ищутся пары точек в одной ячейке 30 м,
|
||
разнесённые по времени, с противоположными курсами, одинаковым покрытием
|
||
и зеркальным уклоном. На одной и той же дороге качение и уклон сокращаются,
|
||
и разница скоростей при равной мощности задаёт составляющую ветра вдоль дороги.
|
||
Составляющие по разным дорогам сводятся в вектор методом наименьших квадратов.
|
||
|
||
**Результат на треке 6 сентября:**
|
||
|
||
| метод | скорость | направление |
|
||
|---|---|---|
|
||
| по секторам курса | 6.3 км/ч | 345° |
|
||
| по встречным проездам (758 пар) | 3.6 км/ч | 325° |
|
||
|
||
Направление сходится в пределах 20°, сила -- в пределах множителя 1.8.
|
||
|
||
**Это и есть достижимая точность способа без железа: направление уверенно,
|
||
сила -- с точностью до двух раз.** Любой вариант с датчиком должен
|
||
эту планку превосходить, иначе он не нужен.
|
||
|
||
Ограничения: работает только задним числом, требует разнообразия курсов
|
||
(кольцевой маршрут или «туда-обратно» -- идеально, прямая в одну сторону --
|
||
бесполезно), и предполагает, что ветер за поездку не менялся. Последнее
|
||
лечится подгонкой в скользящем окне 20-30 минут, но точность падает.
|
||
|
||
## Вариант 2. Прогноз, скачанный ЗАРАНЕЕ по проводу
|
||
|
||
Обходит запрет на сеть в поле: перед выездом, на домашнем проводном интернете,
|
||
скачивается почасовой прогноз ветра по району и кладётся в базу. В поездке сеть
|
||
не нужна вообще.
|
||
|
||
Прогноз при этом используется **не как истина, а как начальное приближение**
|
||
для варианта 1: он задаёт правдоподобный диапазон, а трек его уточняет.
|
||
Возражение про «меряют в другой точке» остаётся в силе, но начальное
|
||
приближение с известным смещением лучше, чем поиск вслепую.
|
||
|
||
Спорный момент: прогноз может оказаться хуже, чем ничего, если приземный ветер
|
||
в лесополосах отличается от станционного в разы. Стоит проверить на нескольких
|
||
треках, прежде чем закладывать.
|
||
|
||
## Вариант 3. Трубка Пито на руле (единственный способ мерить по-настоящему)
|
||
|
||
Так устроены настоящие аэродинамические измерители для велосипеда (Notio,
|
||
Aeropod). Трубка, направленная вперёд, плюс датчик разности давлений дают
|
||
**скорость набегающего воздуха напрямую**, с частотой в десятки герц.
|
||
|
||
Разность между воздушной скоростью и путевой скоростью с GPS, вместе с курсом,
|
||
даёт вектор ветра непрерывно, включая порывы. Сети не требуется вообще.
|
||
|
||
- датчик разности давлений класса MPXV7002DP плюс микроконтроллер с BLE
|
||
- порядок цены комплектующих -- полторы-три тысячи рублей
|
||
- BLE в приложении уже есть, канал знакомый
|
||
- нужна калибровка (сравнением с вариантом 1 на кольцевом маршруте в штиль)
|
||
и защита от дождя и грязи
|
||
- трубка на руле уязвима при падении и при переноске велосипеда
|
||
|
||
**Это единственный из перечисленных способов, который измеряет, а не оценивает.**
|
||
И единственный, который видит порывы, а не среднее.
|
||
|
||
## Вариант 4. Флюгер на руле
|
||
|
||
Дешевле Пито: измеряется только **угол** набегающего потока относительно
|
||
велосипеда, обычным поворотным датчиком. Скорости ветра не даёт, но сильно
|
||
сужает решение в варианте 1 -- направление перестаёт быть неизвестным,
|
||
остаётся подобрать одну величину вместо двух.
|
||
|
||
На велосипеде хрупок и чувствителен к завихрениям от самого седока.
|
||
|
||
## Вариант 5. Голосовой ввод по словам-триггерам (идея пользователя)
|
||
|
||
Седок произносит «в лицо», «в спину», «боковой», «порыв», приложение ставит
|
||
отметку времени. Распознавание на Android умеет работать без сети.
|
||
|
||
**Замечание, которое считаю важным.** Как измерение это слабо: оценка
|
||
субъективна, отметки редкие, а говорить на тяжёлом дыхании и в шуме ветра
|
||
неудобно -- причём шум ветра в микрофоне мешает именно тогда, когда ветер
|
||
и надо отметить.
|
||
|
||
Но у этого есть куда более сильное применение: **не как измерение,
|
||
а как разметка для проверки автоматической оценки.** Десяток отметок
|
||
«в лицо» и «в спину» за поездку -- это готовый ответ, по которому можно
|
||
проверить и откалибровать вариант 1, не покупая железа. Если оценка по треку
|
||
совпадает с отметками -- ей можно верить и без отметок.
|
||
|
||
При таком применении редкость и субъективность отметок почти не мешают:
|
||
проверяется знак и порядок величины, а не точное значение.
|
||
|
||
## Вариант 6. Кнопка вместо голоса
|
||
|
||
То же самое, но нажатием: выносная кнопка на руле по BLE либо кнопка
|
||
на гарнитуре. Надёжнее голоса на тяжёлом дыхании и в шуме, но выражает меньше
|
||
-- реально различимы два-три состояния, не больше.
|
||
|
||
## Вариант 7. Шум ветра в микрофоне
|
||
|
||
Обдув микрофона даёт характерный низкочастотный шум, растущий со скоростью
|
||
потока. Теоретически из него извлекается воздушная скорость.
|
||
|
||
Практически: в кармане бесполезно, нужен вынесенный микрофон; шум дороги
|
||
и шин мешает; калибровка своя для каждого телефона и чехла. Дёшево проверить,
|
||
но рассчитывать не стоит. Упомянуто для полноты.
|
||
|
||
---
|
||
|
||
## О чём хотелось бы услышать мнение сессии разработки
|
||
|
||
1. **Стоит ли вообще ветер в модели**, или проще честно писать «мощность
|
||
на равнине оценена с точностью ±30%» и не усложнять. Разброс 33% измерен,
|
||
вопрос в том, окупается ли его устранение.
|
||
2. **Вариант 5 в роли разметки** (а не измерения) -- насколько это ложится
|
||
на текущий UI и на запись событий с отметкой времени. По цене внедрения
|
||
это самый дешёвый из осмысленных шагов, и он не требует решать заранее,
|
||
покупать ли датчик.
|
||
3. **Оправдан ли вариант 3.** Планка задана: без железа выходит направление
|
||
с точностью около 20° и сила с точностью до двух раз. Датчик за
|
||
несколько тысяч рублей должен дать заметно больше, иначе он не нужен.
|
||
4. **Не ломает ли вариант 2 принцип автономности.** Скачивание заранее
|
||
по проводу формально сеть в поле не требует, но привязывает точность
|
||
расчёта к тому, вспомнил ли пользователь нажать кнопку перед выездом.
|
||
Это тот же изъян, что и у кнопки «уточнить высоты».
|
||
|
||
Готовый код обоих методов варианта 1 -- в `tools/track/track.py`
|
||
(функция `wind_by_passes` и секция «ветер» в `report`), выше описан
|
||
алгоритмически.
|
||
|
||
---
|
||
|
||
# БЭКЛОГ: автоматический разбор повторов внутри одной поездки
|
||
|
||
Заказано пользователем 17 сентября 2026.
|
||
|
||
## Задача
|
||
|
||
Пользователь катает интервалы: приезжает к подъёму, проезжает его 5 раз,
|
||
скатываясь вниз между заездами, потом уезжает домой. Сейчас, чтобы разобрать
|
||
такую тренировку, приходится либо останавливать и заново стартовать трек
|
||
на каждом повторе (пользователь называет это бредом), либо разбирать
|
||
вручную задним числом.
|
||
|
||
Нужно: **один трек на всю тренировку, повторы находятся сами.** Пользователь
|
||
готов на отдельный режим «велотренировка», если так проще.
|
||
|
||
## Что должно получиться на выходе
|
||
|
||
Таблица повторов: номер, время, средняя скорость, каденс, мощность.
|
||
И главное число, по которому принимается решение о завтрашнем дне:
|
||
|
||
отношение последнего повтора к первому
|
||
|
||
Порог, выведенный из данных (11 и 17 сентября, по 5 повторов):
|
||
- последний быстрее или равен первому -- не на пределе, можно повторить завтра
|
||
- последний медленнее на 5-10% -- нормальная тяжёлая тренировка
|
||
- последний медленнее больше чем на 10% -- два дня спокойно
|
||
|
||
## Алгоритм, проверен вручную на этих данных
|
||
|
||
Повторы ищутся по самоподобию трека, без опорных точек от пользователя.
|
||
|
||
1. **Разложить точки поездки по ячейкам** ~10 м (сетка как в `heightmap.py`).
|
||
2. **Найти ячейки, посещённые больше одного раза**, и отбросить те, где
|
||
направление движения (`bearing_deg`) при посещениях различается больше
|
||
чем на 60 градусов: проезд вниз по той же дороге повтором не считается.
|
||
**Без проверки направления половина повторов теряется или считается
|
||
задом наперёд** -- проверено, это главная ловушка.
|
||
3. **Собрать самую длинную цепочку** таких ячеек, идущую подряд по ходу
|
||
движения. Это и есть повторяемый участок. Требовать длину не меньше
|
||
300 м и не меньше 3 проходов, иначе это перекрёсток у дома, а не подъём.
|
||
4. **Разметить проходы:** для каждого прохода записать вход и выход
|
||
по цепочке, время, длину.
|
||
5. **Нормировать время на общую длину:** у пользователя GPS даёт от 1138
|
||
до 1199 м на одном и том же подъёме, то есть 5% разброса. Без нормировки
|
||
сравнение повторов между собой врёт.
|
||
|
||
## Чего делать НЕ надо
|
||
|
||
**Не просить пользователя ставить метки.** Он специально сказал, что
|
||
останавливать и перезапускать трек не будет.
|
||
|
||
**Не опираться на высоту отдельного проезда.** Профиль повторяемого участка
|
||
брать из карты высот по многим проездам (см. соседний раздел): у одиночного
|
||
проезда GPS даёт ступеньки вида 0, 0, 0, 0, 8.5 м на ровном месте.
|
||
|
||
**Не считать мощность усреднением по точкам.** Усреднять по времени.
|
||
При усреднении по точкам отрезки чуть разной длины дают разный вес
|
||
медленным кускам, и на одинаковой скорости выходят разные ватты
|
||
(проверено: 17.9 км/ч -> 173 Вт и 18.1 км/ч -> 209 Вт на одном подъёме).
|
||
|
||
## Готовый код
|
||
|
||
Ручная версия детектора по двум опорным точкам -- в истории этой сессии,
|
||
в `tools/track/`. Она находит проходы правильно, но опорные точки задаются
|
||
руками. Автоматизировать надо шаги 1-3.
|
||
|
||
---
|
||
|
||
# БЭКЛОГ: массовое уточнение высот по всем трекам
|
||
|
||
Заказано пользователем 17 сентября 2026. Приоритет свой, не срочный.
|
||
|
||
## Что нужно
|
||
|
||
Пункт меню, который уточняет высоты **пакетно**: идёт от последнего трека
|
||
назад по времени и обрабатывает те, где `altitude_srtm_m` ещё не заполнена.
|
||
Пользователь запускает его вручную, когда сидит на проводном интернете.
|
||
|
||
Требования к исполнению, из общих правил проекта:
|
||
- показывать прогресс числами: сколько треков обработано, сколько осталось
|
||
- прерываемость: в поле связь рвут РЭБ и РКН, процесс должен переживать
|
||
обрыв и продолжаться с того места, где встал, а не начинать заново
|
||
- уже уточнённые треки не трогать повторно
|
||
|
||
## Зачем, с измерениями
|
||
|
||
Аудит всех 83 велопоездок показал, что **высота за май и июнь непригодна**:
|
||
|
||
май-июнь: набор в БД 0-23 м, расчётный по сглаженной высоте 50-207 м
|
||
июль-сентябрь: набор в БД и расчётный сходятся (168/138, 251/275, 227/235)
|
||
|
||
До 17 мая запись шла раз в 3-4 секунды при шуме высотомера 0.8 м между
|
||
соседними точками. Пример 10 мая (session_id 3): сырая высота скачет
|
||
`36 20 23 20 20 36 36 35 35 35 34 34 33 20 23 20 19 32 32 32 31`, то есть
|
||
приёмник бегает между двумя уровнями с размахом 16 м. Медианный фильтр
|
||
и скользящее среднее это приглаживают, но остаток даёт уклоны до 12.8%
|
||
на треке, где реального набора 14 м на 13 км.
|
||
|
||
Практическое следствие: **любой анализ по уклонам за май и июнь даёт
|
||
фальшивые результаты**, причём правдоподобные на вид. Построенная по этим
|
||
данным кривая «лучший удержанный момент на шатунах» показала май лучшим
|
||
месяцем сезона, хотя по мощности за 20 минут рост с мая по сентябрь
|
||
составил 1.12 -> 1.75 Вт/кг.
|
||
|
||
Отдельно найдены отказы высотомера внутри годных треков: 2 сентября
|
||
(session_id 1788361994788) после точки 4194 значение падает на 10 м
|
||
и **замерзает** -- 26.8, 26.9, 26.9, 26.9 шестьдесят точек подряд,
|
||
а после перерыва записи прыгает на 64.8 м с точностью 22 м. Уклоны
|
||
на этом участке выдуманы целиком.
|
||
|
||
## Признаки негодной высоты, годятся как автотест
|
||
|
||
Все три считаются по уже записанным данным, без сети:
|
||
|
||
1. `elevation_gain_m` в сессии и набор, пересчитанный по точкам, расходятся
|
||
больше чем вдвое в любую сторону
|
||
2. медиана модуля разности высоты между соседними точками выше 1.5 м
|
||
3. доля точек с шагом записи больше 8 секунд выше 15%
|
||
|
||
По этим признакам из 83 поездок отсеиваются 31. После уточнения высот
|
||
через SRTM проверку надо прогнать повторно: она же покажет, помогло ли.
|
||
|
||
## Объём работы, посчитан по базе
|
||
|
||
всего точек 418 465
|
||
уже уточнено 47 420 (11.3%, 12 сессий из 170)
|
||
осталось 371 045
|
||
запросов по 100 3 711
|
||
|
||
Размер пачки 100 задан в `frontend/js/main.js` около строки 3085 и совпадает
|
||
с потолком OpenTopoData.
|
||
|
||
## Что надо поправить ДО массового прогона
|
||
|
||
**Откат по цепочке источников менять не надо.** Он сделан намеренно:
|
||
лучше получить хоть какую-то высоту, чем никакой. Проблема не в откате,
|
||
а в том, что после него не остаётся следа, кто именно ответил.
|
||
|
||
**1. Нет колонки источника.** В `gps_points` есть `altitude_srtm_m`, но нет
|
||
поля, откуда это значение пришло. При работающем откате база неизбежно
|
||
становится смесью источников, и без этой колонки отличить их потом нельзя
|
||
никак. Нужна колонка (ALTER TABLE, без пересоздания), заполняемая при записи.
|
||
Это единственное обязательное изменение.
|
||
|
||
**2. Откат должен быть виден пользователю.** Сейчас он сообщается только
|
||
строкой в консоли (`[gpxz] http 403`). Достаточно показать в конце итог
|
||
вида «уточнено 2314 точек: GPXZ 0, open-elevation 0, open-meteo 2314».
|
||
|
||
**3. Обработка 429 и 402 отдельно от сетевой ошибки.** По лимиту
|
||
останавливаться до завтра, по обрыву повторять. Иначе при массовом прогоне
|
||
упёршийся лимит молча отправит весь остаток базы к запасному источнику.
|
||
|
||
## Чего ждать и чего не ждать
|
||
|
||
Copernicus GLO-30 это сетка 30 м. На подъём длиной 200-400 м приходится
|
||
7-13 отсчётов, этого хватает. На короткий рамп 60-100 м приходится 2-3
|
||
отсчёта, форму по ним не восстановить. То есть массовое уточнение закроет
|
||
затяжные подъёмы и общий набор, но НЕ закроет короткие рампы, на которых
|
||
пользователь срывается. Для них нужен либо барометр, либо разовый замер
|
||
профиля конкретного подъёма угломером.
|
||
|
||
## Лимиты GPXZ против объёма
|
||
|
||
Тарифы с их сайта: Evaluation бесплатно и 100 запросов в сутки (помечен
|
||
«testing only»), Small 99 USD/мес и 2500 в сутки, Large 249 USD/мес и 7500.
|
||
|
||
3711 запросов при 100/сутки = 37 дней
|
||
3711 запросов при 2500/сутки = 1.5 дня
|
||
|
||
Отдельно: одна поездка это медиана 19 запросов, но максимум 133. То есть
|
||
**длинная поездка не влезает в бесплатный суточный лимит даже одна.**
|
||
Если массовый прогон делать на бесплатном ключе, он обязан быть
|
||
возобновляемым и считать свой суточный расход сам.
|
||
|
||
## Вариант без лимитов: свой Open Topo Data
|
||
|
||
GPXZ на своём сайте прямо пишет, что открытый форк их API-сервера
|
||
называется Open Topo Data. Размер пачки 100, уже зашитый в коде,
|
||
взят как раз из его потолка, то есть **протокол совместим и менять
|
||
в клиенте нужен только базовый URL** (сейчас `https://api.gpxz.io/v1/...`
|
||
захардкожен в `frontend/js/main.js` около строки 3091).
|
||
|
||
Считано по базе: все треки пользователя укрываются **двумя градусными
|
||
тайлами 1x1**. Для GLO-30 это порядка сотни мегабайт на диске, разово
|
||
скачивается по проводу.
|
||
|
||
Что это даёт против платного тарифа:
|
||
- лимита нет вообще, весь бэклог прогоняется за один вечер
|
||
- в поле сеть не нужна: у пользователя её глушат, и это постоянная
|
||
проблема всех сетевых зависимостей приложения
|
||
- тот же софт и тот же набор данных, значит результаты сопоставимы
|
||
с уже уточнёнными 12 сессиями, если там был GPXZ
|
||
|
||
Минус: нужен хост, на котором это крутится, и разовая возня с датасетом.
|
||
|
||
## Но своего хоста может и не понадобиться
|
||
|
||
Автор Open Topo Data держит бесплатный публичный инстанс
|
||
`api.opentopodata.org`. Его ограничения:
|
||
|
||
100 точек на запрос -- ровно тот размер пачки, что уже в коде
|
||
1 запрос в секунду -- нужен троттлинг, сейчас пауз между пачками нет
|
||
1000 запросов в сутки -- в десять раз больше бесплатного GPXZ
|
||
|
||
весь бэклог 3711 запросов -> 3.7 суток, 62 минуты чистого времени,
|
||
то есть ~15 минут работы в день
|
||
|
||
Сравнение по срокам на тот же объём:
|
||
|
||
GPXZ Evaluation (бесплатно) 37.1 дней
|
||
Open Topo Data публичный 3.7 дней
|
||
GPXZ Small (99 USD/мес) 1.5 дня
|
||
|
||
**Ключ не нужен вообще**, значит отпадает и вопрос, укладывается ли
|
||
массовый прогон личного архива в пометку «testing only» бесплатного
|
||
тарифа GPXZ.
|
||
|
||
### Выбор датасета, и почему это не мелочь
|
||
|
||
На публичном инстансе **нет Copernicus GLO-30**, на котором работает GPXZ.
|
||
Из глобальных 30-метровых доступны `mapzen`, `aster30m`, `srtm30m`.
|
||
`eudem25m` (25 м, лучшая точность) покрывает только страны EEA, регион
|
||
пользователя в его extent не входит.
|
||
|
||
Отсюда два следствия:
|
||
|
||
1. Датасет будет **не тот**, которым уточнены нынешние 12 сессий.
|
||
Смешивать нельзя, и колонка источника из пункта 2 выше становится
|
||
не пожеланием, а обязательной: без неё база превратится в винегрет
|
||
из трёх рельефов.
|
||
2. Разумный выбор по умолчанию `srtm30m` (USGS): у `aster30m` известны
|
||
шумы и артефакты, `mapzen` это композит из нескольких источников,
|
||
то есть внутри одного датасета точность неоднородна.
|
||
|
||
Хорошая проверка после прогона: 12 уже уточнённых сессий уточнить заново
|
||
новым датасетом в отдельную колонку и сравнить набор высоты. Разойдутся
|
||
сильно - значит выбор датасета важнее, чем кажется, и это надо знать
|
||
до того, как на этих высотах строить разбор поездок.
|
||
|
||
## ПРОВЕРЕНО: в базе лежит не то, что все думали
|
||
|
||
Проверка сделана, и она важнее всего остального в этом разделе.
|
||
|
||
**Ключ GPXZ не работает и, судя по всему, не работал никогда.** Запрос
|
||
к `api.gpxz.io` возвращает HTTP 403 с телом `error code: 1010`
|
||
и заголовком `Server: cloudflare`. Ответ **одинаковый с ключом и без
|
||
ключа**, то есть запрос режет Cloudflare по подписи клиента, до всякой
|
||
проверки ключа. Ключ при этом валидный на вид: 28 символов, префикс `ak_`.
|
||
|
||
**Те 12 «уточнённых» сессий уточнены open-meteo.** Это третий, последний
|
||
запасной источник в цепочке `GPXZ -> open-elevation -> open-meteo`.
|
||
Доказательство: опорные целые значения в `altitude_srtm_m` совпадают
|
||
с ответом open-meteo точка в точку и расходятся с `srtm30m` на 2-3 м.
|
||
|
||
**Дробные значения в базе создаёт приложение, а не источник.** Записанная
|
||
последовательность `39.000, 38.857, 38.714, 38.571, 38.429, 38.286,
|
||
38.143, 38.000` это `39 - k/7`, линейная интерполяция между двумя целыми
|
||
отсчётами. Все внешние DEM (srtm30m, aster30m, mapzen, srtm90m,
|
||
open-elevation, open-meteo) отдают **целые метры**. То есть дроби
|
||
в колонке выглядят как высокая точность, а на деле это прямая, натянутая
|
||
между узлами сетки в 30-90 м.
|
||
|
||
Практическое следствие для любого будущего разбора: **нельзя считать
|
||
уклон по `altitude_srtm_m` на коротких кусках.** Интерполяция даёт
|
||
гладкий правдоподобный профиль там, где данных нет вообще.
|
||
|
||
### Что из этого следует для задачи
|
||
|
||
1. Тихий откат (пункт 1 выше) не гипотетическая опасность, а уже
|
||
случившийся факт: база заполнена последним источником в цепочке,
|
||
и об этом никто не знал.
|
||
2. Колонка источника (пункт 2) обязательна.
|
||
3. Отказ надо показывать пользователю, а не писать в консоль.
|
||
|
||
### Покупать ли GPXZ: нет
|
||
|
||
С карты покрытия на их сайте: лидар 0.5-2 м у GPXZ есть только в США,
|
||
западной и северной Европе и Новой Зеландии. Регион пользователя
|
||
в жёлтой зоне, где по их же легенде 30 м, а по их тексту база
|
||
из Copernicus 30m. То есть платный GPXZ дал бы Copernicus 30m вместо
|
||
бесплатного SRTM 30m: другая вертикальная точность, **та же
|
||
горизонтальная сетка 30 м**.
|
||
|
||
А упирается всё именно в горизонтальную: подъём длиной 60-100 м, на
|
||
котором пользователь срывается, это 2-3 отсчёта на любой тридцатиметровой
|
||
сетке. Ни один DEM его не разрешит, ни платный, ни бесплатный.
|
||
|
||
Измерено на одном подъёме (1153 м, 229 точек, первый заезд 16 сентября):
|
||
источники расходятся между собой до 2-3% уклона на сотню метров, что
|
||
того же порядка, что шум GPS. `mapzen` при этом отдаёт числа, побитово
|
||
совпадающие с `srtm30m`.
|
||
|
||
Вывод: брать бесплатный `srtm30m` с публичного Open Topo Data.
|