baldav/docs/gps-model-findings.md
4aiman c3188911c3 BRouter: analysis + embed the offline routing engine (Stage 1)
Analysis in docs/brouter-findings.md: how BRouter knows roads (.rd5
segments), routes and reaches low elevation gain (profile uphill/downhill
cost + kinematics), the three call surfaces, and an evaluation of two ideas
— fill GPS gaps along roads, and offline waypoint route planning. License is
MIT; the engine has no desktop-only deps, so it embeds on Android ART.

Stage 1 (embed + bridge, compiles into the apk; on-device route not yet run):
- vendored brouter-1.6.3-all.jar into the Android module (dexes cleanly) and
  bundled the trekking/shortest profiles + lookups.dat as assets, copied to
  files/brouter/profiles2 on launch
- MainActivity: a brouter_command.txt handler on the existing FileObserver
  runs btools.router.RoutingEngine off the UI thread (recipe taken via javap
  from ServerHandler) and returns the track as GPX
- Rust brouter_route command: same file bridge as GPS (request + command,
  poll for the result), registered in the invoke handler
- frontend/js/router.js: BRouter.route(points, profile) -> GPX (parseRouteGPX)
- LICENSE-brouter (MIT notice) kept alongside the vendored engine

Segments (.rd5, ~13-23 MB per 5x5 tile) are delivered later (Stage 2, wired
download inside BalDav). IMPLEMENTATION_PLAN.md tracks the 5 stages.
2026-09-09 00:56:13 +03:00

27 KiB
Raw Blame History

Находки по расчётной модели 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), выше описан алгоритмически.