Клуб API Карт

Как увеличить Zoom карты более 19 (Решено)

kashoutin.s
21 октября 2017, 10:11

Приветствую!

Вопрос знатокам.

Документация на api карт говорит, что максимально доступный зум = 23, но при этом тайлы имеются только для масштаба 19 и увеличить далее не имеется возможности.

Проблема заключается в том, что на карту добавляется дополнительный слой wms, у которого детальное отображение информации начиается на 20-21 зуме, но сама карта блокирует увеличение дальше.

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

Решено.

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

ymaps.ready(function () {

    /*  получаем из хранилища встроенный тип слоя */
    let YandexMapLayer = ymaps.layer.storage.get('yandex#map');
    
    /*  Дочерний класс слоя с перекрытием метода getZoomRange */
    let CustomMapLayer = function () {
        /* Calling the parent's constructor */
        CustomMapLayer.superclass.constructor.call(this);
        
        /*  в служебных полях объявляем максимально доступный зум и интересующую нас степерь масштабирования слоя*/
        this._maxAvaliableZoom = 19; 
        this._zoomRange = [0, 23]; 
    }
    
    /*  выполняем наследование */
    ymaps.util.augment(CustomMapLayer, YandexMapLayer, {
        /* Переопределяем в наследнике метод getZoomRange */
        getZoomRange: function (point) {
            let _this = this;
            /* Вызываем асинхронный метод родительского класса. */
            CustomMapLayer.superclass.getZoomRange.call(this, point).then(function(zoomRange) {
                /* 
                 * на случай, если в какой-то местности доступный уровень зума отличается от значения 19 
                 * - делаем обновление локального поля (для дайльнейшего расчета степени искусственного увеличения тайлов) 
                 * но на практике - тайлы с уровнем зума 19 есть везде (Москва и любая степь...), более 19 - тайлов нет
                 */
                if (zoomRange[1] != _this._maxAvaliableZoom) {
                    _this._maxAvaliableZoom = zoomRange[1];
                }
            });
            return ymaps.vow.resolve(_this._zoomRange);
        }
    });
  
    let YCustomMapLayer = function () {

        let layer = new CustomMapLayer();
        let defaultTileUrlTemplate = layer.getTileUrlTemplate();
        
        /* перекрытие размера тайла */
        layer.getTileSize = function(zoom) {
            
            let pixelRatio = ymaps.util.hd.getPixelRatio();
            if(zoom > layer._maxAvaliableZoom) {
                /* считаем требуемую степень масштабирования тайла */
                let scale = 2**(zoom - layer._maxAvaliableZoom);
                let tailImageScale = scale * pixelRatio;
                
                /*  подменяем урл получения тайла - при запросе тайла требуемого масштаба - сохраняем качество отобржения карты */
                /*  фиксируем зум на уровне максимально доступного */
                /*  дополнительно делаем проверку на запрашиваемый масштаб тайла, т.к. максимально доступный масштаб изображения - 16 */
                let zoomOverTileUrlTemplate = defaultTileUrlTemplate
                    .replace("%c", "x=%x&y=%y&z="+layer._maxAvaliableZoom)
                    .replace("{{ scale }}", tailImageScale > 16 ? 16: tailImageScale);
                    
                console.log("zoomOverTileUrlTemplate", zoomOverTileUrlTemplate);
                
                layer.setTileUrlTemplate(zoomOverTileUrlTemplate);
                return [256 * scale, 256 * scale];
            }
    
            /* установка дефотного шаблона url */
            layer.setTileUrlTemplate(defaultTileUrlTemplate);
            
            /* возвращем дефолтный размер тайла */
            return [256, 256];
        };
        
        return layer;
    };

    /* регистрируем новый тип слоя */
    ymaps.layer.storage.add('custom#map', YCustomMapLayer);

    /* создаем новый тип карты с кастомным слоем */
    let customMapType = new ymaps.MapType('Схема +', ['custom#map']);

    /* регистрируем новый тип карты */
    ymaps.mapType.storage.add('custom_map#map', customMapType);

    /* выполняем инициализацию карты, с указанием вновь созданного типа */
    myMap = new ymaps.Map(
        // ID DOM-элемента, в который будет добавлена карта.
        'map',
        // Параметры карты.
        {
            // Географические координаты центра отображаемой карты.
            center: [55.76, 37.64], // Москва
            // Масштаб.
            zoom: 19,
            controls: ["zoomControl"],
            type: 'custom_map#map'
        }
    );

    /* добавляем на карту контрол выбора типа с указанием стандартных карт и вновь созданной */    
    myMap.controls.add(new ymaps.control.TypeSelector({
        mapTypes: ['custom_map#map','yandex#map', 'yandex#hybrid', 'yandex#satellite'],
    }));
    
    

});

