Pull to refresh

Старые карты по новому

Reading time 9 min
Views 4K

Порядка 4х месяцев назад мы решили немного обновить карты. Это, вроде бы как, один из разделов на который мы сильно обращаем внимание пользователей.
Это случилось 4 месяца назад, но только сегодня мы, Евгений johnny_palec Емельянов и Антон kashey Корзунов хотели бы рассказать историю создания сервиса «На карте» портала gdeetotdom.ru. В чем-то мы были первопроходцами, в чем-то догоняющими, но в любом случае граблей было собрано немало, а в конечном итоге получился уникальный в своем роде сервис (не слишком заметно, что мы им гордимся?).
Своими изысканиями, ошибками и находками мы и хотели бы поделиться с сообществом хабра-веб-разработчиков. Но, обо всем по порядку.


Мы наш, мы новый мир построим



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

Интерфесы и юзабилити



Решили объединить богатства на карте в виде слоев и создать приемника нашего тогдашнего сервиса «Где Этот Дом», в котором на карте отображались фотографии домов, инфраструктура и сферические панорамы. Кроме того, нужно было добавить и то, в контексте чего и должен выступать визуальный контент — объявления по недвижимости из нашей базы. Интерфейсные изыскания проще проиллюстрировать макетами двух прототипов — классический подход с балунами и вариант, когда информация по объекту показывается в отдельном блоке. Остановились в итоге на классическом подходе, правда пришлось решать проблему с размещением большого количества информации в небольшом балуне — как решили, можно посмотреть в рабочем варианте.
Если, прочитав это, вы (в очередной раз) подумали что к нам стоит заглянуть только в целях покупки или продажи своего места обитания, можем вас разочаровать — новый сервис на карте сможет помочь вам как найти новую кафешку рядом с домом, так и спланировать (и провести) очередной слет байкеров (Ночные Волки).

Google + Яндекс = взаимовыгодное сотрудничество



На самом деле взаимовыгодная эксплуатация :) Объекты нужно показывать по всей России, а покрытие и качество карт у гугла и яндекса разное, поэтому мы решили использовать оба API, показывая по умолчанию те карты, которые лучше подходят для данного региона с возможностью ручной смены. Так же если по тем или иным причинам API выбраной карты по умолчанию у пользователя не загрузится — автоматически грузится второй вариант.
Правда, мы так до конца и не понимаем почему это требуется, но факт остается фактом — у одной части людей заблокирован google.com, а у другой почему-то странно работает яндекс.
Переключение на рабочий вариант карт происходит автоматически, следуя из базисных принципов нашего АПИ, о котором мы раскажем ниже.

Разведение картографических слоев в средней полосе России



Изначальная задача стояла просто — карты должны работать, и карты должны работать хорошо. И желательно везде.
Разработка началась в середине ноября, через пару дней после Google Day.
Поэтому наши карты:
1. Google maps v3
2. Яндекс карты самых последних версий
3. Самые хорошие и почти что самые умные.
Первичная версия карт была готова через две недели.
Мы на самом деле вложили в них все то, что знали раньше и все то, что придумали по ходу, и давайте забудем что мы вообще-то серьезный сайт о недвижимости и поболтаем исключительно о технической стороне вопроса.

Пять шагов куда-то



Поставим перед собой цель — создать инструмент на основе карты и попытаемся его реализовать.
Шаг 1 — для начала карту надо запустить
Шаг 2 — потом надо как-то задать ее начальные параметры
Шаг 3 — наверное надо запросить некоторые данные с сервера
Шаг 4 — как-то их отобразить
Шаг 5 — и что-то с ними наконец сделать

а теперь, немного поподробнее

Также следует заранее указать, что 90% вызовов функций, межмодульных сообщений и тому подобное у нас запускаются через setTimeout. Спасает от ерроров и подвисонов. Если нужны подробности — спрашивайте.
ПС: также, из-за обертывания вызовов в такие вот таймауты (в нашем понимании _enterState\_leaveState\_callState) мы всегда знаем что и где запускалось, сколько времени заняло, и можем отловить ошибки (но не можем отловить callStack), мы также ловим ерроры на window.onerror и постоянно собираем и анализируем эти еррлоги. Это ВАМ сильно помогает работать на нашем сайте без ошибок


Шаг 1 — Загрузка АПИ


