Печать Яндекс.Карты под API 2.x с метками и кластерами

    Всем известно, что напечать ядрекс-карту API 2.х с метками и кластерами просто так не получится. До сих пор карта строится не на канвасе, а на дивах с подложкой (background-image). Да и канвас любимому браузеру не поможет.
    Была поставлена задача — быстро сделать версию для печати карты. Количество меток — более 600 + кластеры «из коробки».
    Работающий вариант под катом

    инициализируем карту как обычно — зум и центр берем из ссылки
    ymaps.ready(function () {
            var mapTypes = ['yandex#map', 'yandex#satellite', 'yandex#hybrid', 'yandex#publicMap', 'yandex#publicMapHybrid'],
                map = new ymaps.Map($('#map')[0], {
                center:[ parseFloat(<?=  $request_lat?>),
                    parseFloat(<?= $request_lng?>)],
                zoom:parseInt(<?= $request_zoom?>),
                type: mapTypes[<?= $request_mtype ? $request_mtype : 0?>]
            });
    

    делаем принтер-френдли лейаут для метки
    тут надо сказать, что принтер-френдли — это значит, что не используем background-image, а делаем кучу дивов с имиджами.
    у меня иконка метки состоит из 2 частей — картинки и подложки. и то и другое — в спрайте. поэтому такой бешеный style. его я привожу inline для наглядности
            ymaps.layout.storage.add('voina#icon', ymaps.templateLayoutFactory.createClass(
                '<div style="position: absolute; width: 28px; height: 36px; overflow: hidden;z-index: 0; ">' +
                    '<div style="position:absolute;width:20px;height:20px;overflow:hidden;top:4px;left:4px">' +
                        '<img src="/img/new_buttons_21.png" style="position:absolute;left:$[properties.iconOffset]px;"></div>' +
                    '<img src="/img/buttons7.gif" style="position: absolute; left: -264px; top: -70px; "></div>'
                ));
    
    

    то же самое — с иконкой кластера. заморачиваться с разными размерами иконок кластера под разное количество точек внутри я не стал — быстро надо было)
            ymaps.layout.storage.add('voina#cluster', ymaps.templateLayoutFactory.createClass(
                '<div style="position: absolute; margin: -26px 0 0 -26px; width: 58px; height: 58px; overflow: hidden;z-index: 0; ">' +
                    '<div style="z-index:800;position: absolute; width: 58px; height: 58px; text-align: center; font-size: 13px; line-height: 58px;">$[properties.geoObjects.length]</div>' +
                    '<img src="/img/cluster_big.png" style="position: absolute;"></div>'));
    
    

    здесь получаем контейнер лейера самОй подложки карты
            var $container = map.panes.get('layers').getElement(),
    

    так как типы карты у статики другие, делаем соответствия и получаем центр карты
                stMapTypes = {'yandex#map' : 'map', 'yandex#satellite' : 'sat', 'yandex#hybrid' : 'sat,ski', 'yandex#publicMap' : 'pmap'},
                center = map.getCenter(),
    
    

    size — это засада. статик отдает максимум 650 на 450. тоже заморачиваться не стал — пускай будет максимум
    div с картой тоже надо делать не больше этих размеров.
    по-хорошему, если делать див с картой большого размера, нужен другой подход — через layer.
              size = [650, 450],
    

    формируем линку для статики
                mapUrl = 'http://static-maps.yandex.ru/1.x/?ll='+center[1]+','+center[0]+
                    '&z='+map.getZoom()+'&l='+stMapTypes[map.getType()]+
                    '&size='+size[0]+','+size[1];
    

    тут начинается магия
    делаем див с абсолютным позиционированием и располагаем его посередине, так как привязка pane будет к центру вьюпорта. на самом деле, надо было вызывать map.panes.get('layers').getViewport() и считать центр. но при загрузке pane лежит посередине, а карту мы двигать не дадим
            $('<div></div>').css({
                    position: 'absolute',
                    left: -Math.round(size[0] / 2)+'px',
                    top: -Math.round(size[1] / 2)+'px',
                    zIndex: 800}).
    

    вставляем внутрь img со статикой
                wrapInner($('<img>').attr({'src':mapUrl, width: size[0], height: size[1], border: '0'})).
    

    и вставляем в контейнер лейера над остальными элементами
                prependTo($container);
    

    хорошая функция — отключает все события карты. теперь ни сдвинуть, ни позумить
           map.events.removeAll();
    

    тут я добавляю на карту свои маркеры. они у меня лежат в переменной window.data
            var len = window.data.length;
            if (len)
            {
                for (var i = 0, markers = [ ], properties, latLng; i < len; i++) {
                    latLng = [parseFloat(window.data[i][1]), parseFloat(window.data[i][2])];
                    markers.push( new ymaps.Placemark(latLng,
                        {   iconOffset: -window.data[i][5] * 20 - 1 },
                        {
                            iconLayout:'voina#icon',
                            iconOffset: [1, 2],
                            openBalloonOnClick: false
                        }));
                }
    

    теперь кластерер. что такое margin — я не помню, но было на нормальной карте
           var clusterer = new ymaps.Clusterer({margin: [20]});
    

    для иконок кластеров устанавливаем макет
              clusterer.options.set('clusterIconLayout', 'voina#cluster');
    

    не хотелось писать эту функцию, но по-другому заставить кластеры не раскрываться не смог
              clusterer.createCluster = function (center, geoObjects) {
                    var cluster = ymaps.Clusterer.prototype.createCluster.call(this, center, geoObjects);
    // вот сюда я вписал все возможные отменялки поднятия события
                    cluster.events.add('click', function(e) {
                        e.stopImmediatePropagation();
                        e.preventDefault();
                        return false;
                    });
                    return cluster;
                };
    

    добавляем маркеры в кластерер
        clusterer.add(markers);
    

    на всякий случай кластерер тоже заглушим
                clusterer.events.removeAll();
    

    добавляем кластерер на карту
                map.geoObjects.add(clusterer);
            }
        });
    


    тада!!! все работает и печатается (если браузер выдержит такое количества dom-объектов)
    один лишь баг — два копирайта. при удалении с карты или даже скрытия сыпятся ошибки. для и ладно.



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

    Подробнее
    Реклама
    Комментарии 13
    • 0
      Странно у меня на демо открылась пустая область вместо карты.
      Браузер Google Chrome 21.0.1180.79 m.
      • +1
        действительно странно. наверное, все-таки DOM не выдержал и таки рухнул))
        • 0
          Не расстраивайся. Сделаешь еще. Хорошо, когда люди стараются исправить косяки разработчиков.))) А твоя идея очень оригинальная. Если получится оптимизировать идею, то обязательно напиши в ЛС. Я тоже пытался сделать, но у меня это кончалось развалом карты на куски.
          Кстати на лисе, опере и осле 10 полет нормальный, только хроме капризничает.
          • +1
            У меня в хроме работает под винду и макось. И еще под сафарями. И под ios ;-)
      • 0
        А чем Static API не угодил? Отдает картинку с метками, отлично печатается.
        • 0
          Статик не отдает кастомные плейсмарки, а главное, кластеры
        • +2
          А копирайты у меня неплохо скрылись с помощью CSS. Никаких ошибок.
          • 0
            Напомню, что это нарушение лицензионного соглашения.
            • +2
              Там 2 копирайта получается — один от api, другой от статика. Думаю, нарушением не будет один из них убрать
              • 0
                Если один останется, то нарушения не будет.
          • 0
            Спасибо за информацию, попробую на своём сайте что-то подобное сделать. Правда что-то слишком сложный способ получился, но конечно лучше чем совсем ничего.

            А каким образом реализована печать в самих Яндекс.картах? Там если печатать обычную страницу то также маркеры пустые получаются, а если нажать кнопку «Печать» то всё выводится на печать хорошо.

            Только мне не удалось выяснить каким образом у них так получилось сделать, но навряд ли такую же кучу jquery они используют для этого.
            • 0
              Протёр глаза и посмотрел повнимательней, всё же там используется что-то типа Static API в версии для печати т.к. всё выводится сведенной картинкой. А в Static API можно как-то указать не конкретные координаты метки, а сделать поиск как в обычных картах?
              Что-то типа
              var myGeocoder = ymaps.geocode(myAddress);
              • 0
                нет. можно сначала сделать геокодирование, а потом запрочить картинку с статике.

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