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.
27 KiB
Находки по расчётной модели 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. Шум ветра в микрофоне
Обдув микрофона даёт характерный низкочастотный шум, растущий со скоростью потока. Теоретически из него извлекается воздушная скорость.
Практически: в кармане бесполезно, нужен вынесенный микрофон; шум дороги и шин мешает; калибровка своя для каждого телефона и чехла. Дёшево проверить, но рассчитывать не стоит. Упомянуто для полноты.
О чём хотелось бы услышать мнение сессии разработки
- Стоит ли вообще ветер в модели, или проще честно писать «мощность на равнине оценена с точностью ±30%» и не усложнять. Разброс 33% измерен, вопрос в том, окупается ли его устранение.
- Вариант 5 в роли разметки (а не измерения) -- насколько это ложится на текущий UI и на запись событий с отметкой времени. По цене внедрения это самый дешёвый из осмысленных шагов, и он не требует решать заранее, покупать ли датчик.
- Оправдан ли вариант 3. Планка задана: без железа выходит направление с точностью около 20° и сила с точностью до двух раз. Датчик за несколько тысяч рублей должен дать заметно больше, иначе он не нужен.
- Не ломает ли вариант 2 принцип автономности. Скачивание заранее по проводу формально сеть в поле не требует, но привязывает точность расчёта к тому, вспомнил ли пользователь нажать кнопку перед выездом. Это тот же изъян, что и у кнопки «уточнить высоты».
Готовый код обоих методов варианта 1 -- в tools/track/track.py
(функция wind_by_passes и секция «ветер» в report), выше описан
алгоритмически.