ссылки на документацию:
1. Создание собственного типа карты
2. Наследование [ util.augment ]
3. Определение плотности пикселей клиента [ util.hd ]

Дополнительно:

1. При реализации увеличенного зума для 'yandex#satellite' - в запросе тайла от сервера Яндекса параметр scale не фигурирует и не несет смысла. Качество спутникового снимка не увеличится =)

2. Карта 'yandex#hybrid' состоит из двух слоев - подложка спутниковых снимков и наложение поверх нее векторного слоя с улицами и надписями (с прозрачным фоном). Векторый слой надписей успешно поддается масштабированию с сохранением качества изображения, как и с простым типом карты, рассмотренном выше

13 комментариев
Диапазон масштабов стандартных слоёв зависит от конкретной точки, т.е. в Москве будут доступны более подробные диапазоны слоёв, по сравнению с какой-нибудь степью.
Вам нужно создать свой MapType со своим слоем, у которого должен быть определен метод getZoomRange, и стандартным слоем Яндекса "yandex#map" (если нужна схема)


https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/MapType-docpage/
dimik,
, спасибо.


попробовал данный вариант. гибридный тип карты получилось создать с масштабом 20
// Диапазон доступных масштабов.
layer.getZoomRange = function () {
    return ymaps.vow.resolve([0, 20]);
};
и по верху подложки Яндекса рисуются мои прозрачные тайлы.


Но, как и говорил выше - по моей локации максимально доступный зум тайлов Яндекса - 19. И теперь, зуммируя карту до 20 - я вижу свой слой, под которым на сером фоне красуется надпись - "Для этого участка местности нет данных..."


Собственно и вопрос то в том - можно как-то заставить подложку Яндекса менять масштаб тайлов, в случае если они недоступны для конкретного уровня зума.
Тоесть, если тайлов Яндекса нет для 20-го зума - тогда тайлы подложки, полученные на 19-м зуме  увеличивать до размера 512 пикселей, вместо стандартного размера 256


 
kashoutin.s,
не уверен на 100%, что это можно сделать.
Попробуйте создать свой слой, и в нем использовать стандартный слой Яндекса из хранилища, но ваша реализация должна отдавать другой zoomRange, tiileSize и tileUrl, см соотв. интерфейс слоя
Получить стандартый слой можно из хранилища слоев по ключу
ymaps.layer.storage.get('yandex#map')
dimik,
 не выходит, или где-то ошибаюсь


пробую просто слой получить и его добавить как новый тип карты


ymaps.ready(function () {

    var YCustomLayer = function () {
           var layer = ymaps.layer.storage.get('yandex#map');
           return layer;
      };

      // Добавим слой под ключом.
      ymaps.layer.storage.add('my#custom', YCustomLayer);

      var myMapType = new ymaps.MapType('MQ + Ya', ['my#custom']);

      // Добавим в хранилище типов карты.
       ymaps.mapType.storage.add('mq_ya#hybrid', myMapType);

       
       myMap = new ymaps.Map(
              'map',
               {
                      center: [48.7193900, 44.5018300],

                      zoom: 15,
                     type: 'mq_ya#hybrid'
                }
       );
}); 


