diff --git a/README.md b/README.md index 71b57fa..66f807a 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ - **OTA обновления** — проверка BUILD_NUMBER на роутере (LAN) или VPS, скачивание через браузер. - **Ручные маршруты** — рисование трека на карте тапами, elevation из прошлых треков или open-meteo API. - **Сохранённые маршруты** — импорт GPX-маршрутов, нарисованных заранее (например, в конструкторе Яндекс.Карт: формат ``), выбор активного, наложение цветной линией поверх карты во время записи И на экране просмотра прошлого трека — для визуальной навигации. Управление на отдельной странице (импорт/переименование/цвет/удаление, один активный за раз), переключение линии кнопкой-тогглом. Цвета «проезженных дорог» настраиваются раздельно для вело/пеших/бега. -- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон дискретными полосами по 1% (зелёный→жёлтый→красный→коричневый→чёрный, ≥10% чёрный) или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`` из GPX или онлайн) тянутся по цепочке: **GPXZ** (Copernicus GLO-30, точнее для уклонов; ключ в GPS-настройках, хранится только на устройстве) → open-elevation → open-meteo → OpenTopoData (без ключа). Калории пересчитываются по сглаженным точкам; для уклона высоты сглаживаются (гасит вертикальный шум SRTM). +- **Высоты маршрута (рельеф до поездки)** — экран предпросмотра маршрута (тап по названию): профиль высоты (крутые участки красным), длина/набор/сброс/макс. уклон, тап по точке — высота и уклон, разворот карты на весь экран. Топографическая обводка линии с тогглом **уклон/высота**: уклон по НАПРАВЛЕНИЮ движения, полосами по 1%: подъём зелёный→жёлтый→красный→коричневый→чёрный, спуск зелёный→cyan→синий→navy (≥10% крайний), или гипсометрия по высоте (зелёный→жёлтый→коричневый). Обводка и тоггл доступны и в оверлее на карте записанного трека и во время записи; маршрут рисуется ПОД треком. Высоты (`` из 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° (учитывает курс). Показывается ярким навигационным оверлеем (не меняет сохранённый маршрут и не прерывает запись); долгий тап по кнопке убирает оверлей. diff --git a/docs/plans/2026-10-26-elevation-slope-brouter.md b/docs/plans/2026-10-26-elevation-slope-brouter.md new file mode 100644 index 0000000..13be6cf --- /dev/null +++ b/docs/plans/2026-10-26-elevation-slope-brouter.md @@ -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. diff --git a/docs/session-logs/2026-10-26_elevation-slope-brouter.md b/docs/session-logs/2026-10-26_elevation-slope-brouter.md new file mode 100644 index 0000000..118af86 --- /dev/null +++ b/docs/session-logs/2026-10-26_elevation-slope-brouter.md @@ -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).