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.
353 lines
27 KiB
Markdown
353 lines
27 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`), выше описан
|
||
алгоритмически.
|