Авторы - Ратул Рой и Рави Патвал

Возможность отслеживать объект на Картах Google в режиме реального времени - одна из наиболее важных функций приложений, ориентированных на вызов пассажиров и доставку. Итак, при создании Ola PWA мы знали, что страница «трек-райд» будет одним из наиболее часто используемых экранов.

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

Как говорится, «Рим не за один день построили», и мы не добились желаемого результата с первого раза. Фактически, нам потребовалось несколько итераций, опросов пользователей и мозговых штурмов с командой разработчиков, чтобы постепенно приблизиться к желаемому опыту. В ходе этого путешествия мы провели значительный объем исследований, сделали довольно много POC, пробовали различные приемы, методы и алгоритмы и многое другое, многому научились.

Данные об автомобиле из вызова API опроса

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

Таким образом, изображение автомобиля нужно было перемещать на картах Google из одной координаты в другую каждые 5 секунд (настраивается). Изображение также нужно было каждый раз поворачивать в определенной степени. Ему также нужен API, который нам нужно опросить из внешнего интерфейса, чтобы получить все данные. Образец ответа этого API может быть в следующем формате.

Мы использовали API Google Map V3 для всех операций, связанных с картой.

Примечание:

Все гифки ниже представляют собой симуляцию реальных анимаций, запускаемых на Mac Book Pro с 6-кратной медленной настройкой ЦП (с использованием панели инструментов Chrome Developer). При создании гифок может произойти потеря данных, и фактическая анимация может быть более плавной. Все отчеты FPS действительно отображают реальную производительность анимации.

Этап 0: поиск существующего примера

Прежде чем мы начали кодировать эту страницу, мы хотели проверить, можем ли мы найти существующую страницу с аналогичной функциональностью. Мы нашли один.

В этом разделе карта загружалась с новой координатой широты и долготы в центре каждые 5 секунд. Автомобиль - это маркер на карте, расположенный в центре. Каждые 5 секунд карта перемещается в новый центр на основе данных API, и запускается код, чтобы снова разместить маркер в центре, создавая впечатление, что машина движется (в то время как реальность такова, что карта перемещается, а машина остается в центре). Но между удалением старого маркера и отрисовкой нового маркера в центре есть временная задержка, что вызывает шаткость.

FPS был действительно плохим (20 FPS), как мы и ожидали.

Также, если вы заметили, новые фрагменты карты загружаются каждые 5 секунд при перемещении карты. Я думаю, не очень удобен для полосы пропускания.

Это было достаточно хорошо для начала, так как мы точно знали, какие проблемы нам нужно было решить, чтобы получить лучший опыт.

  1. более плавная анимация
  2. Сохраните данные и батарею

Фаза 1: Мы начали.

Автомобиль как элемент DOM

Мы знали, что можем плавно анимировать автомобиль, используя анимацию CSS3 с аппаратным ускорением, если сможем преобразовать маркер автомобиля в элемент DOM, поскольку объект Google Marker не поддерживает анимацию CSS3. Итак, сначала мы преобразовали автомобиль в объект HTML img. Теперь нам нужно было переместить машину с одной широты на другую. Но элементы DOM работают с пикселями, а не с географическими координатами. Поэтому нам нужна была библиотека проекций для преобразования широты и долготы в соответствующие пиксели.

Мы начали с одной библиотеки Mercator Projection с открытым исходным кодом. Но, наконец, мы использовали метод getProjection () класса Overlay карты Google (API V3), чтобы получить объект проекции, а затем реализовали метод onAdd () и получили доступ к проекции внутри onAdd (). Затем использовал метод getLatLngToContainerPixel () объекта проекции, чтобы получить пиксели для заданной широты и долготы. Пример кода выглядит следующим образом.

Для более плавного эффекта мы использовали bezier в качестве функции CSS3 transition-time для анимации transform и поигрались с ее конфигурацией. Пример кода выглядит следующим образом.

Сохранение данных

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

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

