Appearance
Use Case #3: Drag & Drop маршрута (Эффект «Резинки» / Rubber-banding)
Исходное состояние: Есть построенный маршрут А → Б → В. Линия проходит по скучной трассе, а юзер хочет пустить ее через лес неподалеку. Действие: Пользователь наводит курсор на линию, зажимает кнопку мыши/палец и тянет.
Это самый сложный в программной реализации, но самый тактильный и интуитивный инструмент тонкой настройки.
1. Интент (Намерение)
"Я согласен со стартом и финишем, но мне не нравится конкретно этот кусок, который предложил алгоритм. Я хочу его изогнуть, не сломав остальную часть маршрута".
2. Идеальная логика системы (Концепция Якорей)
Самый важный вопрос — какая часть должна тянуться?
- Правило Якорей: Система должна определить две ближайшие к месту захвата существующие точки. Если пользователь схватил трек на участке между А и Б, то именно они становятся Якорями (Anchors). Точка В и участок Б → В жестко замораживаются.
- В месте, где пользователь бросил курсор, создается новая точка (X).
- Алгоритм пересчитывает роутинг ТОЛЬКО для куска А → X → Б.
3. Секрет идеального UX: Формирующие точки (Shaping points)
Это то, что отличает профессиональный продукт от студенческой поделки. Если юзер 5 раз потянул за трек, у него не должно появиться 5 новых полноправных остановок в списке слева ("Точка 1", "Точка 2"). Иначе навигатор во время поездки сведет его с ума голосом: "Вы достигли промежуточной точки".
- При Drag & Drop должны создаваться «Мягкие» (Формирующие) точки.
- Они отображаются на самой линии маленьким белым кружком, но в текстовом списке маршрута они скрыты. Их единственная задача — гнуть линию.
4. Визуальный отклик (Фазы UX)
- Hover (Наведение): Курсор меняется на "руку" ✋. На линии трека под курсором загорается индикатор (жирная белая точка) — сигнал: "Хватай меня".
- Drag (Тянем):
- Исходная линия маршрута (А → Б) не исчезает сразу, а становится полупрозрачной (Ghost line) — мы показываем, от чего юзер отказывается.
- От якоря А к курсору и от курсора к якорю Б тянутся пунктирные прямые линии (по воздуху). Мы не пытаемся пересчитывать маршрут по дорогам прямо во время движения мыши — это вызывает жуткие лаги. Пунктир — идеальный компромисс.
- Drop (Бросок / Отпускание):
- Применяется логика Snapping (Прилипание). Точка X сама магнитится к ближайшей легальной дороге.
- Пунктир пропадает, на долю секунды появляется лоадер, и отрисовывается новый пересчитанный трек по дорогам. Полупрозрачная старая линия пропадает.
5. Антипаттерны при Drag & Drop
- ❌ Глобальный пересчет: Юзер потянул за кусок в начале маршрута, а роутер радостно "оптимизировал" вообще весь трек до самого финиша, уничтожив работу, которую юзер проделал руками 10 минут назад в конце маршрута. Изоляция отрезка должна быть абсолютной!
- ❌ Бросок в "черную дыру": Если юзер бросил курсор на озеро (где нет дорог). Нельзя выдавать Alert "Маршрут не найден" и обнулять действие! Точка должна примагнититься к ближайшей тропе. А если дорог нет в радиусе 1 км, система просто рисует прямую линию (off-grid) и дает Toast-уведомление "Проложен маршрут напрямую без дорог".
Эпики и Задачи (Epics & Tasks)
Декомпозиция Use Case #3 для разработки.
Epic 1: Логика Якорей и Формирующих Точек (Anchors & Shaping Points)
- Task 1.1: Концепция Shaping Points. В стейте (
plannerStore) добавить свойствоtype: 'shaping' | 'stop'для объекта Waypoint. Обновить UI списка точек слева так, чтобы он фильтровал и не показывал точки типа'shaping'. - Task 1.2: Изоляция роутинга (Segment Isolation). Архитектура роутинга должна быть сегментированной (массив геометрий). При создании новой Shaping Point пересчет (обращение к API) должен происходить строго для сегмента
Якорь 1 -> Shaping Point -> Якорь 2. Запретить алгоритмам пересчитывать весь маршрут целиком. - Task 1.3: Определение Якорей. Разработать утилиту, которая по координате клика (в пределах геометрии трека) определяет, какому сегменту (и, соответственно, каким двум Якорям) принадлежит этот участок.
Epic 2: Фазы UX взаимодействия (MapLibre Interaction)
- Task 2.1: Hover State. На слое маршрута отслеживать событие
onMouseMove. Менять курсор и рендерить на треке под курсором динамический маркер "Точка захвата" (Grab Indicator). - Task 2.2: Drag State (Пунктиры). При
onMouseDownна "Точке захвата":- Слой текущего редактируемого сегмента переводится в
opacity: 0.3(Ghost line). - Начинает рендериться временный слой (
line-dasharray) с двумя прямыми линиями:[Якорь 1, Курсор]и[Курсор, Якорь 2]. - Отключить стандартный pan карты при перетаскивании.
- Слой текущего редактируемого сегмента переводится в
- Task 2.3: Snapping и обработка "Черной дыры" (Drop). При
onMouseUp:- Точка X передается в API как новая транзитная остановка.
- Если роутер (например, BRouter) не может найти путь и возвращает ошибку (нет дорог), переключить именно этот сегмент (Якорь 1 -> X -> Якорь 2) в режим
Direct(По прямой). - Вызвать Toast-уведомление "Проложен маршрут напрямую без дорог".
Epic 3: Оптимизация производительности
- Task 3.1: Запрет реалтайм-запросов (No API Calls on Drag). Строго запретить вызовы к роутинг-бэкенду в фазе Drag (движение мыши). API вызывается только один раз на событии
onMouseUp(отпускание курсора). - Task 3.2: Анимация загрузки. На время API запроса (после Drop) показывать UI-лоадер в месте броска или в панели точек, чтобы пользователь понимал, что запрос в обработке.