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.
191 lines
17 KiB
Markdown
191 lines
17 KiB
Markdown
# Разбор 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) и **`E40_N45.rd5`** (40–45E) — город ~40.1E
|
||
попадает во второй. Точные имена и размер (обычно десятки МБ на тайл) — **проверить при
|
||
загрузке**. Загрузка одноразовая по проводу — тот же паттерн «скачать дома», что для DEM/прогноза.
|
||
|
||
## 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`.
|