Docs: elevation/slope accuracy + brouter ford avoidance (plan, session log, README)

This commit is contained in:
4aiman 2026-10-26 21:11:25 +03:00
commit 9d510931ad
3 changed files with 149 additions and 2 deletions

View file

@ -34,9 +34,9 @@
- **OTA обновления** — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер.
- **Ручные маршруты** — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API.
- **Сохранённые маршруты** — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат `<rtept>`), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега.
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM).
- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон по НАПРАВЛЕНИЮ движения, полосами по 1%: подъём зелёный→жёлтый→красный→коричневый→чёрный, спуск зелёный→cyan→синий→navy (≥10% крайний), или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`<ele>` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; уклон считается по дистанционному окну ~30м (одна ячейка DEM), чтобы ступенька SRTM не давала ложный крутой уклон на одном коротком сегменте. **Поправка высот по проездам**: где катал >=N раз, SRTM-высота в превью сдвигается вверх/вниз на медиану (измеренная−SRTM) по ячейкам ~20м (SRTM остаётся базой, недеструктивно; `get_elev_correction`).
- **Редактор маршрутов** — создание («Новый маршрут») и правка сохранённого маршрута прямо в приложении: тап по карте — добавить точку в конец, **тап по линии — вставить точку** (широкая зона попадания), долгий тап по линии — переключить отрезок дорога/прямая, тащить точку — двигать, тап по точке — удалить. Роутинг **по-сегментный**: каждый отрезок между опорными точками отдельно либо по дорогам (BRouter, профиль trekking, высоты из BRouter), либо прямой; по дорогам по умолчанию, где дороги/сегментов нет — молча прямая; кнопка **«🚲 По дорогам»** задаёт режим сразу всем. Маршрут в редакторе рисуется **оковкой с раскраской уклон/высота** как в детали; тумблеры день/ночь, уклон/высота и кнопка «моё место» на карте. Сохраняются и **опорные точки** (5-6, для правки — при повторном открытии столько же), и плотная дорожная геометрия с высотами (для показа и профиля уклона).
- **Оффлайн-роутинг (BRouter)** — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты `.rd5` региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP.
- **Оффлайн-роутинг (BRouter)** — движок BRouter встроен в приложение (Android): маршруты строятся по дорогам без сети, сегменты `.rd5` региона качаются с домашнего зеркала прямо в приложении (экран дорог → «Сегменты»). Используется в редакторе маршрутов («По дорогам») и для заполнения GPS-пропусков. На десктопе/дома движка нет — те же вызовы уходят на домашний BRouter-RouteServer по HTTP. Профиль `trekking` по умолчанию **избегает бродов** (`ford`), заброшенных грунтовок (`tracktype=grade5`) и троп с плохой видимостью (`trail_visibility=bad|horrible|no`); тумблер `%allow_adventure%` (деф. false). Показ опасных участков на превью + опт-ин «испытать удачу» — в работе.
- **Заполнение GPS-пропусков по дороге** — на детали трека кнопка **«🧩»** прокладывает дорожный путь (BRouter, профиль shortest) через каждый GPS-дропаут (разрыв по времени + реальная хорда) и показывает **предпросмотр только достроенных пропусков** (голубым, с бело-чёрной оковкой; недостроенные — красным пунктиром) плюс панель «Достроено N (+X км), M не удалось» с кнопками **Сохранить/Отмена**. Сохранение — **обратимый патч на месте**: достроенные точки помечаются `estimated` и добавляются в трек (оригинал цел), статистика пересчитывается; повторный тап предлагает **«Убрать достройку»** (удаляет только достроенное). Достраиваются только реальные длинные потери сигнала (по умолчанию разрыв > 60с и хорда > 150м); пропуск не достраивается, если дорога заметно длиннее хорды (крюк, > 2×) или почти совпадает с прямой (мостить нечего) — чтобы не перерисовывать нормальные участки объездами.
- **Непроезжие места (nogo)** — красные круги, которые роутинг ОБЪЕЗЖАЕТ. Ставятся кнопкой **⛔**: в планировщике — тапом по карте/линии, на карте записи — «здесь нет проезда» (текущее место). Показываются на картах планировщика, записи и детали трека; тап по кругу — убрать. Избегание работает и через домашний RouteServer, и встроенным движком офлайн (BRouter nogopoints). Web-BRouter умеет nogo сам.
- **Объезд с возвратом на маршрут (на ходу)** — на карте записи кнопка **«↪»**: если активный маршрут ведёт через непроезжее место, строит от текущей позиции объезд обратно НА маршрут за местом затыка (отступ ~1.5 км вперёд, растёт при неудаче до конца маршрута), избегая nogo и разворота на 180° (учитывает курс). Показывается ярким навигационным оверлеем (не меняет сохранённый маршрут и не прерывает запись); долгий тап по кнопке убирает оверлей.

