Пользователь
0,0
рейтинг
23 марта 2011 в 15:58

Разработка → Как подружить Yandex карты с Google и OSM? из песочницы



Есть у меня проект, который работает на основе карт от Яндекса. Выбрал я их потому, что там документация на русском, хорошее качество карт наших городов и приятный интерфейс. Позже, как оказалось, еще и большие возможности.
И вот я заметил, что некоторые места Яндекс спутник показывает в слишком маленьком масштабе, что не годиться для построения маршрутов.
Изначально, я сделал два отдельных файла со скриптами, в одном из которых – была логика работы с Яндексом, а во втором – с Google. Переключение происходило в профиле и действовало на весь сайт, а вся работа с картами на сайте была в псевдокоде(прокси).
Esosedi.ru использовали как раз такой подход с переключением карт, но без перезагрузки. Однако такой вариант мне не подходил потому, что используются маркеры и ломанные линии, к тому же вести два разных файла трудно.

И вот я решил поискать другой вариант.

Вариант 1. Загрузка тайлов


Себе приписывать чужого не буду. Этот вариант я нашел в клубе яндекс карт clubs.ya.ru/mapsapi/replies.xml?item_no=7125. Переделав всю логику под этот вариант и, добавив уже OSM, я было обрадовался, что все готово и уже думал о другом, но тут с ужасом увидел, что карты отображаются неправильно. Координаты совпадают, но где-то часть карт не подгружаются, появляются серые места, то сверху, то снизу. Проблема была в том, что не только широта с долготой перепутаны, а используются принципиально разные системы координат. При пересчете на гугловские координаты надо было сдвинуть тайлы по вертикали на определенную величину. Вот как раз эта величина и была серой областью.
В интернете писали что такой вариант не лечиться. Я пробовал увеличить карту по вертикали сверху и снизу, но скрыть ее. После я уже увидел что не только серые полоски появляются сверху, а прям на карте, линия квадратов может не подгружаться. Однако оставил я этот вариант не потому.
Google разрешает использовать свои тайлы только через API, а значит придется в любом случае создавать карту, но вот как отображать метки одной карты на другой?

Вариант 2. Копирование тайлов


План был таков.
Создается экземпляр карты Google где-то далеко-далеко, а точнее, либо с отрицательным индексом, либо с отрицательной величиной top или left. После, навешивается обработчик событий на карту Yandex, который при движении карты Yandex двигает карту Google. У карты Google стоит обработчик движение, при котором копируются divы с картами в div Яндекс карт с классом «YMaps-tile-container», в котором находятся тайлы Яндекса.
GEvent.addListener(gmap,"move", function (overlay, latlng) {
var point = gmap.getCenter();
$layer = $('#googlemap div img').parent().parent().parent();
$('.GMaps-tile-container').html($layer.html()).width($layer.width()).height($layer.height());
$('.GMaps-tile-container').css('left',$('.YMaps-tile-container').css('left'))
.css('top',$('.YMaps-tile-container').css('top'))
.css('width',$('.YMaps-tile-container').css('width'))
.css('height',$('.YMaps-tile-container').css('height'));
$('.YMaps-tile-container').hide();
}, this);
GEvent.addListener(gmap,"tilesloaded", function (overlay, latlng) {
var point = gmap.getCenter();
$layer = $('#googlemap div img').parent().parent().parent();
$('.GMaps-tile-container').html($layer.html()).width($layer.width()).height($layer.height());
$('.GMaps-tile-container').css('left',$('.YMaps-tile-container').css('left'))
.css('top',$('.YMaps-tile-container').css('top'))
.css('width',$('.YMaps-tile-container').css('width'))
.css('height',$('.YMaps-tile-container').css('height'));

}, this);

* This source code was highlighted with Source Code Highlighter.

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

Вариант 3. Двигать карту


Следующий вариант, который пришел на ум, это было просто двигать карту, которая находилась бы внутри другой карты.
Сама карта гугл создается в диве с классом «YMaps-tile-container». Див с тайлами Яндекса скрывается, а точнее не скрывается, а получает прозрачность 0, ведь именно этот див обрабатывает все движения карты.
Добавляем обработчик событий к карте Яндекса. При движении карты Yandex будет происходить синхронизация центра карты Яндекса с картой Google.
YMaps.Events.observe(map, map.Events.Move, function (oMap, offset) {
    gmap.setCenter(new GLatLng(map.getCenter().getLat(), map.getCenter().getLng()), map.getZoom());    
  });
YMaps.Events.observe(map, map.Events.BoundsChange, function () {
  gmap.setCenter(new GLatLng(map.getCenter().getLat(), map.getCenter().getLng()), map.getZoom());
  });


* This source code was highlighted with Source Code Highlighter.

Пробуем… И опять, при небольшом размере карты все работает хорошо, но при разворачивании начинаются заметное торможение и неприятные артефакты.
Возможно это связано с тем, что параллельно грузятся карты Яндекса, хоть их и не видно. Создадим пустой тип карты и установим его.
var none = new YMaps.TileDataSource('', false, false);
var tnone = function () {return new YMaps.Layer(none)};
YMaps.Layers.add("none#layer", tnone);
map.setType(new YMaps.MapType(["none#layer"],'',{minZoom:1, maxZoom:17}));