С этим изменением для каждого отслеживания 1 км мы снизили необходимое панорамирование до одного раза по сравнению с 24 раз в более раннем подходе, позволяющем экономить больше данных и батареи. (при средней скорости автомобиля 30 км / час и интервале опроса 5 секунд)

Ограничение

У нас все еще оставалась проблема, особенно на мобильных устройствах. «Что происходит с автомобилем, когда пользователь панорамирует или масштабирует карту?» Если бы изображение автомобиля было маркером Google, оно бы плавно перемещалось вместе с картой панорамирования или масштабирования. Но помни ? Наш образ автомобиля был элементом DOM, работающим на другом слое. Нам нужно было поймать каждое событие на карте и соответственно перерисовать нашу машину в новых положениях. Для таких событий, как панорамирование и масштабирование пальцем (которое состоит из серии событий перетаскивания, начала перетаскивания, окончания перетаскивания и перемещения мыши), при таком большом количестве перерисовок процесс совсем не будет плавным. Также может быть задержка между прикосновениями пользователей и изменением положения автомобиля на устройстве низкого уровня.

Итак, мы пошли по пути. Мы отключили стандартные функции масштабирования и панорамирования карт Google. Мы создали собственные HTML-кнопки увеличения плюс-минус в родительском div, чтобы пользователи могли изменять уровень масштабирования, нажимая на них. Мы повторно визуализировали изображение автомобиля только при изменении уровня масштабирования, инициированном пользователем. Не очень удобно, но все же аккуратно.

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

Вы можете увидеть финальный опыт движущегося автомобиля в Фазе 1 здесь.

Пользовательские кнопки масштабирования. Панорамирование запрещено.

Вот отчет об анимации, запущенный на Mac Book Pro с 6-кратным замедлением процессора.

Идеальная анимация 60 кадров в секунду

Фаза 2: Быстрое исправление.

Панорамирование и масштабирование по умолчанию включены

Мы обнаружили, что наш подход к Phase1 в конце концов был не совсем удобным для пользователя, и большинство наших пользователей были более склонны к панорамированию и масштабированию карты, чтобы приспособиться к их области просмотра. На самом деле очень немногие люди нажимали на кнопки Custom Zoom. Поэтому мы снова включили функции панорамирования и масштабирования по умолчанию на Google Maps.

Скрыть машину при панорамировании и масштабировании

Но затем нам пришлось решить проблему, из-за которой нам пришлось их отключить. То есть беспрепятственно синхронизировать элемент автомобиля DOM с событиями карты по умолчанию. Мы начали изучать все события карт Google и перечислили те, которые происходят во время панорамирования или масштабирования (например, drag, dragstart, dragend, zoom_changed, center_changed, mousemove и т. Д.), И скрыли HTML car для этих событий. Мы снова сделали его видимым при событии idle (когда карта снова станет стабильной). Вы можете проверить все события на карте здесь.

Не идеально. Но все же лучше, чем Phase1. Вы можете увидеть окончательный опыт масштабирования / панорамирования Phase2 здесь.

Этап 3: НИОКР. Больше НИОКР.

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

Повторный рендеринг автомобиля при панорамировании и масштабировании

Мы пытались переставлять HTML-машину при каждом панорамировании и масштабировании. Хотя мы думали, что у нас может получиться шаткий эффект, к нашему удивлению, весь опыт на рабочем столе был действительно безупречным. Но наши сердца разбились, когда мы обнаружили недокументированное поведение Google Maps, из-за которого наше решение стало неэффективным. Мы не могли изменить положение машины во время панорамирования, так как центр карты не менялся во время панорамирования. Вы можете прочитать о проблеме здесь.

Вернуться к автомобилю в качестве маркера.

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

Отчет по анимации с маркерами.

Автомобиль как объект-символ? Эврика !!!!

У нас оставалось только два варианта.

  1. Имитируйте точные жесты панорамирования и масштабирования на прозрачном элементе div в верхней части карты и перемещайте автомобиль и карту под ним соответственно. Но пришлось много кодировать, и было действительно сложно сопоставить эластичный эффект по умолчанию, который вы получаете при панорамировании.
  2. Снова исследуйте наложения карты.

