Gap-fill (track detail): GPS.fillGaps now returns {points,gaps} with per-gap
status; the 🧩 control previews ONLY the filled dropouts (cyan casing; unfilled
= red dashed) with a Save/Cancel panel. Save is a reversible in-place patch
(estimated points added, original untouched; DB.saveGapFill/revertGapFill +
estimated/gap_filled columns), then recalculateSession. Revert removes them.
Route planner: renamed menu 'Нарисовать маршрут' → 'Нарисовать тренировку'
(it makes a workout session, not a route); routes empty-state now points at the
on-phone planner. showRouteEditor rewritten per-leg: each leg road (BRouter) or
straight independently, road by default, tap a leg to toggle it, long-press to
insert, silent straight fallback where no road; the global button sets all legs.
13 KiB
План: BRouter на телефоне — понятный гэп-филл и планировщик маршрутов
Context (зачем)
BRouter уже встроен и роутит, но две фичи на телефоне непонятны в использовании:
-
Заполнение GPS-пропусков (кнопка 🧩 на детали трека). Сейчас
GPS.fillGapsвозвращает ВЕСЬ трек с дорисованными пропусками, аGapCtlрисует его целиком (main.js:4784) — поэтому голубая линия ложится поверх всего трека, даже там, где GPS был. Не видно, где именно BRouter достроил, непонятно, что это станет треком, и НЕТ сохранения/отмены. Нужно: тап кнопки → предпросмотр ТОЛЬКО достроенных пропусков → явная кнопка сохранить/отмена. Сохранение — патч на месте, обратимо (не новый трек). -
Планирование маршрута по точкам. Оно уже есть: «Сохранённые маршруты» → «Новый маршрут» →
showRouteEditorс тумблером «🚲 По дорогам». Но пользователь его не нашёл: в меню есть отдельный пункт «Нарисовать маршрут», который на деле рисует ТРЕНИРОВКУ (сессию с калориями,showDrawRouteScreen), а пустой список маршрутов советует импортировать GPX/рисовать в Яндексе. Плюс «По дорогам» — глобальный тумблер на весь маршрут. Нужно: понятный вход + по-сегментное решение (по дорогам по умолчанию, где дороги нет — прямая, ручной тумблер влияет на текущий отрезок).
Итог решений с пользователем: (1) патч на месте обратимо; (2) по-сегментный роутинг, дорога по умолчанию, прямая где нет дороги, ручной тумблер на отрезок; (3) пункт меню переименовать (функцию оставить).
Часть A — Гэп-филл: предпросмотр только пропусков + обратимый патч на месте
A1. GPS.fillGaps → богатый результат (frontend/js/gps.js:1400)
Сейчас возвращает плоский массив. Вернуть { points, gaps }:
gaps:[{ aIdx, bIdx, routed:[{lat,lon}], addedM, chordM, status }], гдеstatus="filled" | "skipped-long" | "skipped-noroute"(сейчас пропуск молча скипается — теперь причина известна для отчёта в UI).- Достроенным точкам присваивать интерполированные ts по отрезку
[a.ts, b.ts](сейчас всем ставитсяa.ts, что ломает порядок при чтении из БД) и флагestimated: true. - Обратная совместимость: единственный вызов —
GapCtl(main.js:4783), правится тут же.
A2. GapCtl — предпросмотр только пропусков + панель сохранить/отмена (frontend/js/main.js:4772-4791, showSessionDetail)
- По тапу: посчитать
fillGaps(points), нарисовать только достроенные отрезки (каждыйrouted+ его две реальные концевые точки) отдельным ярким стилем (голубая линия с бело-чёрной оковкой, как маршрут в brouter-web) поверх исходного трека. Весь трек НЕ перерисовывать. - Пропуски со
status !== "filled"пометить иначе (красный пунктир по хорде) — видно, что их достроить не вышло. - Показать панель-баннер: «Достроено N пропусков (+X.X км). M не удалось.» + подпись «Так трек будет выглядеть после сохранения» + кнопки [Сохранить] и [Отмена].
- [Отмена] — убрать оверлей и панель, ничего не менять.
- [Сохранить] —
DB.saveGapFill(sessionId, filledPoints)→GPS.recalculateSession(id, {noDelete:true})→ перерисовать деталь. Если трек уже достроен — вместо этого показывать [Убрать достройку] →DB.revertGapFill(id). - Найти в
showSessionDetailимя переменной сессии/sessionId(рядом уже естьpoints).
A3. БД: обратимое хранение (frontend/js/db.js)
- Миграции идемпотентным паттерном (как
db.js:1422-1433):if (!gpsCols.includes("estimated")) ALTER TABLE gps_points ADD COLUMN estimated INTEGER DEFAULT 0;if (!gsCols.includes("gap_filled")) ALTER TABLE gps_sessions ADD COLUMN gap_filled INTEGER DEFAULT 0. saveGapFill(sessionId, filledPoints): вставить только достроенные точки сestimated=1,seq = seq предыдущей реальной точки(порядок держитORDER BY COALESCE(seq,999999), tsвdb.js:866— совпадающий seq + интерполированный ts ставит их между концами), батч- вставкой по образцуimportGpxSession/addGpsPointsBulk(db.js:751,874). Выставитьgps_sessions.gap_filled=1. Оригинальные точки не трогаются.revertGapFill(sessionId):DELETE FROM gps_points WHERE session_id=? AND estimated=1;gap_filled=0. Затем пересчёт статы вызывающей стороной.- Загрузчик
db.js:866ужеSELECT *— колонкуestimatedподхватит сам; убедиться, что она мапится вpoint.estimated.computeStatsуже трактует.estimated(gps.js:241) — доп. правок в расчёте не нужно. - Продуктовый
seed_meta.versionНЕ трогаем (это пользовательская БД, не products_seed).
A4. Отрисовка трека и стиль (frontend/js/main.js:~4912, frontend/css/styles.css)
- В отрисовке трека детали пометить
estimated-точки/отрезки отдельным стилем, чтобы после сохранения было видно, какие участки достроены. - CSS: стиль голубой линии достройки с бело-чёрной оковкой + стиль панели предпросмотра.
Часть B — Планировщик: понятный вход + по-сегментный роутинг
B1. Переименовать пункт меню (frontend/js/main.js:2733)
«Нарисовать маршрут» → «Нарисовать тренировку» (он сохраняет сессию через
showDrawRouteScreen, а не маршрут). Функцию оставить.
B2. Подсказка в пустом списке маршрутов (frontend/js/main.js:3261)
Текст сейчас зовёт импортировать GPX/рисовать в Яндексе. Дописать, что «Новый маршрут» — это планирование прямо в телефоне по дорогам (BRouter), тапами по карте.
B3. showRouteEditor — по-сегментный роутинг (frontend/js/main.js:3486-3649)
Заменить глобальные brouterMode + единый routeGeom на модель по отрезкам:
- Состояние:
waypoints[],legMode[i]("road"по умолчанию |"straight"),legGeom[i](кэш дорожной геометрии отрезка). - Каждый отрезок между соседними точками роутить
BRouter.route([wp[i],wp[i+1]], "trekking")(debounce). Если отрезок не проложился (нет дороги/сегментов/сети) — этот отрезок падает на прямую линию молча (без тост-спама; можно тонко пометить). - Тап по отрезку переключает режим ИМЕННО этого отрезка (дорога ↔ прямая) — это и есть «ручной тумблер на текущий отрезок». (Тап по точке — удалить, как сейчас; тап по карте — добавить точку в конец; тап по линии сейчас вставляет точку — развести с «переключить отрезок»: вставка по долгому тапу либо отдельная явная механика; уточнить при реализации.)
- Рисовать конкатенацию всех отрезков; сохранять итоговую геометрию (как сейчас,
DB.addRoute/updateRoutePoints). ПереиспользоватьparseRouteGPX, на детали —routeElevStats(профиль высот). - Глобальную кнопку «🚲 По дорогам» превратить в «все отрезки: по дорогам/прямые» (задаёт дефолт для новых отрезков), по-сегментный тап её переопределяет. Подсказка внизу: «тап карты — точка · тап отрезка — дорога/прямая · тап точки — удалить».
Файлы
frontend/js/gps.js—fillGaps(A1).frontend/js/main.js—GapCtl/showSessionDetail(A2, A4), меню (B1),showRoutesScreen(B2),showRouteEditor(B3).frontend/js/db.js— миграции +saveGapFill/revertGapFill(A3).frontend/css/styles.css— стили достройки/панели (A4).
Переиспользование (не изобретать заново)
DB.importGpxSession/addGpsPointsBulk(db.js:874,751) — батч-вставка точек.GPS.recalculateSession(id,{noDelete:true})(gps.js:1697) — пересчёт статы без удаления.computeStatsуже понимаетestimated(gps.js:241); загрузчик точекdb.js:866.BRouter.route(router.js),GPS.parseRouteGPX(gps.js:1369),routeElevStats.- Образец обратимости — сглаживание (
gps_sessions.smoothed+lat_smoothed/lon_smoothed,saveSmoothedTrackdb.js:802).
Верификация (десктоп, Xvfb :99 + домашний RouteServer; потом на теле)
- Гэп-филл. Открыть трек с пропуском (сессия
1782115144011, два дропаута): 🧩 → подсвечены ТОЛЬКО пропуски + панель «Достроено N (+X км), M не удалось» → Сохранить → деталь показывает трек с помеченными достроенными участками, статистика пересчитана → Убрать достройку возвращает исходный трек. Скрины на вирт. экране. - Планировщик. «Сохранённые маршруты» → «Новый маршрут» → 3 точки: отрезки легли по дорогам; тап по отрезку переключает его на прямую и обратно; сохранить → деталь с профилем высот. Скрин.
- Меню: пункт называется «Нарисовать тренировку»; подсказка в пустом списке зовёт планировать на телефоне.
- Коммит перед тестом, затем на устройстве
./build.sh install -d— гэп-филл и планировщик встроенным движком офлайн (докачать сегмент региона, если ещё не докачан).
Явно не делаем
- Не создаём новый трек-копию (выбран патч на месте).
- Не трогаем общий список городов-пресетов, координаты,
pois.json, домен (privacy-скраб был отдельной задачей). - Не переписываем
showDrawRouteScreen(только переименование пункта меню).