baldav/docs/session-logs/2026-09-07_brouter-findings.md
4aiman 7d85773ae3 BRouter: fix routed-line re-route loop; verify planning + gap-fill on desktop
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.
2026-09-09 18:18:35 +03:00

12 KiB
Raw Blame History

Сессия 2026-09-07: разбор BRouter под две идеи

Контекст

Пользователь спросил, разбирал ли я brouter apk. Не разбирал. В дереве оказался brouter-1.6.3.zip (внутри два apk btools.routingapp, движок brouter-1.6.3-all.jar, профили, доки) и FitPro...apk (не brouter). Цель разбора (уточнена пользователем): понять, как BRouter строит маршруты с малым набором высоты, ради двух идей —

  1. заполнять пропажу 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.