# Разбор 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 [bindaddress] ``` Запрос (тот же API, что у brouter.de/brouter-web): ``` GET http://127.0.0.1:/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/``. - **Чего не хватает:** оффлайн-роутера между опорными точками. Сейчас линия между тапами — прямая (ручное рисование). 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`.