Для начала поставлю вас перед фактом — напрямую с АПИ общаться нельзя. Вообще. Никогда.
Да и не удобно это, если честно.
Итак давайте создадим некий объект, с которым будем общаться.
Назовем его SuperMaps.
Определим у него, для начала, три метода — SetWorker, StartWorker, KillWorker( пропустим тот момент что называть вообще-то надо было SetMapProvider или другими умными словами, отображающими назначение компонента. У него на самом деле одна задача — РАБОТАТЬ!)
Первый устанавливает вариант используемой нами карты, второй ее стартует, ну а третий, так уж получилось, убивает.
Правда, следует уточнить, под «вариантом карты» тут понимается опять же обертка конечного АПИ, а не прямое использование (не пугайтесь, лично у нас такие обертки занимают не более 200 строк)
Для успешного запуска используется такая последовательность действий:
1. старт таймера запуска
2. подключение АПИ (defered), в случае гугл карт тут у нас два шага — в начале пробуем загрузить апи с google.ru, если это не получается — с google.com. И это не прихоть( еррор логи все видят ).
3. проверка подключения АПИ
4. запуск АПИ
5. отчет о запуске АПИ
5+. запуск остальных систем
(6) где-то тут срабатывает таймер 1, если карты нет — нам прийдется попробовать другой АПИ. Как ни странно — такое происходит довольно-таки часто — фаерволы, плагины и хреновые каналы творят чудеса.

Шаг 2 — Ткнуть носом в нужную точку на карте


На этот этапе требуется открыть карту в некой точке. Тут ничего особо выдающегося нету — параметры текущего viewport'а сохраняются в url, откуда потом и берутся. Если их нет — открывается «featured» адрес, либо, если вы все же уже посещали сайт — данные вашего последнего сеанса, из кук, для каждого домена-региона нашего сайта отдельно. Вот такая фича. Очень удобно :)

Шаг 3 — Получить данные с сервера


Относительно недавно я уже рассказывал про секреты загрузки данных в виде кучи тайлов, правда в реализации гдеэтогодома тайлы грузятся не кучей — за один запрос с сервера передается до 6-ти кусков, в одном запросе. Если точнее — система пытается всегда грузить данные в 4 потока, но не более N тайлов в одной пачке.
Адресация тайла в данном случае выглядит как /maptiles/7-l032/103/031/311/011;21~2;20~21;19~100; самые глазастые могут заметить что тут запрашивается 4 тайла седьмого слоя, у которых просто немного отличается хвост. Для клиента такая оптимизация равнозначна уменьшению времени отлика, и экономии трафика, так как чем больше жирнеет файл — тем сильнее звереет gzip. Ну и конечно не следует забывать что «клиентское кеширование» наших данных смысла не имеет — 90% данных меняются за день. А серверу собрать в пачку уже готовые файлы — не сложно — он железный.
С этим алгоритмом, правда, связан один смешной случай — изначально для лучшей оптимизации мы брали массив адресов, проводили natsort и разбивали на красивые урлы.
Как ни странно, этот вариант, который великолепно работал во всех браузерах, намертво вешал любой ИЕ, если в первом массиве было более 40 блоков (полный экран для одного слоя создает запросы к 6-8 блокам данных, всего клиент может активировать под 50 слоев данных).
В данный момент используем обычный sort, который выдает не самый идеальный вариант, и иногда url становится длинноват.
В нашем случае данная подсистема загрузки, хранения и, одновременно, управления отображением является центральной и называется ZTile.

Шаг 4 — как-то их отобразить, эти данные


С отображением у нас связаны, наверное, одни из главных архитектурных заморочек.
Итак, с сервера пришел некий набор объектов, каждый объект имеет свой базовый тип (обьявление, ПОИ, фотография и тд) и свой слой (по сути, порядковый номер предзаданого where-выражения выборки из базы).
Как на основе этих данных отобразить информацию.
У нас получился конвеер примерно такого плана:
zTile-Сервер-данные-zTile-SuperMaps-фабрика типов-тип-фабрика слоев-*сортировка, кластеринг*-SuperMaps-APIworker-API-view-тип-render
Цепочка длинновата, и давайте я ее поясню.
zTile запрашивает с сервера данные, после чего их получает.
Далее он запрашивает главный компонент — SuperMaps — на создание модели для объекта.
SuperMaps просматривает заявленные обработчики, и если один из них соглашается принять обьект, делегирует ему создание объекта такого типа.
В полученный объект «заливается» индетификатор слоя, в котором был загружен объект, после чего, после пережовывания фабрикой слоев — объект может поменять свой тип (точнее уточнить свое поведение).
После этого объект в принципе знает что он такое и как бы он хотел отображаться.
*сортировка* будет описана в шаге 5, но, по сути, она просто скажет объектам что пока выйти из тени, и оставить о себе запись в DOM дереве.
При отображении объекта он отправляет себя на рендер опять же в SuperMaps, который спускает вызов в текущий worker, который создает view макера (контрола, балуна), после чего этот view атачится к модели отображаемого объекта.
После чего, как назло, он опять может поменять свое представление, чтобы подстроиться под нужды АПИ.
Это не происходит на Яндекс или Гугл картах, но на тех же Ovi — где просто нельзя нарисовать свой html в маркер — система сработает именно так.
Одним из бонусов данного подхода является то, что конечный АПИ карт является по сути конечным в любом смысле.
Мы можем его поменять и отобразить тот же set объектов, в том же виде, но уже на другой карте, просто сменив карту и вызвав refresh кластеринга.

