openlayers3瓦片加载的源码浅析与小结

--感谢作者:老羽 (QQ:274103592, 邮箱:michael.zy@163.com, 简书:老羽 )

一、类图与逻辑

ol-tilerender.png

上图中列了关于瓦片图层加载相关的重要方法。

  1. Map对象初始化时根据options.renderer创建ol.renderer.Map的实例,默认是ol.renderer.canvas.Map;
  2. ol.render.canvas.Map实现了抽象方法createLayerRenderer,这是一个简单工厂,根据不同的图层创建对应的ol.renderer.Layer。其中ol.layer.Tile对应的就是ol.renderer.canvas.TileLayer;
  3. ol.renderer.canvas.TileLayer.prepareFrame调用source对应的 TileGrid.getTileRangeForExtentAndResolution获取可视范围内的瓦片范围,并循环遍历加载瓦片;
  4. TileGrid在初始化时就计算出了对应layer的所有瓦片范围:
    1. calculateTileRanges-》循环遍历resolutions,调用 getTileRangeForExtentAndZ,根据extent计算瓦片范围;
    2. getTileRangeForExtentAndResolution计算瓦片的范围:
  ol.tilegrid.TileGrid.prototype.getTileRangeForExtentAndResolution = function(
    extent, resolution, opt_tileRange) {
    var tileCoord = ol.tilegrid.TileGrid.tmpTileCoord_;
    // 根据extent的左下角的计算瓦片坐标;
    this.getTileCoordForXYAndResolution_(
      extent[0], extent[1], resolution, false, tileCoord);
    var minX = tileCoord[1];
    var minY = tileCoord[2];
      // 根据extent的右上角的计算瓦片坐标;
     this.getTileCoordForXYAndResolution_(
        extent[2], extent[3], resolution, true, tileCoord);
    // 得到某个resolution级别下的瓦片范围(左下角瓦片坐标 - 右上角瓦片坐标)
    return ol.TileRange.createOrUpdate(
      minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
   };

  // 根据extent左下角及右上角的坐标-origin后得到地图的长宽 / resolution得到像素值;
  // 然后 / tileSize 得到需要多少张瓦片;
  // 当计算extent右上角的瓦片坐标时,因为瓦片坐标是从0开始计算,当瓦片数量为例如1.5此类小数时,
  // 应该是2张瓦片,从0开始计算,那么XY就应该向下取整,取1;0,1两张瓦片;
  ol.tilegrid.TileGrid.prototype.getTileCoordForXYAndResolution_ = function(
    x, y, resolution, reverseIntersectionPolicy, opt_tileCoord) {
    var z = this.getZForResolution(resolution);
    var scale = resolution / this.getResolution(z);
    var origin = this.getOrigin(z);
    var tileSize = ol.size.toSize(this.getTileSize(z), this.tmpSize_);

    var adjustX = reverseIntersectionPolicy ? 0.5 : 0;
    var adjustY = reverseIntersectionPolicy ? 0 : 0.5;
    var xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX);
    var yFromOrigin = Math.floor((y - origin[1]) / resolution + adjustY);
    var tileCoordX = scale * xFromOrigin / tileSize[0];
    var tileCoordY = scale * yFromOrigin / tileSize[1];

    if (reverseIntersectionPolicy) {
      tileCoordX = Math.ceil(tileCoordX) - 1;
      tileCoordY = Math.ceil(tileCoordY) - 1;
    } else {
      tileCoordX = Math.floor(tileCoordX);
      tileCoordY = Math.floor(tileCoordY);
    }

    return ol.tilecoord.createOrUpdate(z, tileCoordX, tileCoordY, opt_tileCoord);
  };

二、各种瓦片加载的小结

通过上述分析后,应该能较好的理解瓦片的坐标是如何计算的,当应用到不同的地图瓦片加载时就可以得心应手。以下通过不同的几种类型继续说明瓦片计算的方式:

1、TMS瓦片加载

先看看TMS瓦片的规则,origin在左下角,X轴从左至右递增,Y轴从下往上递增(先计算左下角,然后计算右上角)。 TMS瓦片规则

而TileGrid设置origin为ol.extent.getBottomLeft(extent)后,规则也是从左下角到右上角,X轴从左至右递增,Y轴从下往上递增,与TMS规则是完全一致的,参考代码与参考效果如下:

  var resolutions = [];
  var tileSize = 256;
  var extent = [12665080.52765571, 2550703.6338763316, 12725465.780000998, 2601457.820657688]; //深圳地区
  var projection = new ol.proj.get("EPSG:3857");
  var projectionExtent = projection.getExtent();

  for (var i = 0; i < 19; i++) {
    resolutions[i] = Math.pow(2, 18 - i);
  }            

  var tilegrid = new ol.tilegrid.TileGrid({
    origin: ol.extent.getBottomLeft(projectionExtent),
    resolutions: resolutions,
    extent: projectionExtent,//extent,
    tileSize: [256, 256],
  });         

  var map = new ol.Map({
    target: "map",
    layers: [
      // 调试瓦片
      new ol.layer.Tile({
        source: new ol.source.TileDebug({
          projection: projection,
          tileGrid: tilegrid,
          tileSize: [256, 256],
          extent :  projectionExtent,
          wrapX: false
        }),
      })
    ],
    view: new ol.View({
      projection: projection,
      center: [12697184.079535482, 2563239.3065151004],//深圳
      resolutions: resolutions,
    }),
  });
  map.getView().setZoom(1);

