baldav/docs/brouter-findings.md
4aiman 99cd4c8d30 Scrub location/identity: home city name and external mirror IP → placeholders
Remove the home-city name from docs/comments/UI strings and the external
mirror IP (WAN) from code, docs and the Android cleartext allowlist, replacing
the IP with mirror.invalid (set the real host locally, uncommitted). LAN mirror
and routing keep working; the WAN fallback needs the real host set. Generic
big-city map presets are kept (not identifying).
2026-09-10 00:55:45 +03:00

195 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Разбор BRouter 1.6.3 под две идеи BalDav (2026-09-07)
Получено разбором `brouter-1.6.3.zip` (read-only: листинг архива, чтение текстовых
доков и профилей через stdout, инспекция `brouter-1.6.3-all.jar` в памяти — распаковки
на диск и запуска Java не было). Цель — понять, как BRouter «знает, где дорога» и строит
маршруты с малым набором высоты, и оценить под это две идеи пользователя:
1. **Заполнение GPS-пропусков по дороге** — когда GPS пропадает и появляется, пробел
заполнять кратчайшим путём по реальным дорогам, а не хордой/скорость×время.
2. **Оффлайн-планирование маршрута по опорным точкам** в BalDav — замена онлайн-BRouter.
Обе идеи требуют оффлайн-роутинга; сеть в поле недоступна (РЭБ/РКН — как в обсуждениях
ветра и DEM), поэтому годятся только оффлайн-схемы.
---
## 1. Что внутри дистрибутива
| Часть | Что это |
|---|---|
| `BRouterApp.1.6.3-api19-release.apk`, `...api30-release.apk` | Android-приложение `btools.routingapp` (одно и то же, под разные min-SDK). Считает маршрут, карту НЕ рисует. |
| `brouter-1.6.3-all.jar` (356 КБ) | Standalone-движок на чистой Java. Main-Class `btools.server.BRouter` (HTTP-сервер). Тот же движок, что в apk. |
| `profiles2/*.brf` | Профили маршрутизации (текстовые скрипты стоимости). `lookups.dat` — таблица тегов/значений. |
| `readmes/` | Документация: установка, `profile_developers_guide.txt`, `mapcreation.md`, интеграция с OsmAnd, особенности Android 11. |
BRouter — **маршрутизатор без карты**: на вход опорные точки + профиль, на выход трек
(GPX/GeoJSON). Отрисовка и навигация — на стороне карт-тула (OsmAnd/Locus/…), либо, в нашем
случае, на стороне BalDav.
## 2. Где дорога: данные `.rd5`
- Граф дорог — **сегментные файлы `.rd5`**, тайлы **5°×5°** по широте/долготе. Внутри —
OSM-ways и nodes, закодированные в бинарный битстрим по таблице `lookups.dat` (для каждого
тега — набор значений с частотами и алиасами).
- Готовые тайлы качаются с **brouter.de/brouter/segments4** (пересобираются еженедельно) или
строятся самому из OSM-dump + SRTM (`mapcreation.md`, скрипт `process_pbf_planet.sh`,
требуется Osmosis/pbfparser).
- **Высоты** в сегментах берутся из **SRTM-90м** (CGIAR). Именно они дают BRouter'у профиль
рельефа, по которому он считает набор высоты (см. §4). Без SRTM сегменты соберутся, но без
высот — маршруты «с малым набором» не получатся.
- Для нашего региона (~47N/40E) нужны тайлы, чьи SW-углы кратны 5:
**`E35_N45.rd5`** (35–40E, 45–50N, ~23.8 МБ) и **`E40_N45.rd5`** (40–45E, ~14.1 МБ) —
опорная точка ~40.1E попадает во второй (проверено загрузкой). Одноразовая загрузка по проводу —
тот же паттерн «скачать дома», что для DEM/прогноза.
- **ВЕРСИЯ ДАННЫХ (важно, проверено на устройстве).** `lookups.dat` и профили ОБЯЗАНЫ совпадать
по версии с сегментами, иначе «lookup version mismatch». Дистрибутив 1.6.3 несёт `lookups.dat`
версии 10, а живые сегменты brouter.de/segments4 — версии 11. Взяли v11 lookups.dat + профили из
репозитория (abrensch/brouter master, `misc/profiles2/`); движок 1.6.3 их принимает и роутит.
## 3. Как строится маршрут (движок)
- Класс `btools.router.RoutingEngine`. Ключевые шаги (по строкам байткода):
`matchWaypointsToNodes` — **привязка каждой опорной точки к ближайшему дорожному узлу графа**
(это и есть ответ «где дорога, а где нет»: точка вне дорог подтягивается к дороге), затем поиск
пути по графу между привязанными узлами.
- Стоимость ребра считает **интерпретатор профиля** (`btools.expressions`) по тегам ребра из
`.rd5`. Поиск минимизирует суммарную стоимость (по сути A*/Dijkstra по сегментному графу с
кэшами профиля).
- На выход движок отдаёт трек и метрики: `track-length`, `filtered ascend` и `plain-ascend`
(набор высоты — сглаженный и сырой), `cost`, `energy`, `time` (по кинематике профиля).
Формат — GPX (`formatAsGpx`/`writeGpx`) или GeoJSON/KML.
## 4. Малый набор высоты — как именно (основной режим)
Это чисто вопрос **профиля**, а не движка. В `trekking.brf` (context:global):
```
assign consider_elevation = true
assign downhillcost = 60 # штраф за спуск
assign downhillcutoff = 1.5 # уклоны меньше 1.5% не штрафуются
assign uphillcost = 0 # (в trekking по умолчанию 0; крутить вверх — здесь)
assign uphillcutoff = 1.5
```
Плюс **кинематическая модель времени/энергии** — прямой аналог модели мощности BalDav:
```
assign totalMass = 90 # масса байк+седок, кг
assign maxSpeed = 45 # км/ч
assign S_C_x = 0.225 # CdA (площадь×коэф. лобового сопротивления)
assign C_r = 0.01 # коэффициент качения
assign bikerPower = 100 # средняя мощность, Вт
```
Чтобы получить маршрут **с малым набором высоты**, поднимают `uphillcost` (и/или `downhillcost`):
движок начинает предпочитать обходные, но более пологие дороги. `uphillcutoff`/`downhillcutoff`
задают порог уклона, ниже которого рельеф игнорируется. То есть «основной режим» пользователя в
онлайн-BRouter — это trekking с ненулевым `uphillcost`.
Отдельно есть **`shortest.brf`**: `downhillcost=0`, `uphillcost=0`, `turncost=0` — чистейший
кратчайший путь по дорогам, без учёта рельефа. Это ровно то, что нужно для **идеи 1** (заполнить
GPS-пропуск — важно «просто чтобы была дорога», кратчайшая).
## 5. Как вызвать оффлайн — три поверхности
**(а) Standalone-jar на компе (HTTP-сервер).** Подтверждённая строка usage:
```
java -cp brouter-1.6.3-all.jar btools.server.RouteServer <segmentdir> <profiledir> <customprofiledir> <port> <maxthreads> [bindaddress]
```
Запрос (тот же API, что у brouter.de/brouter-web):
```
GET http://127.0.0.1:<port>/brouter?lonlats=39.7,47.2|40.1,47.4&profile=shortest&format=geojson&timode=0&alternativeidx=0
```
Параметры (из байткода `RouteServer`): `lonlats` (пары lon,lat через `|`), `profile`, `format`
(gpx/geojson/kml), `timode` (turnInstructionMode), `alternativeidx`, `nogos`. Нужен JRE + каталог
сегментов `.rd5` + каталог профилей. Годится для инструментов `tools/track/` (проложить маршрут по
`lonlats`, что уже выдаёт `tools/steep_segments.js`).
**(б) Приложение BRouter на телефоне — Server-Mode.** Приложение поднимает локальный HTTP-сервер
(порт 17777 по умолчанию); любой клиент на устройстве, включая WebView BalDav, шлёт тот же
`GET /brouter?lonlats=...`. Требует установленного приложения BRouter и скачанных сегментов.
**(в) Приложение BRouter — Android-intent.** С v1.6.2 у приложения есть «интерфейс без файлового
доступа»: BalDav (Kotlin) шлёт intent к `btools.routingapp` с опорными точками и профилем, получает
GPX/GeoJSON обратно. Данные приложения с Android 11 лежат в
`Android/media/btools.routingapp/brouter/segments4`.
**(г) Встраивание движка в BalDav.** `brouter-1.6.3-all.jar` — чистая Java (356 КБ), пакеты
`router`/`mapaccess`/`expressions`/`codec`/`util`. Классы движка можно добавить в Android-модуль
BalDav (`src-tauri/gen/android`) и звать из Kotlin, а из JS — через существующий Rust↔Kotlin-мост
(как GPS-сервис). Полностью самодостаточно и оффлайн, второе приложение не нужно.
## 6. Идея 1 — заполнение GPS-пропусков по дороге
- **Как:** при разрыве взять последнюю точку до пропуска и первую после, проложить между ними
маршрут профилем `shortest.brf`, вставить дорожную полилинию как заполнение гэпа.
- **Стык с `computeStats` (frontend/js/gps.js, ветка `isGap`):** сейчас гэп заполняется хордой или
`скорость×время`. Дорожный путь точнее (по реальным дорогам). Точки-заполнители помечать
`estimated` (как dead-reckoning), чтобы они не влияли на глюк-фильтры высоты и были видимо
«достроенными». **Решить:** идёт ли дорожная дистанция в зачёт трека или только рисуется (сейчас
гэп считается по скорости; дорожная длина может быть и точнее, и длиннее реального пути).
- **Риски/границы:** без сегментов на регион заполнить нечем; дорога-кратчайшая может отличаться от
фактически проеханной; на бездорожье (поля, где пользователь реально ездит) кратчайший путь по
графу дорог может увести. Нужен разумный лимит длины гэпа, при котором достраиваем.
## 7. Идея 2 — оффлайн-планирование маршрута по точкам
- **Что уже есть в BalDav** (frontend/js/main.js): `showRouteEditor` (тап по карте = точка,
тап по линии = вставка, тащить/удалять), `showRouteDetail` (предпросмотр + профиль высоты
`routeElevStats` + фуллскрин), `drawRoutePolyline`, «Сохранённые маршруты», топо/уклон-обводка.
Парсер `parseRouteGPX` уже читает GPX/`<ele>`.
- **Чего не хватает:** оффлайн-роутера между опорными точками. Сейчас линия между тапами — прямая
(ручное рисование). BRouter вернёт дорожный GPX/GeoJSON, который **уже парсится** `parseRouteGPX`,
и дальше работает существующий предпросмотр/профиль высоты.
- То есть UI-обвязка в основном готова; предметная работа — вызов BRouter и подмена прямых отрезков
дорожным маршрутом (+ выбор профиля: trekking с `uphillcost` для «поменьше в горку»).
## 8. Варианты интеграции и рекомендация
| Вариант | Плюсы | Минусы |
|---|---|---|
| **A. Зависимость от приложения BRouter** (intent или localhost HTTP) | Быстро; роутинга в BalDav нет; BRouter сам качает/обновляет сегменты | Второе приложение ставить и настраивать; управление сегментами вне BalDav; связанность |
| **B. Встраивание движка** (jar в Android-модуль + Rust/JS-команда) | Самодостаточно, полностью оффлайн, один apk; сегменты кладём куда хотим | Бандлинг Java-движка; доставка `.rd5` в приложение; **проверить лицензию** (GitHub abrensch/brouter); управление хранилищем сегментов |
| **C. Порт на Rust/JS** | Нет внешних зависимостей | Огромный объём (движок + формат `.rd5` + интерпретатор профилей) — **отклонить** |
**Решение — B (встраивание)** (подтверждено пользователем). Встраиваемость проверена по байткоду:
движок (150 классов) использует только `java.io/net/util/lang/text/security/zip` — ни AWT/Swing/
RMI/ImageIO/servlet/sun.*, всё есть на Android ART. `RouteServer` — на голых `ServerSocket`/`Socket`
(контейнер не нужен). Значит в процессе можно звать напрямую `btools.router.RoutingEngine`
(без HTTP-сервера): дать каталог сегментов + профиль + опорные точки, получить GPX/GeoJSON.
Для инструментов `tools/track/` (анализ, не телефон) сразу годится **вариант (а)** — jar на компе.
## 9. Решения и открытые вопросы
Решено (2026-09-07):
1. **Лицензия — MIT** («Copyright (c) 2019 BRouter contributors», github.com/abrensch/brouter).
Встраивание разрешено без ограничений; при распространении сохранить копирайт-нотис MIT
(положить `LICENSE-brouter` в репозиторий рядом с движком).
2. **Интеграция — вариант B (встраивание движка)**. Код BalDav открыт, MIT это позволяет.
3. **Профиль — trekking** (для обеих идей). Идея 1 всё же считается `shortest` для чистого
кратчайшего пути (см. §6); trekking — для планирования (идея 2).
4. **Идея 1, порог хорды:** прокладывать по графу, НО если дорожная длина в разы больше хорды —
рисовать хорду (граф увёл не туда, напр. вокруг поля). Коэффициент подобрать на практике
(старт ~3×, вынести в конфиг/константу).
5. **«Крутилка» `uphillcost`** (см. §4) — пользователь про неё не знал. План: начать со
стандартного trekking; если маршруты слишком в горку — добавить настройку «избегать подъёмов»
(поднимать `uphillcost`). Пока не крутим.
6. **Доставка сегментов — загрузка по проводу внутри BalDav** (решено). Экран в BalDav: дома на
Wi-Fi/проводе скачать готовые тайлы региона с brouter.de/segments4 в папку приложения
(app data dir, аналог seed-БД); в поле сеть не нужна. Тот же паттерн, что задумывался для DEM/
прогноза. Свою сборку сегментов из OSM+SRTM не делаем.
7. **Встраиваемость подтверждена** (см. §8): движок без десктоп-зависимостей, зовём
`btools.router.RoutingEngine` в процессе из Kotlin.
## 10. Проверка (PoC, отдельным шагом, по согласию)
Требует JRE и загрузки сегментов региона (сеть) — вне текущей задачи. Когда разрешишь:
запустить `RouteServer` на компе с каталогом `.rd5` и `profiles2/`, дёрнуть
`GET /brouter?lonlats=...&profile=shortest&format=geojson` по паре точек из
`tools/steep_segments.js`, убедиться, что возвращается дорожный маршрут с `track-length`/`ascend`.