Шаг 5 — нормально отобразить данные, и другие плюшки


Один из самых сложных вопросов при отображении набора данных, особенно большого набора данных, и, особенно, динамически меняющегося набора данных — это кучи маркеров, наползающие друг на друга.
Наш вариант кластеризации (ИМХО) самый лучший из известных мне, и на голову превосходит тот же googlemarkermanager(v2\v3) как по функционалу, так и по скорости.
А работает очень просто.
По тем же принципам что и загрузка данных в шаге 3 мы разбиваем карту на четкие ячейки, и атачим к этим ячейкам объекты, которые в них попадают.
После чего в пределах сета объектов одной ячейки определяется взаимное наложение и происходит банальное объединение.
Только вот один момент есть положительный — без изменения набора данные перестраивать группу не требуется. Данные о наложении маркеров для на друга в одной ячейке совершенно статические и изменить этот набор может только изменение ячейки.
То есть если вы сместите карту и увидите немного новых объектов — только новые группы-ячейки и будут перестраиваться. Старые, еще раз повторю, статичны.
Полная перестройка происходит только при зуме и включении\выключении слоев, если хоть один новый объект появился или исчез из сета объектов кластерой группы.
Правда, тут правильно заметили в соседнем топике, что в результате такого вот «квадратно-гнездового» способа кластеризации могут появиться различные артефакты — ведь группы работают автономно, и им ничего не мешает оставить вроде бы сводобные маркеры на границах.

Решается просто: создаем еще один clusterManager с чуть другим размером кластера (на один пиксель больше — уже достаточно) и сеткой, сдвинутой на половину своего размера.
В него попадает результат группировки изначальных маркеров и этот результат — опять группируется.
Он и схлопнет перекрывающие балуны в один. И опять же не потребует полной перестройки при операциях с картой.
Правда стоит признать, что с тех пор когда я написал эти строки прошло уже почти полгода, а лично мы, так и не воспользовались своим же советом. не картами едиными....


В результате система совершенно внятно работает на десятках тысяч маркеров, и, я думаю, если еще немного поколдовать, можно и еще раза в полтора ускорить.
Но это не требуется, так как мы уже упираемся в два момента — 1. Скорость обновления DOM дерева
2. Количество маркеров на карте.
Первое мы решали долго, смотрели на различные DOM fragments и читали километровые доки, но решили в итоге просто — все появления и пропадания — есть групповая операция, мы просто оборачиваем ее в транзакцию (display:none и еще немного, аттач\детач элементов, еще немного и опять показываем карту), что сокращает reflow & repaint в разы. Очень хотелось бы узнать что про это дело (вроде бы) рассказывали на DevConf (но где не был, там не был).
Следует сразу уточнить что, так как эта операция выполняется непрерывным js кодом — никакого «мигания» карты вы не увидите.

Второе, если честно, мы так и не решили.
У нас если лимит обьектов для отображения, который может менятся, в зависимости от скорости железного друга, у нас есть радиус схлопывания маркеров который позволяет феноминально чистить карту от перегрузки маркерами.
Но сли на выходе у нас всеравно получается перегруз колличества маркеров, и мы не можем гарантировать нормальную работу (ИЕ6) с таким колличеством объектов — их надо уменьшить. Меняем один фактор и делаем себе refresh. В результате может получиться слишком мало объектов — еще раз меняем один из факторов и refresh.
Как результат — алгоритм иногда сходится до 8ти «тиков». Правда, пользователю это практически не заметно.

Конец ли это?


Данному повествованию наверное да, конец. Нет смысла увеличивать размеры топика. Самые интересные моменты чуть более подробно мы опишем чуть позже отдельными, более развернутыми материалами.
Различные тонкости, такие как установка нужных колбеков в нужных местах, ресайзы карты ползунком, «внешние балуны» (наверное, все-таки посмотрите на нашу реализацию такой простой вещи как балун маркера), отладка и логирование…
Давайте лучше мы просто ответим на ваши вопросы, а там уж посмотрим.

Что дальше?



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

итак что интересного и зачем надо было или нужно будет прочитать километр текста выше и посмотреть на самом сайте:
1.два АПИ( на самом деле 4(+ovi,+bing) без проблем переключения
2.загрузка данных с сервера, reloaded
3.внешние балуны на карте — но их лучше смотреть(открываем балун и двигаем карту)
4.кластеринг маркеров и данных в балуне( www.gdeetotdom.ru/map/#lat=55.72867&lng=37.81804&m=google&z=15&l=7-14, ровно в низу и посередине карты)
5.отображение гугл тайлов на яндекс карте( треки дорог «россии 360» )
Tags:
Hubs:
+5
Comments 9
Comments Comments 9

Articles