doBrouter() ended with changed(), which nulled routeGeom and re-queued
doBrouter — an endless re-route loop where the road line was never drawn
(editor kept the straight line, distance unchanged). Draw directly instead
(updatePolyline/updateStats).
Verified end-to-end in the UI on a virtual display (Xvfb) against the
RouteServer: editor 'По дорогам' turns a 1.69km straight line into a 2.25km
road-following route; the gap-fill '🧩' overlay draws a shortest-road cyan
dashed line across a track's GPS dropouts. Docs (README/PROJECT_MAP/plan/log)
updated to match; router.js/poi.js now listed in PROJECT_MAP.
12 KiB
Сессия 2026-09-07: разбор BRouter под две идеи
Контекст
Пользователь спросил, разбирал ли я brouter apk. Не разбирал. В дереве оказался
brouter-1.6.3.zip (внутри два apk btools.routingapp, движок brouter-1.6.3-all.jar,
профили, доки) и FitPro...apk (не brouter). Цель разбора (уточнена пользователем): понять,
как BRouter строит маршруты с малым набором высоты, ради двух идей —
- заполнять пропажу GPS кратчайшим путём по дороге; 2) оффлайн-планирование маршрута по точкам в BalDav (замена онлайн-BRouter).
Действия
Работали по утверждённому плану ~/.claude/plans/merry-discovering-dream.md (сохранён смысл ниже;
файл планов ротируется). Разбор read-only: листинг архива, чтение доков/профилей через stdout,
инспекция jar в памяти (python zipfile из BytesIO), извлечение строк байткода — распаковки на диск
и запуска Java не было.
Выяснено: где дорога — сегменты .rd5 (тайлы 5×5°, OSM в битстрим по lookups.dat, высоты из
SRTM-90м); роутинг — btools.router.RoutingEngine (matchWaypointsToNodes → поиск по графу,
стоимость из профиля btools.expressions); малый набор высоты — параметры профиля
uphillcost/downhillcost/cutoff + кинематика (totalMass/S_C_x=CdA/C_r/bikerPower, аналог модели
мощности BalDav); shortest.brf — чистый кратчайший путь (под идею 1). Вызов оффлайн: jar
RouteServer <segmentdir> <profiledir> <customprofiledir> <port> <maxthreads> + HTTP
/brouter?lonlats=..&profile=..&format=geojson; приложение BRouter (Server-Mode HTTP или intent);
встраивание jar (356 КБ) в Android-модуль BalDav.
Результат
Создан docs/brouter-findings.md — полный разбор: состав, данные, алгоритм, малый набор высоты,
3 способа вызова + встраивание, обе идеи со стыком к существующему коду (computeStats ветка isGap;
showRouteEditor/showRouteDetail/parseRouteGPX/routeElevStats в main.js), таблица вариантов
интеграции с рекомендацией (целевой — встраивание движка; сначала проверить ценность на
установленном приложении BRouter), открытые вопросы (лицензия abrensch/brouter, сегменты
качать/строить, хранилище на телефоне, профили). Реализацию идей НЕ делали — отдельные фичи.
README не трогал: пользовательской фичи пока нет, это анализ. PROJECT_MAP — добавлен раздел «Разборы (docs/)» с указателями на gps-model-findings и brouter-findings.
Следующие шаги
- Ответы пользователя на открытые вопросы §9 разбора → планировать идею 1 и/или идею 2.
- Опционально PoC: RouteServer на компе + сегменты региона (нужен JRE + загрузка, по согласию).
Дополнение 2026-09-09: Этап 1 проверен на устройстве
Поставил сборку с мостом, залил тайлы E35_N45/E40_N45 в files/brouter/segments4 (adb push → run-as cp), запустил через CDP. Первый вызов упал: «lookup version mismatch lookups.dat=10 rd5=11» — дистрибутив 1.6.3 несёт lookups.dat v10, а сегменты brouter.de/segments4 — v11. Взял v11 lookups.dat + trekking.brf + shortest.brf из abrensch/brouter master (misc/profiles2), заменил вложенные ассеты; движок 1.6.3 их принял. Результаты: shortest Ростов ~5.4км за 3.2с (холодный), trekking Ростов→Новочеркасск 44.8км/набор 316м за 1.2с (кросс-тайл), на зашитых в apk v11-данных 617мс. Мост JS→Rust→Kotlin(движок)→GPX работает целиком. Этап 1 = DONE.
Урок: профили и lookups.dat ОБЯЗАНЫ совпадать по версии с сегментами (сейчас v11).
Дополнение 2026-09-09 (2): Этап 2 примитив проверен на устройстве
Загрузка в приложении сперва вставала ровно на 7922 байтах. Я ошибочно свалил на VPN/сеть —
неверно. Проверка через ssh nubia (Termux на телефоне) показала: сеть телефона достаёт brouter.de
целиком (HTTP 200, ranged GET 200КБ@90КБ/с). Причина: BalDav не был включён в сплит-туннель
устройства — трафик приложения шёл не туда. Пользователь добавил BalDav в туннель. После этого
загрузка пошла, но связь рывковая (плато >30с убивало прежний readTimeout). Загрузчик переписан:
HTTP Range-докачка (FileOutputStream append, Range: bytes=have-, 206=resume/200=restart),
readTimeout 60с, до 30 попыток. Итог на устройстве: E40 (14.1МБ) скачан приложением (done ~55с),
маршрут на нём — кросс-тайл trekking 44.8км/316м за 1.66с, listTiles отдаёт оба тайла.
Урок (себе): не диагностировать и не валить на сеть. Увидел недоступность — сообщить пользователю.
Дополнение 2026-09-09 (3): своё зеркало вместо прямого brouter.de
По решению пользователя данные берём со своего зеркала (не с brouter.de напрямую). Инфраструктура:
- Роутер Keenetic (белый IP 77.66.192.45): на :88 уже uhttpd (docroot /opt/share/www, USB 23ГБ free). Положил тайлы+профили в /opt/share/www/brouter/{segments4,profiles2} — раздаётся как http://192.168.1.1:88/brouter/... (uhttpd игнорирует Range → отдаёт файл целиком; на LAN неважно).
- ПК: BRouter RouteServer (jar) в /mnt/02F6E7F6F6E7E7BD/brouter-mirror (segments4/profiles2/customs + run-routeserver.sh + brouter.service). Проверен: GPX/GeoJSON 200. systemd-юнит подготовлен (автостарт без логина, multi-user.target) — пользователь ставит sudo-командами. Это удалённый роутинг-эндпоинт пользователя, НЕ источник для BalDav (телефон роутит встроенным движком).
- BalDav (downloadTile): источники по порядку LAN→WAN→brouter.de. Проверено на устройстве: тайл с LAN-зеркала за <2с, маршрут на нём ок. Осталось: WAN-пробросы на Keenetic (PPPoE0 :8088→роутер:88, :17777→ПК:17777) — outward-facing, с подтверждением; для release добавить network-security-config под cleartext на mirror-хосты.
Дополнение 2026-09-09 (4): своё зеркало на роутере + BRouter-Web + тёмная карта
- Зеркало на Keenetic: тайлы+профили в /opt/share/www/brouter/ (uhttpd :88), доступно LAN 192.168.1.1:88 и WAN 77.66.192.45:8088 (static-NAT PPPoE0 :8088→:88). BalDav качает тайлы с зеркала (LAN→WAN→brouter.de).
- BRouter RouteServer на ПК: /mnt/02F6E7F6F6E7E7BD/brouter-mirror, СИСТЕМНЫЙ systemd brouter.service (multi-user.target, автостарт без логина, поставил пользователь sudo). WAN :17777→ПК:17777.
- BRouter-Web (nrenner 0.18.1) на зеркале: http://192.168.1.1:88/brouter-web/ (+WAN), config.js → RouteServer, дефолт-слой ÖPNV (CARTO/др. — «needs key»), центр Новочеркасск.
- Тёмная карта в BalDav: перебрали провайдеров (CARTO/Thunderforest/2GIS/Yandex — ключи/квоты/ToS/проекция/
недоступность в РФ; проба доступности из BalDav по мобильному Tele2: доступны OSM/CARTO/ÖPNV/Esri/2GIS,
нет OpenTopo/Yandex-vec). Транспортную инвертом — плохо в городе. Решение: OSM-стандарт + CSS
invert(1) hue-rotate(180deg) brightness(1.5)на .leaflet-tile-pane, тумблер ☾ (запись/детали/дороги), без перепаковки. Проверено на устройстве.
Дополнение 2026-09-09 (5): визуальная проверка Stage 3/4 на десктопе (Xvfb) + фикс бага
Задача: доделать Stage 3 (планирование по точкам) и Stage 4 (заполнение гэпов), прогнать тесты на десктопе со скринами на виртуальном экране (Xvfb :99), не только по API.
Прогон в реальном UI (npx tauri dev на Xvfb :99, драйв через xdotool, скрины import):
- Stage 3 «🚲 По дорогам»: 3 опорные точки по Новочеркасску. Прямая линия 1.69км сменилась дорожной 2.25км по улицам (Буденновская → кольцо → просп. Ермака). Роутинг — PC-RouteServer (десктоп без встроенного движка), профиль trekking.
- Stage 4 «🧩»: трек 2026-06-22 (session 1782115144011, 16.24км, два дропаута ~2км хорды) — оверлей проложил голубой пунктир по улицам между концами гэпов (профиль shortest). Трек без фолловинг-гэпов (21.81км 2026-07-07) — тумблер включается, оверлей пуст, без ошибок.
Баг, найденный этой проверкой (был в 8e3c9fd): doBrouter в конце звал changed(), а тот
обнулял routeGeom и снова ставил doBrouter в очередь — бесконечный ре-роутинг, дорожная
линия НИКОГДА не рисовалась (в UI линия оставалась прямой, дистанция не менялась). Исправлено:
doBrouter рисует напрямую (updatePolyline(); updateStats();), changed() не трогает. Отдельным
коммитом (цикл виден в истории). Для проверки временно уводил дефолт-центр редактора на
Новочеркасск — откатил.
Проверка на телефоне встроенным движком (Stage 3 планирование, Stage 4 гэпы, докачка сегментов) —
когда тело подключат: ./build.sh install -d.