* This source code was highlighted with Source Code Highlighter.

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

Вариант 3.1. Использование moveBy


Вот на этой странице api.yandex.ru/maps/jsapi/examples/synhromaps.html находится пример, как синхронизировать две Яндекс карты между собой. Вместо центрирования там используется сдвиг на определенный интервал(видимо такой вариант снижает нагрузку). У карт Google есть точно такая же функция, но значения сдвига Яндекс карты не подходит. После нескольких опытов я оставил и эту затею.

Вариант 4. Двигать маркеры


Если вариант с движением тайлов не прокатил из-за большой нагрузки, то вариант с движением объектов должен быть лучше.
План схож с вариантом 3, за одним исключением: обработкой событий должен заниматься обработчик Google карт. Для этого надо скрыть слои Яндекса, отвечающий за обработку событий.
И вот тут нас ждет разочарование. По непонятным причинам Гугл карты в данном варианте обрабатывают только нажатие кнопки мыши, но не передвижение и отпускание. И сколько часов я потратил на поиск результатов не сосчитать.
После долгих экспериментов в попытках удалить стандартные обработчики Яндекс Карт, оказалось, что надо сделать чуть-чуть по-другому и все заработает.
Для начала надо перенести Гугла карты из дива «YMaps-tile-container» в самый корень, то есть в div, в котором создается сама Яндекс карта. Теперь карту покрывает серый фон. Установим значение background дива с классом «YMaps-layer-container» в transparent. Теперь мы видим и карту и объекты, но гугл не получает событий. Установим z-index гугл карт в 1, а у дива «YMaps-layer-container» поставим auto. Пробуем… Готово!

И волки сыты и овцы целы!

Все уместилось в две функции, код которых можно увидеть ниже.
Тут же можно подключить и OSM.
//Вызывается при смене карты.
function unbindGoogleMap()
{
  $('#googlemap').hide();
  $('.YMaps-map-type-layer-container').show();
  $('.YMaps-layer YMaps-common-object-layer').show();
  if (mapevents[0])
  mapevents[0].cleanup();
}
/Вызывается при выборе Google карты
function bindGoogleMap(type)
{
  var selector = '#'+$(map.getContainer()).attr('id');
  map.setType(new YMaps.MapType(["none#layer"],'',{minZoom:1, maxZoom:17}));
  map.redraw();
  if ($('#googlemap').length==0)
  {
    var $div = $('<div>').attr('id','googlemap').width($(selector).width()).height($(selector).height()).css({zIndex:1});
    $('#routemap').prepend($div);
  }
  $('#googlemap').show();
  $('.YMaps-layer-container').css({background:'transparent',zIndex:'auto'});
  $('.YMaps-map-type-layer-container').hide();
  $('.YMaps-layer YMaps-common-object-layer').hide();
  if (!gmap)
  {
    gmap = new GMap2(document.getElementById('googlemap'));
    gmap.enableContinuousZoom();
    gmap.enableScrollWheelZoom();
  }
  switch(type)
  {
    case 'gotsat': case 4:
    gmap.setMapType(G_SATELLITE_MAP);;
    break;
    case 'gothyb': case 5:
    gmap.setMapType(G_HYBRID_MAP);;
    break;
    case 'gotrel': case 6:
    gmap.setMapType(G_PHYSICAL_MAP);;
    break;
    default: gmap.setMapType(G_NORMAL_MAP);
  }  
  gmap.setCenter(new GLatLng(map.getCenter().getLat(), map.getCenter().getLng()), map.getZoom());
  //Двигают картой
  GEvent.addListener(gmap,"move", function (overlay, latlng) {
    map.setCenter(new YMaps.GeoPoint(gmap.getCenter().lng(), gmap.getCenter().lat()), gmap.getZoom());
  }, this);
  //Как-то изменяют область видимости
  GEvent.addListener(gmap,"update", function (overlay, latlng) {
    map.setCenter(new YMaps.GeoPoint(gmap.getCenter().lng(), gmap.getCenter().lat()), gmap.getZoom());
  }, this);  
  //Если нажимают на контроллер зума
  mapevents[0] = YMaps.Events.observe(map, map.Events.BoundsChange, function () {
    gmap.setCenter(new GLatLng(map.getCenter().getLat(), map.getCenter().getLng()), map.getZoom());
  });
}


* This source code was highlighted with Source Code Highlighter.



