Что, если навигатор перестанет упрямо твердить «Развернитесь!», когда вы свернули с маршрута и предложит новый, более вам подходящий?

Изначально мы хотели решить этот конкретный кейс, ведь слишком прямолинейный алгоритм не допускал, что пользователь может намеренно выбрать другой путь, и всегда стоял на своём. Вот как это выглядело:

Навигатор преждевременно считал, что пользователь развернулся, перестраивал маршрут, а затем, поняв, что движение продолжается по прежнему направлению, возвращал его обратно. В результате пользователь слышал много лишних голосовых инструкций и наблюдал постоянные развороты камеры навигатора.

Решать проблему начали с логики перестроения и реализовали алгоритм дискриминации маршрута. По сути, этот алгоритм научился строить маршруты, которые не были похожи на изначальный.

Дискриминация маршрута

У каждого ребра в процессе работы алгоритма поиска появляется числовой вес (стоимость) — комплексная характеристика, включающая реальное (оценочное) время проезда по ребру и набор штрафов. Эти штрафы не учитываются в итоговом времени, но позволяют менять поведение поиска в соответствии с нашей продуктовой бизнес-логикой. Вес влияет на то, в каком порядке алгоритм просматривает рёбра: на очередной итерации всегда выбирается ребро с наименьшим весом.

Исходные веса: алгоритм выбирает центральный путь как самый дешёвый по сумме весов
Исходные веса: алгоритм выбирает центральный путь как самый дешёвый по сумме весов

Суть дискриминации — искусственно завысить веса определённых рёбер (которые мы хотим дискриминировать), тем самым пересортировав очередь на обработку. Если без дискриминации ребро имело минимальный вес и обрабатывался первым, то после дискриминации оно уступает свою очередь другим рёбрам с более низким весом. А завышение это достигается простым умножением исходного веса на повышающий коэффициент больше 1.

Дискриминировали центральные рёбра — их веса увеличили в три раза.  В этом случае алгоритм выбирает верхний путь, так как он стал дешевле по сравнению с центральным с учётом дискриминации.
Дискриминировали центральные рёбра — их веса увеличили в три раза. В этом случае алгоритм выбирает верхний путь, так как он стал дешевле по сравнению с центральным с учётом дискриминации.

Предпочитаемые рёбра

Дальше, казалось, дело техники — включить этот алгоритм в приложении. Сделали — и всё пошло не по плану. Мы не учли, что пользователь не всегда отклоняется от маршрута «осознанно». Значительная часть перестроений происходит случайно, особенно в начале маршрута: например, из-за погрешностей работы GPS или незначительных изменений маршрута в процессе движения по дворам. Такие случаи не должны менять маршрут. Пришлось откатить и пересмотреть подход.

Параллельно появилась другая идея: если пользователя устраивает маршрут, но он сходит с него не потому, что хочет поехать по другому, а по иным причинам (например, случайно, или чтобы быстрее проехать одно локальное место) — то предлагать новый, максимально похожий на исходный. Так появилась концепция предпочитаемых рёбер — занижение весов нужных рёбер с помощью коэффициента от 0 до 1.

Алгоритм предпочитаемых ребёр работает ровно по тому же принципу, что и дискриминация, только веса мы не завышаем, а занижаем, используя понижающий коэффициент (от 0 до 1). В этом примере алгоритм уже выбрал нижний путь, так как веса стали в пять раз меньше.
Алгоритм предпочитаемых ребёр работает ровно по тому же принципу, что и дискриминация, только веса мы не завышаем, а занижаем, используя понижающий коэффициент (от 0 до 1). В этом примере алгоритм уже выбрал нижний путь, так как веса стали в пять раз меньше.

В итоге у нас уже два алгоритма, но не было логики их включения.

Логика включения

Первая идея была максимально простой: если нас раздражает, что навигатор, после того как мы сошли с предложенного маршрута, упрямо пытается вернуть нас обратно и каждый раз предлагает развернуться — значит, можно использовать пару проигнорированных разворотов как триггер для включения дискриминации маршрута.

Сделали первую сборку — и поняли, что это так не работает. На практике ключевое — тестировать алгоритмы в реальной поездке, сидя за рулём на привычных маршрутах. Оказалось, что ждать двух разворотов слишком долго — хочется сразу новый маршрут. А если продолжать сходить с маршрута, алгоритм начинает ухудшать маршрут до бесконечности.

В результате мы:

  • сократили время срабатывания новой логики, 

  • добавили отключающий дискриминацию триггер — любое последующее перестроение маршрута после срабатывания дискриминации.

Параллельно подумали и про предпочитаемые рёбра. Сохранять выбранный маршрут — это хорошо, но чаще всего нужен просто лучший маршрут в данный момент. Мы реализовали несколько эвристик, чтобы лучше определять, когда стоит сохранить маршрут, а когда пересчитать его заново для оптимального результата:

  • если пользователь выбирает первый маршрут — считаем, что ему всегда важно получать самый быстрый маршрут в моменте;

  • если выбран альтернативный — считаем, что он пользователю приглянулся по каким-то причинам больше других, и его важно сохранить.

Казалось, что это всё. Но во время следующей итерации тестирования в реальных условиях обнаружилась ещё одна раздражающая проблема: стоит отклониться от хорошего маршрута и получить дискриминированный вариант — навигатор сразу подсказывает, что найден маршрут лучше. 

Логично для системы, но не для пользователя. И так понятно, что новый маршрут будет хуже. Нет смысла предлагать «лучший» вариант вернуться на исходный маршрут — это лишний раз будет раздражать пользователя бесполезными уведомлениями. 

Поэтому мы дополнили логику новым условием: поиск лучшего маршрута должен начинаться не сразу, а через некоторое время после срабатывания алгоритма дискриминации.

Также не забыли и про ещё один сценарий: движение по дворам в начале маршрута. Мы этого можем не замечать, но пока навигатор запущен и машина выезжает из двора на улицу, он может несколько раз перестроить маршрут. Это связано как с уточнением GPS-сигнала, так и с препятствиями во дворах, которые приходится объезжать. В результате наша логика обросла ещё одним условием: пока пользователь едет по двору, мы не дискриминируем маршрут, т.е. любое перестроение во дворе не будет влиять на дальнейший маршрут.  

Вывод

Как только мы внедрили алгоритм дискриминации, концепцию предпочитаемых рёбер и гибкую логику включения, навигация в 2ГИС стала заметно удобнее. 

Самое важное — мы избавились от ощущения, что навигатор «не понимает» пользователя. Маршруты стали предсказуемее и логичнее: система лучше понимает намерения водителя и предлагает более подходящий вариант. Особенно приятно, что ушёл негативный фидбек — теперь поведение навигатора вызывает меньше вопросов и раздражения.

Мы не останавливаемся и продолжаем развивать наши алгоритмы. Если хочешь с нами, то у на как раз открыты вакансии в команде Транспорт. Нам очень нужны C++/Qt/QML разработчик под Android, менеджер продукта, проектный менеджер и Senior QA-инженер.

Комментарии (2)


  1. saege5b
    24.06.2025 09:33

    Вот ещё бы как-то решили бы проблему спуфинга жпс...


  1. Nick0las
    24.06.2025 09:33

    Тема конечно интересная. А вы не думали добавить в навигатор возможность личной настройки правил дискриминации? А то ваш навигатор упорно не желает вести по грунтовым дорогам, даже если это намного быстрее (и комфортнее для некоторых водителей), зато часто предлагает неприятные маневры типа U-образного разворота на многополосном шоссе, хотя в принципе можно проехать немного дольше, но без таких маневров.