示意图

假如上面代码中,我想只显示深圳地区的瓦片,其余的瓦片不显示,这种场景是很普遍的,那么代码调整如下:

  var tilegrid = new ol.tilegrid.TileGrid({
    origin: ol.extent.getBottomLeft(projectionExtent),  //origin位置不能变!!!!!!
    resolutions: resolutions,
    extent:  extent,//projectionExtent //设置extent为深圳片区的extent;
    tileSize: [256, 256],
  });

  // ..................

  // 调试瓦片
  new ol.layer.Tile({
    source: new ol.source.TileDebug({
      projection: projection,
      tileGrid: tilegrid,
      extent: extent,//projectionExtent //设置extent为深圳片区的extent;
      wrapX: false
    }),
  })

2、WMTS瓦片加载

WMTS规则如下,origin在左上角,X轴从左至右递增,Y轴是从上往下递增(先计算左上角,然后计算右下角)

WMTS规则

那么将tileGrid设置origin为ol.extent.getTopLeft(projectionExtent), 但是TileGrid始终都是先计算左下角的瓦片坐标,然后计算右上角的瓦片坐标,因此Y轴是相反的。那么修改Y轴坐标就可以得到正确值:

  var tilegrid = new ol.tilegrid.TileGrid({
    origin: ol.extent.getTopLeft(projectionExtent),  // WMTS Origin在左上角,origin位置不能变;
    resolutions: resolutions,
    extent: extent,
    tileSize: [256, 256],
  });

  // 其余代码略.....
  new ol.layer.Tile({
    source: new ol.source.TileImage({
      projection: projection,
      tileGrid: tilegrid(),
      tileUrlFunction: function (tileCoord, pixelRatio, proj) {
        if (!tileCoord) {
          return "";
        }
        var z = tileCoord[0];
        var x = tileCoord[1];
        var y = -tileCoord[2] - 1; // y轴取反,-1目的是为了从0开始计数;

        return ''; // 自行设置URL ,请注意 WMTS中用TileRow标识Y,用TileCol表示X;
      }
    }),
  })

3、百度地图瓦片加载

百度瓦片片规则如下:Origin在[0,0],X轴从左至右递增,Y轴从下往上递增(从左下角到右上角)。

Paste_Image.png

从百度的瓦片规则看出来,与TileGrid的规则是完全一致,将origin设置为[0,0]即可。参考代码如下:

  var tilegrid = new ol.tilegrid.TileGrid({
    origin: [0, 0],
    resolutions: resolutions,
    extent: extent,//projectionExtent,
    tileSize: [256, 256],
  });

 var tilesource = new ol.source.TileImage({
    projection: projection,
    tileGrid: tilegrid,
    tileUrlFunction: function (xyz, obj1, obj2) {
      if (!xyz) {
        return "";
      }
      var z = xyz[0]+ 11; // 从第11级开始加载;深圳地区;
      var x = xyz[1];
      var y = xyz[2];
      if (x < 0) {
        x = "M" + (-x);
      }
      if (y < 0) {
        y = "M" + (-y);
      }                    
      return "http://online3.map.bdimg.com/tile/?qt=tile&x=" + x + "&y=" + y + "&z=" + z + "&styles=pl&udt=20141119&scaler=1";                   
    }
  });

4、腾讯地图瓦片加载

腾讯地图完全遵守TMS规则,地图投影坐标系采用Web Mercator投影,最小缩放级别为第4级。参考代码如下:

  // QQ地图完全遵守TMS规则;
  var tileGrid = new ol.tilegrid.TileGrid({
    resolutions: resolutions3857,
    tileSize: [256, 256],
    extent: projection3857Extent,
    origin: ol.extent.getBottomLeft(projection3857Extent), // Origin左下角
  });

  var tilesource = new ol.source.TileImage({
    tileUrlFunction: function (xyz, obj1, obj2) {
      if (!xyz) {
        return "";
      }
      var z = xyz[0];
      var x = xyz[1];
      var y = xyz[2];
      return "http://rt1.map.gtimg.com/realtimerender?z=" + z + "&x=" + x + "&y=" + y + "&type=vector&style=0&v=1.1.2"
    },
    projection: projection3857,
    tileGrid : tileGrid
  });