На этом все. Живой пример можно увидеть на сайте bikecamp.ru/map
Михайленко Олег @blare
карма
27,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • 0
    Клево! Будет очень полезно в своей разработке.
  • 0
    Читая название, мне вспоминается фраза одного хабра-человека: «Гуглить Яндексом».
  • +4
    Может я неправильно понял, но вы делали что-то типа аналога mmaps.net?
    • 0
      промахнулся с ответом
    • 0
      промахнулся с ответом
      • +1
        Тогда вам штрафной круг ещё ехать. ;)
  • 0
    Нет, этот сервис скорее что-то около gpsies.com и marshruty.ru, но направленные на велосипедистов. А такое разнообразие нужно было для улучшение качества сервиса.
  • 0
    Спасибо за статью. Как-то копал в этом направлении, но объединить всё в одном не получалось. (
  • 0
    Очень здорово!
    А подскажите еще как у вас реализована кнопка — развернуть на весь экран. Вроде как с этим есть некоторые проблемы у Яндекс карт.
    • +1
      У Яндекса есть даже пример api.yandex.ru/maps/jsapi/doc/dg/concepts/map-controls-aux.xml#flag-button

      На самом деле нет никаких проблем. Разворачиваете карту на весь экран(присваиваете ширину и высоту по размеру окна), устанавливаете свойство display в fixed и вызываете перерисовку карты.

      //для Google
      gmap.checkResize();
      //для Yandex
      map.redraw();
      • 0
        Спасибо!
  • +5
    Давно занимался подобным вопросом для mapcam.info.
    Если нужно использовать много карт, проще взять Google API и адаптировать под него проекцию Яндекс, чем адаптировать все карты под Яндексовскую проекцию. Что получилось можно посмотреть здесь. Сейчас используются только OSM, Google, Яндекс и Bing, остальные отключил т.к. в основном люди пользуются этой четверкой.

    • 0
      молодчина здорово ведь вышло.
    • 0
      В данном случае я хотел оставить интерфейс Yandex карт. Он мне как-то больше понравился.
    • 0
      Вроде как Yandex тоже не разрешает использовать одни тайлы без логотипов и копирайтов.
    • 0
      Очень интересно как вы перевели координаты яндекса в координаты остальных.
      Там сдвиг по вертикали — как он зависит от широты?
  • +1
    заголовок сайта не однозначный :):

    Это новый сервис для велосипедистов!

    кто что подумал?
    • 0
      мастерская?)
      Как-то об этом писали в темах, где я давал ссылку на свой сервис проект, но я не понял о чем они.
      Спасибо, теперь поменяю название=)
  • 0
    Пытливый ум — это очень полезное качество. И материал хороший получился.
  • +1
    Быть может я туплю, но все эти карты можно слоями в OpenLayers добавить и точно так же переключать слои, двигать маркеры…
    • 0
      Яндекс запрещает использовать свои карты вне родного АПИ
  • 0
    «Велосыпыдысты» уехало в хроме 10 под линуксом на другую строку
    • 0
      Спасибо, поправлю
  • +2
    *рвет волосы*
    На esosedi.ru берутся карты google.v2 после чего прототип GOverlay расширяется так что он может уже работать в яндекс картах( + два метода)
    То есть можно создать гугловый маркер и добавить на яндекс карты. Одна из фичей Яндекс карт что они работают не с классами, а с интерфейсами — допишите в базовый класс гугловых оверлеев пару функций, и яндекс сможет сработать на этом интерфейсе.
    На gdeetotdom.ru(он кстати умеет показывать гугловые тайлы на яндекс картах) и tili-testo.ru(последняя версия) используется другой подход — у вас есть доступ к базовому MapWorker который имеет ссылку на активный прокси класс к конечному АПИ. Все объекты и маркеры — собственные, и работают именно с MapWorker и он умеет переинициализировать их заново при смене АПИ.
    Файл каждого из прокси — относительно мал(~600 строк и половина там на самом деле мусор(конкретно маркеры и полигоны))

    Ну и самый простой вариант — взять обе функции меркатора обоих систем и либо отобразить данные на них, либо компенсировать дельту сдвига по Y(так например и делает ged)
    Но самое главное — что и у автора все работает :P
    • 0
      ммм… на gdeetotdom.ru там вместе с интерфейсом карты переключаются
      а в tili-testo.ru я вообще не смог переключиться=(

      В любом случае, это был бы отличный материал для еще одной статьи на эту тему)
  • 0
    Use <source lang="javascript" />, Luke!
  • 0
    Нормальный такой изврат :)
  • 0
    периодически пользуюсь mmaps.net, очень помогает.
    Но использовать на своем сервисе такую идею очень здорово.
    Супер.
  • 0
    Любопытно… идея подобного сервиса для публикации маршрутов высказывалась на нашем местном велофоруме.
  • +1
    Уважаемый, у вас там данные передаются в двойном объёме ;)

    {«routes»:[{
    «0»: «26»,
    «id»: «26»,
    «1»:" \u0412\u043e\u043a\u0440\u0443\u0433 \u041b\u043e\u0441\u0438\u043d\u043e\u0433\u043e \u041e\u0441\u0442\u0440\u043e\u0432\u0430",
    «name»:" \u0412\u043e\u043a\u0440\u0443\u0433 \u041b\u043e\u0441\u0438\u043d\u043e\u0433\u043e \u041e\u0441\u0442\u0440\u043e\u0432\u0430",
    «2»: «55.842583»,
    «lat»: «55.842583»,
    «3»: «37.726536»,
    «lon»: «37.726536»
    }],«objects»:[],«lat»:«571»,«lon»:«386»}
    • 0
      Спасибо) я уже это видел, в следующем обновлении будет пофиксино)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.