Сегодня мы запустили новую версию веб-интерфейса сервиса Яндекс.Метро. Теперь в новом «островном» дизайне доступны схемы метрополитена пяти городов. Но мы обновили не только визуальную часть, но и перенесли всю логику с серверной стороны на клиентскую.
В этом посте мы хотели бы рассказать, как именно мы это делали, какие решения выбирали и почему.
Сервис Яндекс.Метро был запущен в далеком 2007 году. С тех пор веб-интерфейс практически не развивался, дизайн выглядел откровенно устаревшим. Почтенный возраст сервиса сказывался и на технической стороне. Все схемы представляли собой наборы изображений в формате GIF, на которых при помощи координат размещались объекты. Из-за этого возникали трудности с обновлением графа, нельзя было самостоятельно вносить изменения в схему (приходилось привлекать аутсорсеров, заказывать у них картинки). Так как названия станций также были вшиты в картинки, локализация схем потребовала бы генерации полных наборов GIF-изображений.
Никто из разработчиков серверной части сейчас над этим сервисом не работает, а документирована она очень слабо. Так что бэкэнд старого Яндекс.Метро — это в некотором роде черный ящик, который умел принимать параметры и возвращать маршруты. Однако поддерживать его при этом было крайне трудно. Сервис определенно требовал обновления, и главный вопрос заключался в том, как максимально приблизить его по уровню технологий к мобильным приложениям.
Нельзя сказать, что Яндекс.Метро было совершенно заброшено, у него есть достаточно широкая аудитория. Веб-интерфейс ежедневно посещает около 220 тысяч человек, приложение для Android скачано 3 миллиона раз, для iOS – 1 миллион загрузок. Стоит отметить, что мобильные приложения разрабатывались несколько позже веб-версии, и лишены большинства ее недостатков.
При разработке нужно было учесть несколько важных перемен, произошедших с момента запуска Метро:
Для воспроизведения графа были использованы те же данные что и для мобильных приложений. У них уже была собрана база XML-файликов, каждый из которых содержал по одной схеме и данные о связанности графа. Это также упростит локализацию: в ближайшее время все схемы будут переведены на разные языки.
В самом начале разработки встал вопрос о том, какую технологию использовать для отрисовки схемы: canvas, SVG или CSS3. Рассматривался также способ отрисовки через визуализационную библиотеку RaphaelJS. Эта библиотека рисует в SVG, но если она обнаруживает IE8, отрисовка происходит в VML, чем обеспечивается максимальная кроссбраузерность.
Сначала на основе XML-файлов было написано три прототипа схемы: на canvas, SVG и RaphaelJS. Все три варианта были прогнаны через бенчмарки. По результатам самым быстрым оказался SVG-вариант, за ним с не очень значительным отставанием последовал canvas, а самым медленным оказался вариант на основе RaphaelJS. Слишком уж много времени тратится на подгрузку и исполнение кода, проверяющего, какие функции доступны в используемом браузере. На ста итерациях отрисовки у нас получились следующие результаты в Chrome и Firefox соответственно:
Таким образом, окончательный выбор стоял между canvas и SVG. У canvas есть свои преимущества, но для рисования векторных схем SVG подходит лучше. Дело в том, что SVG-элементы хорошо вписываются в DOM страницы. А значит, можно обращаться к ним как к обычным DOM-нодам со знакомым интерфейсом, отлавливать события и т.д. Кроме того, в векторном формате отпадает необходимость перерисовывать схему при изменении масштаба.
Чтобы иметь возможность выделять маршруты на схеме, потребовалось разделить ее на слои. Самый нижний слой предназначен для статичных объектов, помогающих сориентироваться. В будущих версиях там будут отображаться реки и другие заметные элементы ландшафта. На среднем слое рисуется вся схема. Самый верхний слой – это слой маршрута. Чтобы иметь возможность выделять его, между ним и слоем общей схемы есть еще один слой: подключая его, мы обеспечиваем размытость общей схемы.
Позже для удобства разработки и присваивания CSS-стилей слой с общей схемой был разбит на три: с перегонами, станциями и переходами.
У новой схемы есть неоспоримые преимущества: она векторная, ее можно масштабировать без написания дополнительного кода, ее проще поддерживать и добавлять дополнительные фичи. К сожалению, изменения в схему пока вносятся вручную через XML-файлы, но даже это проще, чем было в изначальной версии.
Вся логика прокладки маршрутов в старой версии Метро исполнялась на серверной стороне. На каждое действие пользователя на сервер отправлялся запрос, там просчитывался новый вариант и отправлялся ответ. Для расчетов применялся алгоритм Дейкстры с небольшими модификациями для поддержки поиска нескольких маршрутов, например, можно обрубить последнее ребро и продолжить поиск путей.
Примерно тот же алгоритм использовался и при создании мобильных приложений, но так как разрабатывались они несколько позже, в них применены более современные технологии и подходы. Там вся логика уже перенесена в клиентскую часть. В iOS-версии граф представлен в виде матрицы смежности, в Android-клиенте реализована объектно-ориентированная модель. Станции представлены в виде JS-объектов, у каждого из которых есть массив линков — перегонов. Эти соединения также являются объектами.
При выборе основы для веб-интерфейса рассматривались оба варианта. Но модель из приложения под Android показалась нам более оптимальной.
На каждый запрос маршрутизатор возвращает порядка 30 маршрутов. Релевантных из них очень мало – не больше 10%. Но проблема заключается в том, что у многих пользователей разное понимание об «оптимальном маршруте». Некоторые предпочитают скорость, а некоторые – комфорт. Девушка на высоких каблуках будет готова потратить лишние 10 минут, чтобы не идти по длинному переходу, а вот для человека, опаздывающего на самолет, эта разница будет критичной. А значит, маршруты нужно оценивать по разным критериям.
Чтобы не усложнять всю систему, были разработаны специальные фильтры: класс объектов, интерфейс которых состоит из одного метода, выполняющего одно простое действие. Также были добавлены композитные фильтры, содержащие множество фильтров. Они применяются к массиву маршрутов по заданному разработчиком порядку.
Теперь фильтрация вынесена в отдельный модуль. Это позволит нам в ближайшем будущем унифицировать процесс фильтрации на всех платформах. Таким образом, выбор маршрута не будет зависеть от того, с какого устройства введен запрос.
При разработке MVC мы воспользовались уже имеющимися в Яндексе наработками: JS-библиотеками, отвечающими за островной дизайн, логику, хранение данных и оповещение компонентов о пользовательских действиях и изменениях состояний.
Непосредственно после запуска нового варианта сервиса мы планируем привести нашу схему в соответствие с официальной. Далее мы внедрим некоторые функции которые уже есть в мобильных приложениях. Например, информацию о том, из каких вагонов удобнее всего делать пересадку. Также мы реализуем уже заложенные в архитектуре фичи: возможность выбора маршрутов и перевод схем на несколько языков. Кроме того, в планах разработка визуального инструмента для внесения изменений в схему. Пока что любые изменения вносятся путем правки xml-файлов. Если же появится визуальный редактор, один контент-менеджер сможет оперативно вносить изменения одновременно под все платформы.
В этом посте мы хотели бы рассказать, как именно мы это делали, какие решения выбирали и почему.
Сервис Яндекс.Метро был запущен в далеком 2007 году. С тех пор веб-интерфейс практически не развивался, дизайн выглядел откровенно устаревшим. Почтенный возраст сервиса сказывался и на технической стороне. Все схемы представляли собой наборы изображений в формате GIF, на которых при помощи координат размещались объекты. Из-за этого возникали трудности с обновлением графа, нельзя было самостоятельно вносить изменения в схему (приходилось привлекать аутсорсеров, заказывать у них картинки). Так как названия станций также были вшиты в картинки, локализация схем потребовала бы генерации полных наборов GIF-изображений.
Никто из разработчиков серверной части сейчас над этим сервисом не работает, а документирована она очень слабо. Так что бэкэнд старого Яндекс.Метро — это в некотором роде черный ящик, который умел принимать параметры и возвращать маршруты. Однако поддерживать его при этом было крайне трудно. Сервис определенно требовал обновления, и главный вопрос заключался в том, как максимально приблизить его по уровню технологий к мобильным приложениям.
Нельзя сказать, что Яндекс.Метро было совершенно заброшено, у него есть достаточно широкая аудитория. Веб-интерфейс ежедневно посещает около 220 тысяч человек, приложение для Android скачано 3 миллиона раз, для iOS – 1 миллион загрузок. Стоит отметить, что мобильные приложения разрабатывались несколько позже веб-версии, и лишены большинства ее недостатков.
При разработке нужно было учесть несколько важных перемен, произошедших с момента запуска Метро:
- Технологии визуализации в браузерах шагнули далеко вперед, отрендерить карту на стороне клиента уже не составляет никаких проблем.
- Сильно улучшилась производительность JavaScript-движков. Многие задачи, которые раньше можно было делать только на сервере, теперь вполне выполнимы на клиентской стороне.
- Снизилась доля устаревших браузеров. IE6 и IE8 почти не встречаются, а значит, нет причин использовать GIF.
Как разрабатывалась новая версия
Для воспроизведения графа были использованы те же данные что и для мобильных приложений. У них уже была собрана база XML-файликов, каждый из которых содержал по одной схеме и данные о связанности графа. Это также упростит локализацию: в ближайшее время все схемы будут переведены на разные языки.
В самом начале разработки встал вопрос о том, какую технологию использовать для отрисовки схемы: canvas, SVG или CSS3. Рассматривался также способ отрисовки через визуализационную библиотеку RaphaelJS. Эта библиотека рисует в SVG, но если она обнаруживает IE8, отрисовка происходит в VML, чем обеспечивается максимальная кроссбраузерность.
Сначала на основе XML-файлов было написано три прототипа схемы: на canvas, SVG и RaphaelJS. Все три варианта были прогнаны через бенчмарки. По результатам самым быстрым оказался SVG-вариант, за ним с не очень значительным отставанием последовал canvas, а самым медленным оказался вариант на основе RaphaelJS. Слишком уж много времени тратится на подгрузку и исполнение кода, проверяющего, какие функции доступны в используемом браузере. На ста итерациях отрисовки у нас получились следующие результаты в Chrome и Firefox соответственно:
technology | time_to_download | time_to_draw | overall_time |
canvas | 34.58 ms | 181.72 ms | 216.30 ms |
svg | 28.02 ms | 74.02 ms | 102.07 ms |
svg-raphael | 45.87 ms | 1147.58 ms | 1193.45 ms |
technology | time_to_download | time_to_draw | overall_time |
canvas | 55.26 ms | 769.16 ms | 824.42 ms |
svg | 102.60 ms | 136.51 ms | 239.11 ms |
svg-raphael | 148.40 ms | 2369.88 ms | 2518.28 ms |
Таким образом, окончательный выбор стоял между canvas и SVG. У canvas есть свои преимущества, но для рисования векторных схем SVG подходит лучше. Дело в том, что SVG-элементы хорошо вписываются в DOM страницы. А значит, можно обращаться к ним как к обычным DOM-нодам со знакомым интерфейсом, отлавливать события и т.д. Кроме того, в векторном формате отпадает необходимость перерисовывать схему при изменении масштаба.
Чтобы иметь возможность выделять маршруты на схеме, потребовалось разделить ее на слои. Самый нижний слой предназначен для статичных объектов, помогающих сориентироваться. В будущих версиях там будут отображаться реки и другие заметные элементы ландшафта. На среднем слое рисуется вся схема. Самый верхний слой – это слой маршрута. Чтобы иметь возможность выделять его, между ним и слоем общей схемы есть еще один слой: подключая его, мы обеспечиваем размытость общей схемы.
Позже для удобства разработки и присваивания CSS-стилей слой с общей схемой был разбит на три: с перегонами, станциями и переходами.
У новой схемы есть неоспоримые преимущества: она векторная, ее можно масштабировать без написания дополнительного кода, ее проще поддерживать и добавлять дополнительные фичи. К сожалению, изменения в схему пока вносятся вручную через XML-файлы, но даже это проще, чем было в изначальной версии.
Поиск путей на графе
Вся логика прокладки маршрутов в старой версии Метро исполнялась на серверной стороне. На каждое действие пользователя на сервер отправлялся запрос, там просчитывался новый вариант и отправлялся ответ. Для расчетов применялся алгоритм Дейкстры с небольшими модификациями для поддержки поиска нескольких маршрутов, например, можно обрубить последнее ребро и продолжить поиск путей.
Примерно тот же алгоритм использовался и при создании мобильных приложений, но так как разрабатывались они несколько позже, в них применены более современные технологии и подходы. Там вся логика уже перенесена в клиентскую часть. В iOS-версии граф представлен в виде матрицы смежности, в Android-клиенте реализована объектно-ориентированная модель. Станции представлены в виде JS-объектов, у каждого из которых есть массив линков — перегонов. Эти соединения также являются объектами.
При выборе основы для веб-интерфейса рассматривались оба варианта. Но модель из приложения под Android показалась нам более оптимальной.
Фильтрация результатов поиска
На каждый запрос маршрутизатор возвращает порядка 30 маршрутов. Релевантных из них очень мало – не больше 10%. Но проблема заключается в том, что у многих пользователей разное понимание об «оптимальном маршруте». Некоторые предпочитают скорость, а некоторые – комфорт. Девушка на высоких каблуках будет готова потратить лишние 10 минут, чтобы не идти по длинному переходу, а вот для человека, опаздывающего на самолет, эта разница будет критичной. А значит, маршруты нужно оценивать по разным критериям.
Чтобы не усложнять всю систему, были разработаны специальные фильтры: класс объектов, интерфейс которых состоит из одного метода, выполняющего одно простое действие. Также были добавлены композитные фильтры, содержащие множество фильтров. Они применяются к массиву маршрутов по заданному разработчиком порядку.
Теперь фильтрация вынесена в отдельный модуль. Это позволит нам в ближайшем будущем унифицировать процесс фильтрации на всех платформах. Таким образом, выбор маршрута не будет зависеть от того, с какого устройства введен запрос.
Архитектура клиентского приложения
При разработке MVC мы воспользовались уже имеющимися в Яндексе наработками: JS-библиотеками, отвечающими за островной дизайн, логику, хранение данных и оповещение компонентов о пользовательских действиях и изменениях состояний.
Планы
Непосредственно после запуска нового варианта сервиса мы планируем привести нашу схему в соответствие с официальной. Далее мы внедрим некоторые функции которые уже есть в мобильных приложениях. Например, информацию о том, из каких вагонов удобнее всего делать пересадку. Также мы реализуем уже заложенные в архитектуре фичи: возможность выбора маршрутов и перевод схем на несколько языков. Кроме того, в планах разработка визуального инструмента для внесения изменений в схему. Пока что любые изменения вносятся путем правки xml-файлов. Если же появится визуальный редактор, один контент-менеджер сможет оперативно вносить изменения одновременно под все платформы.