падает с ошибкой  Uncaught TypeError: t.getParent is not a function.

может есть иной вариант доступа к дефолтным слоям... вместо ymaps.layer.storage.get('yandex#map')
kashoutin.s,
ymaps.layer.storage.get('yandex#map') возвращает не экземпляр слоя, а его конструктор. Вам надо самому инстанцировать его в YCustomLayer
dimik,
если не трудно - можно пример?
kashoutin.s,
Что-то типа этого:

var YCustomLayer = function () {

  var YandexLayer = ymaps.layer.storage.get('yandex#map');
  var layer = new YandexLayer;
  layer.getZoomRange = function() {
     return ymaps.vow.resolve([0, 23]);
  };
  layer.getTileSize = function(zoom) {
     if(zoom >= 20) {
        return [256 * _scaleFactor_, 256 * _scaleFactor_];
     }
     return [256, 256];
  };


  return layer;
};
Обновлено 21 октября 2017, 18:45
dimik,
,благодарю. я просто пытался вызвать new не вынося полученный конструктор в отдельную переменную.


в целом немного поправив пример (выделил жирным), получается следующее


let YCustomMapLayer = function () {
    let YandexMapLayer = ymaps.layer.storage.get('yandex#map');
    let layer = new YandexMapLayer();
    let defaultTileUrlTemplate = layer.getTileUrlTemplate();
    
    /* перекрытие доступного уровня зума для слоя */
    layer.getZoomRange = function () { 
        return ymaps.vow.resolve([0, 23]);
    };


     /* перекрытие размера тайла */
     layer.getTileSize = function(zoom) {
        if(zoom >= 20) {
            /* считаем требуемую степень масштабирования тайла */
            let scale = 2**(zoom - 19) ;
            /*  подменяем урл получения тайла - при запросе тайла требуемого масштаба - сохраняем качество отобржения карты */

            layer.setTileUrlTemplate("https://vec0%d.maps.yandex.net/tiles?l=map&v=17.10.19-0&x=%x&y=%y&z=19&scale="+scale+"&%l");


            return [256 * scale, 256 * scale];
          }
          layer.setTileUrlTemplate(defaultTileUrlTemplate);
          return [256, 256];
    };
    return layer;
};


/* регистрируем новый тип слоя */
ymaps.layer.storage.add('custom#map', YCustomMapLayer);



/* создаем новый тип карты с кастомным слоем */
let customMapType = new ymaps.MapType('Схема +', ['custom#map']);



/* регистрируем новый тип карты */
ymaps.mapType.storage.add('custom_map#map', customMapType);



ну и далее - инициализируем карту с новым типом. 


все удачно работает.
Спасибо за помощь в решении задачи
kashoutin.s,
по хорошему не надо там 19 хардкодить, а следует вызывать "настоящий" getZoomRange. 
Плюс параметр scale нельзя увеличивать бесконечно.
Плюс вы забыли учитывать его изначальное значение - DPI экрана пользователя, сейчас часто уже 2.
В общем хороший overzoom не простая задача :(, но АПИ всегда может размыть и размазать изображение. 
thekashey,
согласен, хардкодить константы не есть гуд... и следует исправить - вчера закинул сюда рабочий вариант, без причесывания ))
 
максимальный scale запрашиваемого тайла - 16  (4096х4096 pixel) и это тоже нужно добавить обработку)


необходимость учета DPI в зависимости от качества экрана конкретного клиента проверяется через ymaps.util.hd.getPixelRatio() 
thekashey,
это конечно да, но не очень понятно как это сделать, если getTileSize синхронный, а getZoomRange асинхронный
kashoutin.s,
Если не трудно, поделитесь потом финальным вариантом, думаю, будет полезно добавить его в песочницу
dimik,
хоршо