Мы выбрали второй подход и эврика !!! Мы нашли объект Symbol карты, который может плавно анимироваться на полилинии, соединяющей две координаты, и в то же время может плавно вращаться и перемещаться при панорамировании и масштабировании. Нам по-прежнему нужна была библиотека проекций, чтобы определять, когда загружать новую карту, чтобы минимизировать загрузку фрагментов карты. Мы протестировали наш POC на мобильном телефоне низкого уровня, и он работал как по волшебству.

«Анимация символов на полилинии более плавная, поскольку она является частью самой полилинии».

Для плавной анимации мы создали невидимую полилинию с конечной точкой в ​​качестве новой позиции автомобиля и начальной точкой в ​​качестве текущей точки автомобиля (текущее положение символа на карте).

Для невидимой полилинии непрозрачность и толщина штриха были установлены на минимум.

Теперь наш значок перемещается по ломаной линии, используя смещение (от 0 до 100%) и поворот от текущего поворота автомобиля до конечного значения поворота.

Пример кода для анимации символа в строке можно найти здесь.

Ограничение

Остался еще один небольшой глюк. В отличие от маркеров, объект Symbol не принимает URL-адрес значка. Это должен быть вектор с одним атрибутом «путь». Но изображение автомобиля, которое нужно было показать, было умеренно сложным с тенями и разными оттенками.

Вы можете увидеть здесь Symbol.

Фаза 4: Наконец-то !!!!

Исследования и разработки с командой дизайнеров. Несколько символов для одной машины.

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

Еще одно ограничение, снятое с помощью RequestAnimationFrame

Анимация была плавной, но затем мы столкнулись с проблемой DOM setInterval.

Современные браузеры, такие как Chrome, имеют тенденцию подавлять анимацию DOM, когда вкладка неактивна. Поэтому, когда пользователь возвращается к экрану, машина проехала бы приличное расстояние, и все анимации DOM, которые до сих пор стояли в очереди, начали выполняться, заставляя машину «летать» от источника до места назначения.

Нам на помощь приходит R equestAnimationFrame.

Этот API присутствует во всех основных браузерах, он исключает возможность ненужного рисования и может объединять несколько анимаций в один цикл перекомпоновки и перерисовки, что помогает нам добиться анимации 60 кадров в секунду.

В нашем случае метод анимации автомобиля передается в качестве параметра window.requestAnimationFrame. Для каждой новой позиции автомобиля мы отменяем предыдущий запрос (используя cancelAnimationFrame), если есть один ожидающий. Таким образом, когда вкладка снова становится активной, выполняется последний запрос анимации, который находится между местоположением автомобиля (когда вкладка стала неактивной) и самым новым местоположением. Кроме того, поскольку наш алгоритм определяет, что расстояние, которое нужно пройти, велико (и нереально), он просто помещает автомобиль в центр карты без какой-либо анимации (без полетов!).

Пример кода ниже, демонстрирующий использование RequestAnimationFrame.

moveCab определяет, нужно ли выполнять анимацию или нет

Как уже упоминалось, для каждой новой позиции мы очищаем предыдущие интервалы и запрос кадра анимации,

Вы можете увидеть финальный опыт здесь.

Анимация тоже была очень плавной (57FPS).

Вот и все. Работа еще продолжается. Есть так много вещей, которые мы хотели бы попробовать. Например, изучение других компонентов Google Maps, оптимизация API опроса, новые алгоритмы анимации и т. Д. Будет поддерживать этот поток со всеми обновлениями !!!

Тем временем вы можете заказать такси Ola @ https://book.olacabs.com и проверить страницу после бронирования, чтобы увидеть приведенный выше код в действии.

Авторы

Ратул Рой. Главный инженер, Ола

Рави Патвал, инженер-программист II, Ола

Особая благодарность Ракешу Бехере и Санкалпу.