View file

@ -0,0 +1,91 @@
# План: точность высот/уклона в предпросмотре + безопасность маршрутов (brouter)
## Context
Пять замечаний по предпросмотру маршрута и маршрутизации (baldav):
1. Режим «уклон» врёт: рисует крутой подъём там, где его нет (и наоборот). Высоты из DEM
квантуются по ячейкам 30м, точки внутри ячейки имеют одну высоту, на границе - скачок, и вся
Δ падает на один короткий сегмент = ложный крутой уклон. (Второе - «не рисует где есть» - предел
разрешения SRTM 30м, не чинится без более точного DEM.)
2. Спуски красить иначе: сейчас уклон красится по МОДУЛЮ (вверх и вниз одинаково). Спуски по
направлению движения должны уходить из зелёного в голубой -> navy.
3. brouter трижды завёл к бродам/ледяной воде (ford - теги OSM в данных brouter, не SRTM). Нужно:
избегать бродов/сложных троп/крутых спусков по умолчанию, ПОКАЗЫВАТЬ оставшиеся опасные участки
на предпросмотре заранее, и дать опцию «разрешить (испытать удачу)». Полный объём.
4. Кнопка объезда ↪ на записи срабатывает всегда и ведёт «к первому вейпоинту»: нет проверки, что
впереди реально есть затык, а цель выбирается по глобально ближайшей точке маршрута (ломается на
петлях и вдали от маршрута).
5. Уточнять высоты по многим проездам. Решение пользователя: база - SRTM, медиана высот из треков
берётся ТОЛЬКО как поправка вверх/вниз к SRTM (не замена), там где катал.
Общее: раскраска уклон/высота и расчёт уклона - в ОДНИХ функциях, используются в предпросмотре
маршрута, детали записанного трека, редакторе и на экране записи. Правка общих функций чинит везде;
проверить надо каждое место (WT4: починил A - проверь B и C в той же области).
## Критичные файлы и функции
- `frontend/js/gps.js`: `GPS.routeElevStats` (расчёт `grades[]`/`smoothed[]`), `GPS.filterAndSmoothAlt`
(сглаживание высот - сейчас по ТОЧКАМ, окно 5), `GPS.fillGaps`/`get_ride_graph` (сетка ~20м - база для п.5).
- `frontend/js/main.js`: `routeSlopeColor` + `ROUTE_SLOPE_BANDS` + `slopeLegendHtml` (п.2),
`drawRoutePolyline`/`showRouteDetail` (предпросмотр), `rejoinReroute` (п.4), `fetchElevations` (п.5).
- `src-tauri/gen/android/app/src/main/assets/brouter/profiles2/trekking.brf` (п.3, избегание) +
копия в `tools/brouter-web-staging/profiles2/trekking.brf` (единый источник - править обе).
- `MainActivity.kt` `routeBrouter` (п.3, вытащить теги путей из `OsmTrack.messageList` рядом с GPX).
- `src-tauri/src/lib.rs` `get_ride_graph`/новый агрегат высот (п.5).
## Подход по пунктам
### П.1 - уклон по дистанционному окну (вместо ступенек ячейки)
В `GPS.routeElevStats` считать `grades[i]` как центральную разность высоты по РАССТОЯНИЮ окном
~30м (одна ячейка DEM), а не между соседними точками: для точки i найти вперёд j и назад k так,
чтобы суммарная дистанция >= W (конфиг `gradeWindowM`, дефолт 30), grade = (smoothed[j]-smoothed[k])/dist.
Так одиночная ступенька ячейки размазывается по всей ячейке. `maxGrade` и профиль - по тем же
сглаженным. «Не рисует где есть» - записать как предел разрешения SRTM; реально улучшает п.5.
### П.2 - направленная раскраска спусков
`routeSlopeColor(gradePct)` сделать знакочувствительной: подъём (>=0) - тёплый веер (как сейчас
green->yellow->red->brown->black), спуск (<0) - холодный веер green->cyan->blue->navy по модулю.
Два массива полос + обновить `slopeLegendHtml` (две шкалы: подъём/спуск). Направление = порядок
точек (он же направление движения в треке/маршруте).
### П.3 - brouter: избегать + показывать + опция
- Профиль `trekking.brf`: добавить штрафы (через `assign` в `---context:way`) на `ford=yes`,
пересечение waterway без моста, `tracktype=grade4|grade5`, `trail_visibility=bad|horrible`,
высокий `sac_scale`/`mtb:scale`; крутой спуск уже есть через `downhillcost`. Параметр
`%allow_adventure%` (дефолт false) - при true штрафы снимаются («испытать удачу»).
- Показ опасного: в `routeBrouter` рядом с GPX вернуть JSON тегов сегментов из `OsmTrack.messageList`
(btools даёт теги путей); нужен короткий спайк - проверить, что messageList заполняется и какие
поля. На превью (`drawRoutePolyline`/`showRouteDetail`) пометить участки ford/вода/крутой спуск
отдельным стилем + счётчик «броды: N, крутые спуски: M». Тумблер «разрешить броды/сложные тропы»
(localStorage + `%allow_adventure%` в вызов BRouter).
- Если messageList тегов не отдаёт - запасной путь: крутые спуски берём из уклона (уже есть), броды -
пометить честно как «не выяснено без тегов» и решить отдельно. Спайк решает.
### П.4 - объезд ↪ только при реальном затыке впереди
В `rejoinReroute`: (а) спроецировать текущую позицию на маршрут -> индекс прогресса (не глобально
ближайший - ломается на петлях), учитывать направление движения; (б) объезд строить ТОЛЬКО если
есть nogo на маршруте ВПЕРЕДИ в пределах окна (напр. до +1.5км по маршруту); иначе toast «впереди
затыка нет» и ничего не строить. Цель rejoin - первая точка маршрута за nogo + запас.
### П.5 - поправка высот по проездам (база SRTM + медиана как сдвиг)
- Rust (`lib.rs`, рядом с `get_ride_graph`): агрегат по сетке ~20-30м из `gps_points.altitude`:
на ячейку median(alt) и count. Новый command, напр. `get_elev_correction`.
- JS (`fetchElevations`/`routeElevStats`): после получения SRTM-высот, где у ячейки count>=порога,
скорректировать: `elev = SRTM + smooth(median_ridden - SRTM)`. SRTM остаётся базой и формой; треки
только двигают вверх/вниз где катал. Порог проездов и сглаживание сдвига - в конфиг.
- GPS-высота шумная -> median по многим точкам/проездам + отбрасывание выбросов; барометр DJI
(отдельный TODO) улучшит позже.
## Порядок и коммиты
Пункты независимы. Делать по одному, коммит на каждый (commit-before-test), сборка через
`./build.sh -d -i android`, проверка на телефоне; follow-up фиксы - отдельными коммитами.
Рекомендуемый порядок: 2 (быстрый, визуальный) -> 1 -> 4 -> 5 -> 3 (самый большой, со спайком).
## Проверка (на устройстве + где можно на ПК)
- П.1/П.2: открыть предпросмотр реального маршрута с известным рельефом; переключить уклон/высота;
убедиться, что крутой цвет только на реально крутом, спуски холодные. Вёрстку/цвета можно глянуть
на ПК в dev (reference_verify_record_screen). Проверить ВСЕ места раскраски: превью маршрута,
деталь записанного трека, редактор, экран записи.
- П.3: построить маршрут через известный брод/грунтовку -> по умолчанию объезжает; включить
«разрешить» -> ведёт через, но помечено на превью. Спайк messageList - логом из routeBrouter.
- П.4: на записи вдали от затыка нажать ↪ -> «впереди затыка нет»; поставить nogo на маршруте
впереди -> объезд строится за него, не к старту.
- П.5: взять ячейки, где много проездов, сравнить профиль до/после коррекции; где не катал - SRTM.

