如何使用Leaflet地图使用'矮胖'(粗粒度)缩放级别?

时间:2015-03-06 17:21:14

标签: javascript jquery gis leaflet

我将Leaflet.js用于基于桌面和移动浏览器的地图,并且需要支持各种地图图块服务。其中一些切片服务使用粗略缩放级别(如1,5,10,15)定义,如果我请求不受支持的缩放级别,则服务不会返回切片。 (例如,如果我在不支持缩放级别service/tiles/6/x/y时请求6。)

Leaflet tile图层支持minZoommaxZoom,但我想弄清楚是否有最佳做法来进行粗略缩放级别,以及其他人是否遇到过这种情况。

我在GitHub上发现这篇文章解决了不支持的缩放级别的平铺缩放:https://github.com/Leaflet/Leaflet/pull/1802

但我不确定这是否适用。 (我不确定我是想缩放还是“补间”缩放级别......但如果这有意义并且不太困难我愿意尝试。)

我已经开始尝试这种方法变得混乱,因为缩放会导致更多缩放,我必须区分用户驱动的缩放和系统驱动的缩放:

// layer metadata (assumption: Levels is in ascending order of zoom)
var layerDef = { Title: 'Service lines', Levels: [10, 15, 19] };

// create Leaflet Tile Layer and show on map
var layer = L.tileLayer('://host/service/{z}/{x}/{y}');
layer.minZoom = layerDef.Levels[0];
layer.maxZoom = layerDef.Levels[layerDef.Levels-1];
layer.addTo(map);

// initialize lastZoom for coarse zoom management
var lastZoom = map.getZoom();    

var userZoom = true;

// handle supported zoom levels when zoom changes
map.on('zoomend', function (e)
{
    // get new zoom level
    var z = e.target._zoom || map.getZoom();

    if (userZoom) // assume user initiated this zoom
    {
        // is this zoom level supported?
        var zIdx = $.inArray(z, layerDef.Levels);
        if (zIdx === -1)
        {
            // zoom level is not supported; zoom out or in to supported level

            // delta: < 0 for zoom out, > 0 for zoom in
            var zDelta = z - lastZoom;
            var zLastIdx = $.inArray(lastZoom, layerDef.Levels);
            var newZoom = -1;
            if (zDelta > 0)
            {
                // user zoomed in to unsupported level.
                // zoom in to next supported level (rely on layer.maxZoom)
                newZoom = layerDef.Levels[zLastIdx + 1];
            }
            else
            {
                // user zoomed out to unsupported level.
                // zoom out to next supported level  (rely on layer.minZoom)
                newZoom = layerDef.Levels[zLastIdx - 1];
            }
            if (newZoom !== -1)
            {
                userZoom = false; // set flag

                setTimeout(function ()
                {
                    map.setZoom(newZoom); // set zoom -- seems to not work if called from within zoomend handler
                }, 100); // delay call to setZoom() to fix issue
            }
        }
    }
    else
    {
        userZoom = true; // clear flag
    }
    lastZoom = z;
});

(旁注:我希望粗略缩放级别的原因很明显:在每个缩放级别创建和存储光栅图块会很昂贵,特别是对于大型地理区域,尤其是与自己的离线移动设备一起使用时[本地]磁贴服务器,有限的无线数据计划,有限的存储容量等。例如,这可能不是玩具应用程序和谷歌地图可能遇到的问题,而是特定于域和移动应用程序的空间和带宽是非常珍贵。)

谢谢!

更新:我发现我对此代码遇到的问题是map.setZoom(z)zoomEnd处理程序中调用时无法正常工作(它确实设置了缩放,但导致显示灰色/不存在的图块问题,可能是因为Leaflet仍处于缩放/缩放过程中。修复是使用setTimeout将呼叫延迟到setZoom()。但是,如果有其他人处理过此问题,我仍然很好奇,如果有更好的方式......(我更新了上面的代码以使用setZoom修复)

2 个答案:

答案 0 :(得分:1)

目前在GitHub上的Leaflet存储库中正在审核一个提交。它将zoomFactor添加到地图的选项中。也许这就是你要找的东西。至少,我认为只要您的可用tileset具有缩放级别(不知道这是否是正确的技术术语)是最低可用缩放级别的倍数,它就会起作用。

请参阅:https://github.com/Leaflet/Leaflet/pull/3285

答案 1 :(得分:1)

以下(不保证,基于this)应与Leaflet v1.7.3一起使用,但可能不适用于当前的主。

它使用serverZooms选项将磁贴服务器上的可用缩放级别指定为有序数组。

覆盖L.TileLayer._getZoomForUrl以返回匹配或下一个较低的可用服务器缩放。还会覆盖L.TileLayer._getTileSize以增加切片大小,以便在服务器缩放之间缩放切片。

L.TileLayer.Overzoom = L.TileLayer.extend({

    options: {
        // List of available server zoom levels in ascending order. Empty means all
        // client zooms are available (default). Allows to only request tiles at certain
        // zooms and resizes tiles on the other zooms.
        serverZooms: []
    },

    // add serverZooms (when maxNativeZoom is not defined)
    // @override
    _getTileSize: function() {
        var map = this._map,
            options = this.options,
            zoom = map.getZoom() + options.zoomOffset,
            zoomN = options.maxNativeZoom || this._getServerZoom(zoom);

        // increase tile size when overscaling
        return zoomN && zoom !== zoomN ?
            Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * options.tileSize) :
            options.tileSize;
    },

    // @override
    _getZoomForUrl: function () {
        var zoom = L.TileLayer.prototype._getZoomForUrl.call(this);
        return this._getServerZoom(zoom);
    },

    // Returns the appropriate server zoom to request tiles for the current zoom level.
    // Next lower or equal server zoom to current zoom, or minimum server zoom if no lower
    // (should be restricted by setting minZoom to avoid loading too many tiles).
    _getServerZoom: function(zoom) {
        var serverZooms = this.options.serverZooms || [],
            result = zoom;
        // expects serverZooms to be sorted ascending
        for (var i = 0, len = serverZooms.length; i < len; i++) {
            if (serverZooms[i] <= zoom) {
                result = serverZooms[i];
            } else {
                if (i === 0) {
                    // zoom < smallest serverZoom
                    result = serverZooms[0];
                }
                break;
            }
        }
        return result;
    }
});


(function () {
  var map = new L.Map('map');
  map.setView([50, 10], 5);

  new L.TileLayer.Overzoom('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    serverZooms: [0, 1, 2, 3, 6, 9, 12, 15, 17],
    attribution : '© <a target="_parent" href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map);
})();
body {
  margin: 0;
}
html, body, #map {
  width: 100%;
  height: 100%;
}
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.js"></script>

<div id="map"></div>