Skip to content

Use Case #5: Удаление промежуточной точки (Route Auto-Healing)

Исходное состояние: У пользователя есть тщательно проложенный маршрут из 5 точек: А(Старт) → Б → В → Г → Д(Финиш). Действие: Пользователь понимает, что заезд в точку В (дальнее кафе) ему не нужен. Он удаляет точку В (через список или меню на карте).


1. Интент (Намерение пользователя)

"Я хочу вырезать этот кусок пути. Пожалуйста, соедини предыдущую точку (Б) и следующую (Г) самым оптимальным маршрутом напрямую. При этом категорически запрещаю трогать участки А → Б и Г → Д — я потратил полчаса на их ручную настройку!"

2. Идеальная логика системы (Хирургическая изоляция)

Снова работает правило «Якорей»:

  • Идентификация соседей: Система определяет соседей удаляемой точки: Б и Г. Они становятся жесткими якорями.
  • Блокировка графа: Участки А → Б и Г → Д «замораживаются» в памяти.
  • Локальный пересчет: Запрос на прокладку НОВОГО маршрута уходит только для сегмента Б → Г.
  • Очистка мусора (Важнейший нюанс!): Если на участках Б → В или В → Г ранее были наставлены «мягкие» точки (shaping points), они должны каскадно удалиться вместе с точкой В. Если система их оставит, новый маршрут Б → Г всё равно будет делать нелепую петлю к невидимым координатам.

3. Визуальный отклик и интерфейс (UX Сращивания)

Мгновенное перестроение без анимации вызывает «слепоту к изменениям» (change blindness).

  • Фаза Hover (Предотвращение ошибки): При наведении на «Удалить», точка В и прилегающие отрезки (Б → В и В → Г) подсвечиваются красным (или становятся полупрозрачными). Юзер видит "радиус поражения".
  • Фаза "Призрачный след" (Ghost Track): По клику точка В исчезает. Старая петля (Б → В → Г) становится бледно-серой пунктирной линией ("фантомный след").
  • Фаза Стягивания (Rubber-band): За доли секунды рассчитывается новый прямой путь Б → Г и отрисовывается плавной анимацией. Серые "призраки" плавно растворяются (Fade out).
  • Динамика UI:
    • Карточка удаленной точки схлопывается, нумерация пересчитывается.
    • Дистанция откручивается назад (анимация счетчика).
    • График высот плавно "сжимается", вырезая горб удаленного сегмента.

4. Антипаттерны (Как делать НЕ надо)

  • Глобальный пересчет (Эффект домино): Самый страшный грех. Оптимизация всего трека от старта до финиша уничтожает часы ручной работы пользователя.
  • Маршрут-франкенштейн (Разрыв): Оставление двух несвязанных кусков (А → Б и Г → Д). Маршрут всегда должен оставаться непрерывным графом.
  • Удаление вне экрана без фокуса: Если удаление произошло через панель слева, а точка была далеко за экраном, система пересчитает всё "втихаря". Камера обязана сделать плавный "Pan & Zoom", чтобы показать юзеру новый сросшийся участок Б → Г.

5. Edge Cases (Граничные случаи)

  • Удаление Старта (А) или Финиша (Д):
    • Решение: Никакого пересчета и обращения к серверу. Просто мгновенно "отрезаем хвост/голову" из локальной памяти. Соседняя точка автоматически становится новым Стартом/Финишем.
  • Удаление точки в тупике (Out & Back аппендикс):
    • Юзер построил маршрут А → Б → В(тупик на горе) → Б(возврат) → Г. И удаляет В.
    • Решение: Система вырезает весь этот аппендикс целиком, не пытаясь строить маршрут Б → Б, оставляя чистый транзит А → Б → Г.
  • Географический барьер (Удаление "Моста"):
    • Точки Б и Г на разных берегах реки, В — на мосту. Юзер удалил В. Ближайший другой мост в 100 км.
    • Решение: Маршрут "срастается", но его длина увеличивается на 200 км. Жизненно важен Toast-уведомление (⚠️ Дистанция увеличена на 200 км из-за поиска новой переправы) и доступная кнопка Undo.

Эпики и Задачи (Epics & Tasks)

Декомпозиция Use Case #5 для разработки.

Epic 1: Изолированный пересчет и Очистка мусора (Core Auto-Healing)

  • Task 1.1: Каскадное удаление (Garbage Collection). При удалении Waypoint, функция-редьюсер в plannerStore должна найти все промежуточные точки с type: 'shaping', лежащие на удаляемых сегментах (Б → В и В → Г), и удалить их из массива вместе с точкой В.
  • Task 1.2: Изоляция сегментов. Функция запроса к API должна отправлять роутеру только пару координат [Сосед 1, Сосед 2] (Б и Г), не передавая весь остальной трек. Полученная новая геометрия вставляется между старыми, замороженными сегментами.
  • Task 1.3: Обработка Start/Finish & Out-and-back. Написать алгоритм определения граничных случаев:
    • index === 0 (Старт) или index === length - 1 (Финиш) -> только локальное удаление (splice).
    • Выявление тупиковых сегментов ([x, y, x]) -> удаление сегмента целиком без вызова API.

Epic 2: Анимации сращивания (Ghost Track & Rubber-banding)

  • Task 2.1: Hover-превью ("Радиус поражения"). При событии onMouseEnter на кнопке корзины в списке точек, вызывать экшен, который подсвечивает (меняет цвет или добавляет свечение/opacity) на удаляемые слои геометрии в MapLibre.
  • Task 2.2: Ghost Track. При onClick (удаление), перевести удаляемые сегменты геометрии в отдельный временный слой (серая пунктирная линия).
  • Task 2.3: Cross-fade анимация. После получения нового сросшегося сегмента Б → Г от API, анимированно отрисовать его, одновременно плавно уводя в прозрачность (fade out) слой "Ghost Track".

Epic 3: Синхронизация UI (Smart Viewport & Metrics)

  • Task 3.1: Умная камера (Pan & Zoom). Перед удалением вычислить Bounding Box нового сегмента Б → Г. Если он не виден на экране (т.к. юзер удалял из списка), инициировать fitBounds или flyTo, чтобы сфокусировать внимание на месте сращивания.
  • Task 3.2: Анимация Height Profile. Обновить компонент ElevationProfile так, чтобы удаление куска данных происходило не рывком, а плавным "схлопыванием" графиков по оси X (через D3.js или Framer Motion).
  • Task 3.3: Географические барьеры (Alerts). Если новая дистанция (Б → Г) превышает сумму старых дистанций (Б → В + В → Г) более чем на заданный коэффициент (например, +200%), триггерить Toast "Внимание: значительное увеличение дистанции".