Клуб API Карт

Склейка двух слоев на стороне клиента

Пост в архиве.

Привет коллеги!

Предыстория: Есть некий сервис на котором выкладываются карты/снимки. Поскольку покрытие не сопоставимое с данными Яндекс.Карт, то, в качестве подложки под свои снимки, хочется видеть карту/снимок из Яндекс.Карт. Если просто создать прозрачный слой и положить его сверу слоя Яндекса, то в тех местах, где есть данные и свои и Яндекса получается очень неприятный эффект, когда в начале грузится слой Яндекса, а потом проявляются данные с сервиса, а учитывая что привязка может отличаться, так еще и смещение на глазах у пользователя происходит.

 

А теперь вопрос: Как сделать такой источник данных для слоя, чтобы он получил данные из двух истоников, склеил их, а уж потом отдал в API?

13 комментариев

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

1. Берем нужный тип карты и получаем его экземпляр

var mapType = ymaps.mapType.storage.get('yandex#map');

2. Вытаскиваем из него слой

var yandexLayerConstructor = ymaps.layer.storage.get(mapType.getLayers()[0]);

3. Создаем на его основе слой

var newLayer = new yandexLayerConstructor();

4. Переопределяем функцию getTileUrl

newLayer.getTileUrl = function (tileNumber, zoom) {

   // Если зум и номер тайла подходят для показа вашей карты

    if (tileNumber[0] == ....) {

       return 'http://...';

    } else {

      // Иначе берем тайл из стандартного слоя

      return yandexLayerConstructor.prototype.getTileUrl.call(this, tileNumber, zoom);

    }

}

 

Часть наших тайлов имеют прозрачность, их надо накладывать на слой Яндекса. Да и наличие нашего тайла можно определить только запрос на сервер сделав, их тоже немало. Так что такой вариант не подходит, уже пытались. Нужно именно получить с двух источников и склеить, причем до показа обох слоев.

С помощью апи такую штуку сделать не получится

А для чего тогда в API штуки типа ICanvasTile?
И как, к примеру, осуществляется "растягивание" тайлов спутника в браузере при редактировании зданий на народной карте?

Подозреваю, что такая возможность есть, может недокументированная, но должна быть. Я, просто, не очень на "ты" с веб-программированием, но о Яндекс.Картах кое-что знаю. ;)

Растягиваение тайлов - это просто показ маленькой картинки в большом размере, тут никакой особой магии нет.

 

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

 

Для реализации такой логики вам придется создать свой экземпляр IDomTile или ICanvasTile (а для кроссбраузерности видимо и тот и тот). Эти тайлы будут получать url картинки, дальше вы будете отправлять запросы за двумя картинками и в зависимости от того, когда и как они подгрузились, генерировать финальную картинку.

Это возможно, просто трудоемко.

Да, именно такой сценарий я и рассматривал. Только я не понимаю в каком месте создавать свои экземпляры IDomTile/ICanvasTile и какие методы им перекрывать.

Посмотрела по доке и коду - эти возможности незадокументированные, то есть использовать вы это все можете на свой страх и риск.

У слоя есть контейнер тайлов, а у него уже, в свою очередь тайлы.

Задать контейнер тайлов в слой можно через опцию tileContainerClass.

Чтобы контейнер задать, нужно взять класс стандартной реализации и его доопределить.

Классы сами тут http://api.yandex.ru/maps/doc/jsapi/2.x/ref/reference/layer.tileContainer.CanvasContainer.xml

 

В них есть опция tileClass, через которую можно задать конструктор

Вот, это уже полезная информация. Спасибо!

tileContainerClass как раз и есть та самая недокументированная возможность?

А каким образом тайлы в api скачиваются? Можно ли задействовать теже методы или самим качать?

Да, tileContainerClass и есть оно.

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

А можно попросить вас набросать фрагмент кода. Тяжело без документации и с моими познаниями в JavaScript :(

А я, когда к вам поеду, конфеты или тортик в благодарность захвачу ;)

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

Сапсибо, буду разбираться. Про debug хорошая подсказка. Исходников должно хватить.

С горем пополам, сделал более-менее рабочую версию (пока только для Dom тайлов). Может кому пригодиться или кто покритикует.


    DoubleTileDomContainer = ymaps.layer.tileContainer.DomContainer;
    DoubleTileDomContainer.prototype.defaultGetTile = DoubleTileDomContainer.prototype.getTile;

    DoubleTileDomContainer.prototype.getTile = function (tileNumber, tileZoom, priority) {
        var urls = this._layer._tileUrlTemplate.split("|");
        if (urls.length == 2) {
            var tileUrlTemplate = this._layer._tileUrlTemplate;
            var tileBg;
            var tileMain;
            try {
                this._layer._tileUrlTemplate = urls[0];
                tileBg = this.defaultGetTile(tileNumber, tileZoom, priority);
                tileBg.options.set("tileTransparent", false);

                this._layer._tileUrlTemplate = urls[1];
                tileMain = this.defaultGetTile(tileNumber, tileZoom, priority + 10);
                tileMain.options.set("tileTransparent", true);
            } finally {
                this._layer._tileUrlTemplate = tileUrlTemplate;
            }

            tileMain.bgTile = undefined;
            tileMain.defaultDraw = tileMain._draw;
            tileMain._draw = function () {
                this.defaultDraw();
                if (this.bgTile) {
                    this.bgTile.defaultDraw();
                }
            }

            tileBg.mainTile = tileMain;

            tileBg.defaultRenderAt = tileBg.renderAt;
            tileBg.renderAt = function (context, clientBounds, animate) {
                this.defaultRenderAt(context, clientBounds, animate);
                this.mainTile.renderAt(context, clientBounds, animate);
            }

            tileBg.defaultDestroy = tileBg.destroy;
            tileBg.destroy = function () {
                this.defaultDestroy();
                this.mainTile.destroy();
            };

            tileBg.defaultDraw = tileBg._draw;
            tileBg._draw = function () {
                if (this.mainTile._rendered && this.mainTile._loaded || this.mainTile._error) {
                    this.defaultDraw();
                    this.mainTile.bgTile = undefined;
                } else {
                    this.mainTile.bgTile = this;
                }
            }
            return tileBg;
        } else {
            return this.defaultGetTile(tileNumber, tileZoom, priority);
        }
    }


Слой задается так:

myLayer = new ymaps.Layer("http://sat0%d.maps.yandex.net/tiles?l=sat&%c" + "|" + кастомный_слой,
{tileContainerClass: DoubleTileDomContainer});