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.
18 KiB
Разбор BRouter 1.6.3 под две идеи BalDav (2026-09-07)
Получено разбором brouter-1.6.3.zip (read-only: листинг архива, чтение текстовых
доков и профилей через stdout, инспекция brouter-1.6.3-all.jar в памяти — распаковки
на диск и запуска Java не было). Цель — понять, как BRouter «знает, где дорога» и строит
маршруты с малым набором высоты, и оценить под это две идеи пользователя:
- Заполнение GPS-пропусков по дороге — когда GPS пропадает и появляется, пробел заполнять кратчайшим путём по реальным дорогам, а не хордой/скорость×время.
- Оффлайн-планирование маршрута по опорным точкам в 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):
-
Лицензия — MIT («Copyright (c) 2019 BRouter contributors», github.com/abrensch/brouter). Встраивание разрешено без ограничений; при распространении сохранить копирайт-нотис MIT (положить
LICENSE-brouterв репозиторий рядом с движком). -
Интеграция — вариант B (встраивание движка). Код BalDav открыт, MIT это позволяет.
-
Профиль — trekking (для обеих идей). Идея 1 всё же считается
shortestдля чистого кратчайшего пути (см. §6); trekking — для планирования (идея 2). -
Идея 1, порог хорды: прокладывать по графу, НО если дорожная длина в разы больше хорды — рисовать хорду (граф увёл не туда, напр. вокруг поля). Коэффициент подобрать на практике (старт ~3×, вынести в конфиг/константу).
-
«Крутилка»
uphillcost(см. §4) — пользователь про неё не знал. План: начать со стандартного trekking; если маршруты слишком в горку — добавить настройку «избегать подъёмов» (подниматьuphillcost). Пока не крутим. -
Доставка сегментов — загрузка по проводу внутри BalDav (решено). Экран в BalDav: дома на Wi-Fi/проводе скачать готовые тайлы региона с brouter.de/segments4 в папку приложения (app data dir, аналог seed-БД); в поле сеть не нужна. Тот же паттерн, что задумывался для DEM/ прогноза. Свою сборку сегментов из OSM+SRTM не делаем.
-
Встраиваемость подтверждена (см. §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.