View file

@ -0,0 +1,56 @@
# 2026-10-26 - Точность высот/уклона в предпросмотре + безопасность маршрутов (brouter)
Работали по утверждённому плану `docs/plans/` (финальный вариант - см. ниже «План»). Пять пунктов
по предпросмотру маршрута и маршрутизации. Телефон в ходе работы отключился - часть проверок на
устройстве ждёт переподключения (отмечено).
## Что сделано (по пунктам плана)
### П.2 - направленная раскраска спусков (коммит 27c60a4)
`routeSlopeColor` стала знакочувствительной: подъём - тёплый веер (green->yellow->red->brown->black),
спуск по направлению движения - холодный (green->cyan->blue->navy). Легенда показывает обе шкалы.
Один общий `routeSlopeColor`/`drawRoutePolyline` -> чинит везде (превью, деталь трека, редактор,
оверлей на записи). Проверка на устройстве: ЖДЁТ (нужен реальный маршрут с рельефом).
### П.1 - уклон по дистанционному окну (коммит 80ddff9)
`GPS.routeElevStats`: `grades[i]` теперь центральная разность высоты по РАССТОЯНИЮ окном ~30м
(config `gradeWindowM`), а не между соседними точками. Ступенька ячейки DEM (30м) размазывается по
ячейке - уходит ложный крутой уклон на одном коротком сегменте. `maxGrade` - по тем же.
«Не рисует где есть» - предел разрешения SRTM 30м, не чинится без более точного DEM (улучшает П.5).
Проверка: ЖДЁТ устройство.
### П.4 - объезд ↪ только при реальном затыке впереди (коммит ac37f36)
`rejoinReroute`: (а) прогресс = ближайшая точка маршрута к позиции; (б) направление по компасу; (в)
объезд строится ТОЛЬКО если nogo реально на маршруте впереди в окне ~2.5км, иначе toast «впереди
затыка нет». Цель - первая точка за nogo + 300м. Раньше строил всегда и прыгал «к первому вейпоинту»
(глобально ближайшая точка на петле). Проверка: ЖДЁТ устройство (поставить nogo впереди/без nogo).
### П.5 - поправка высот по проездам: база SRTM + медиана как сдвиг (коммит 0d5a598)
Rust `get_elev_correction(minPasses)`: по ячейке ~20м медиана (`altitude_gps_m` − `altitude_srtm_m`)
по >= N разным сессиям-проездам. SRTM остаётся базой; это только сдвиг вверх/вниз там, где катал.
JS `applyRiddenElevCorrection` применяет сглаженную дельту к высотам маршрута в превью
(`showRouteDetail`), недеструктивно (копия; нет покрытия -> без изменений). Порог проездов -
`localStorage baldav_elev_corr_passes` (деф. 3). Проверка: ЖДЁТ (ячейки с многими проездами).
### П.3 - brouter: ИЗБЕГАТЬ (Part A, коммит 85aa87e). Part B - следующим заходом.
Профиль `trekking.brf` (asset + staging синхронно): штраф на броды (`ford=yes|stepping_stones`,
node и way), `tracktype=grade5`, `trail_visibility=bad|horrible|no`; тумблер `%allow_adventure%`
(деф. false = избегать). Теги подтверждены в lookups.dat. Синтаксис сверен с грамматикой BRouter,
но локального brouter-jar нет -> ПРОВЕРКА СИНТАКСИСА только маршрутизацией на устройстве (ошибка
профиля = routeBrouter бросает ошибку = безопасный отказ, не кривой маршрут).
## НЕ сделано (объявлено явно)
- **П.3 Part B** - показать опасность на превью + опция «испытать удачу»: нужен (1) спайк, отдаёт ли
btools `OsmTrack.messageList` теги путей рядом с GPX в `routeBrouter`; (2) если да - пометить
ford/вода/крутой спуск на превью + счётчик; (3) отдельный профиль `trekking-adventure.brf`
(allow_adventure=true) + тумблер в редакторе (профиль выбирается по ИМЕНИ файла, параметры в
btools рантаймом не пробрасываются). Пока включён только default-avoid.
- Все проверки на устройстве (телефон отключился): П.1/П.2/П.4/П.5 + синтаксис профиля П.3.
## Критичные места (для продолжения)
- `frontend/js/gps.js`: `routeElevStats` (grades окно), `filterAndSmoothAlt`.
- `frontend/js/main.js`: `routeSlopeColor`/`ROUTE_SLOPE_UP`/`ROUTE_SLOPE_DOWN`/`slopeLegendHtml`,
`drawRoutePolyline`, `rejoinReroute`, `loadElevCorrection`/`applyRiddenElevCorrection`.
- `src-tauri/src/lib.rs`: `get_elev_correction`.
- `.../assets/brouter/profiles2/trekking.brf` (+ `tools/brouter-web-staging/profiles2/trekking.brf`).
- `MainActivity.kt` `routeBrouter` (для Part B - messageList).