OpenLayers源碼閱讀(四):TileImage及TileArcGISRest、XYZ、WMTS

前提:需要對網格地圖服務WMTS有一定的瞭解,瞭解其切片原理,明確座標系、原點、級別與分辨率等概念。

1、我們如何使用

說明:TileImage爲TileArcGISRest、XYZ、WMTS等的父類,在實際使用中,不會用到TileImage,TileImage會在後續小節中介紹。

1.1 TileArcGISRest

官網例子https://openlayers.org/en/v4.6.5/examples/arcgis-tiled.html?q=arcgis

var layer = new ol.layer.Tile({
	extent: [-13884991, 2870341, -7455066, 6338219],
	source: new ol.source.TileArcGISRest({
		url: "https://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer"
	})
})

1.2 XYZ

官網例子https://openlayers.org/en/v4.6.5/examples/xyz-esri.html?q=arcgis

var layer = new ol.layer.Tile({
	source: new ol.source.XYZ({
		url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{z}/{y}/{x}'
	})
})

1.3 WMTS

官網例子https://openlayers.org/en/v4.6.5/examples/wmts.html?q=wmts

var projection = ol.proj.get('EPSG:3857');
var projectionExtent = projection.getExtent();
var size = ol.extent.getWidth(projectionExtent) / 256;
var resolutions = new Array(14);
var matrixIds = new Array(14);
for (var z = 0; z < 14; ++z) {
  // generate resolutions and matrixIds arrays for this WMTS
  resolutions[z] = size / Math.pow(2, z);
  matrixIds[z] = z;
}

var layer = new ol.layer.Tile({
	opacity: 0.7,
	source: new ol.source.WMTS({
		url: 'https://services.arcgisonline.com/arcgis/rest/services/Demographics/USA_Population_Density/MapServer/WMTS/',
		layer: '0',
		matrixSet: 'EPSG:3857',
		format: 'image/png',
		projection: projection,
		tileGrid: new ol.tilegrid.WMTS({
			origin: ol.extent.getTopLeft(projectionExtent),
			resolutions: resolutions,
			matrixIds: matrixIds
		}),
		style: 'default',
		wrapX: true
	})
})

2、屬性

2.1 TileImage

在這裏插入圖片描述這裏重點關注以下幾個屬性:

  • projection:座標系
  • tileGrid:格網信息
  • tileLoadFunction:針對返回的瓦片地址做可選處理
  • tileUrlFunction:針對tile coordinate 做處理。比如級別限定、範圍限定
  • url/urls:需要包含"{z}/{x}/{y}"
  • wrapX: 是否伸展

2.2 TileArcGISRest

在TileImage基礎上,增加了params字段,同ImageArcGISRest中的params字段相同。
因此,在使用該類時,是動態獲取瓦片,不會讀取地圖服務器的已緩存的瓦片。

2.3 XYZ

在TileImage基礎上,增加了maxZoom、minZoom字段,對圖層進行級別限定

2.4 WMTS

在TileImage基礎上,增加了layer、style、version、format、matrixSet等OGC標準的wmts請求參數字段和dimensions擴展字段-用於添加額外的參數信息。

3、閱讀源碼

3.1 TileImage

https://github.com/openlayers/openlayers/blob/v4.6.5/src/ol/source/tileimage.js
默認參數設置

ol.source.TileImage.prototype.getTileGridForProjection = function(projection) {}

ol.source.TileImage.prototype.getTileCacheForProjection = function(projection) {}

ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) {
	imageTile.getImage().src = src;
}

關鍵函數 createTile_ 、getTile 、getTileInternal_
流程:

座標系一致
座標系不一致
無緩存
有緩存
有緩存
無緩存
轉換
getTile
getTileInternal_
project cache
createTile_
this.tileCache.get
project.cache
ol.reproj.Tile
// 根據z、x、y、Projection、pixelRatio 、key創建ol.Tile
ol.source.TileImage.prototype.createTile_ = function(z, x, y, pixelRatio, projection, key) {
  var tileCoord = [z, x, y]; //默認的網格座標
  var urlTileCoord = this.getTileCoordForTileUrlFunction(
      tileCoord, projection);  // 轉化爲地圖服務的網格座標
  var tileUrl = urlTileCoord ?
    this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; // 計算瓦片的地址
  var tile = new this.tileClass(
      tileCoord,
      tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY,
      tileUrl !== undefined ? tileUrl : '',
      this.crossOrigin,
      this.tileLoadFunction,
      this.tileOptions); // 生成對象ol.Tile
  tile.key = key;
  ol.events.listen(tile, ol.events.EventType.CHANGE,
      this.handleTileChange, this);
  return tile;
};

// 根據z、x、y、Projection、pixelRatio 獲取ol.Tile
ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection) {
  var sourceProjection = /** @type {!ol.proj.Projection} */ (this.getProjection()); // 圖層座標系
  // 判斷圖層座標系是否視圖座標系一致
  if (!ol.ENABLE_RASTER_REPROJECTION ||
      !sourceProjection || !projection ||
      ol.proj.equivalent(sourceProjection, projection)) {
    return this.getTileInternal(z, x, y, pixelRatio, sourceProjection || projection);
  } else {
    var cache = this.getTileCacheForProjection(projection);//通過座標系獲取緩存
    var tileCoord = [z, x, y];
    var tile;
    var tileCoordKey = ol.tilecoord.getKey(tileCoord);
    // 判斷緩存是否存在
    if (cache.containsKey(tileCoordKey)) { 
      tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
    }
    var key = this.getKey();
    if (tile && tile.key == key) {
      return tile;
    } else {
      var sourceTileGrid = this.getTileGridForProjection(sourceProjection); //圖層格網
      var targetTileGrid = this.getTileGridForProjection(projection); // 視圖格網
      var wrappedTileCoord =
          this.getTileCoordForTileUrlFunction(tileCoord, projection);
      // z、x、y轉爲對應圖層的 z、x、y,並獲取瓦片
      var newTile = new ol.reproj.Tile(
          sourceProjection, sourceTileGrid,
          projection, targetTileGrid,
          tileCoord, wrappedTileCoord, this.getTilePixelRatio(pixelRatio),
          this.getGutterInternal(),
          function(z, x, y, pixelRatio) {
            return this.getTileInternal(z, x, y, pixelRatio, sourceProjection);
          }.bind(this), this.reprojectionErrorThreshold_,
          this.renderReprojectionEdges_);
      newTile.key = key;

      if (tile) {
        newTile.interimTile = tile;
        newTile.refreshInterimChain();
        cache.replace(tileCoordKey, newTile);
      } else {
        cache.set(tileCoordKey, newTile);
      }
      return newTile;
    }
  }
};

// 根據z、x、y、Projection、pixelRatio 獲取ol.Tile
ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
  var tile = null;
  var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y); //根據z、x、y生成key值
  var key = this.getKey();
  // 根據key值,判斷瓦片緩存中是否已存在。
  if (!this.tileCache.containsKey(tileCoordKey)) { // 不存在
    tile = this.createTile_(z, x, y, pixelRatio, projection, key); //創建瓦片
    this.tileCache.set(tileCoordKey, tile); // 添加到緩衝中
  } else {
    tile = this.tileCache.get(tileCoordKey); // 存在,則獲取瓦片
    if (tile.key != key) { //更新瓦片
      // The source's params changed. If the tile has an interim tile and if we
      // can use it then we use it. Otherwise we create a new tile.  In both
      // cases we attempt to assign an interim tile to the new tile.
      var interimTile = tile;
      tile = this.createTile_(z, x, y, pixelRatio, projection, key);

      //make the new tile the head of the list,
      if (interimTile.getState() == ol.TileState.IDLE) {
        //the old tile hasn't begun loading yet, and is now outdated, so we can simply discard it
        tile.interimTile = interimTile.interimTile;
      } else {
        tile.interimTile = interimTile;
      }
      tile.refreshInterimChain();
      this.tileCache.replace(tileCoordKey, tile);
    }
  }
  return tile;
};

3.2 TileArcGISRest

https://github.com/openlayers/openlayers/blob/v4.6.5/src/ol/source/tilearcgisrest.js
關鍵函數 fixedTileUrlFunction、getRequestUrl_
流程:

fixedTileUrlFunction
getRequestUrl_
// 圖層的tileCoord(z、x、y)
ol.source.TileArcGISRest.prototype.fixedTileUrlFunction = function(tileCoord, pixelRatio, projection) {
  // 獲取格網
  var tileGrid = this.getTileGrid();
  if (!tileGrid) {
    tileGrid = this.getTileGridForProjection(projection);
  }

  if (tileGrid.getResolutions().length <= tileCoord[0]) {
    return undefined;
  }
  // 計算該瓦片的範圍
  var tileExtent = tileGrid.getTileCoordExtent(
      tileCoord, this.tmpExtent_);
  var tileSize = ol.size.toSize(
      tileGrid.getTileSize(tileCoord[0]), this.tmpSize);

  if (pixelRatio != 1) {
    tileSize = ol.size.scale(tileSize, pixelRatio, this.tmpSize);
  }

  // Apply default params and override with user specified values.
  var baseParams = {
    'F': 'image',
    'FORMAT': 'PNG32',
    'TRANSPARENT': true
  };
  ol.obj.assign(baseParams, this.params_);

  return this.getRequestUrl_(tileCoord, tileSize, tileExtent,
      pixelRatio, projection, baseParams);
};

// arcgis url 請求
ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent,
    pixelRatio, projection, params) {

  var urls = this.urls;
  if (!urls) {
    return undefined;
  }

  // ArcGIS Server only wants the numeric portion of the projection ID.
  var srid = projection.getCode().split(':').pop();

  params['SIZE'] = tileSize[0] + ',' + tileSize[1];
  params['BBOX'] = tileExtent.join(',');
  params['BBOXSR'] = srid;
  params['IMAGESR'] = srid;
  params['DPI'] = Math.round(
      params['DPI'] ? params['DPI'] * pixelRatio : 90 * pixelRatio
  );

  var url;
  if (urls.length == 1) {
    url = urls[0];
  } else {
    var index = ol.math.modulo(ol.tilecoord.hash(tileCoord), urls.length);
    url = urls[index];
  }

  var modifiedUrl = url
      .replace(/MapServer\/?$/, 'MapServer/export')
      .replace(/ImageServer\/?$/, 'ImageServer/exportImage');
  return ol.uri.appendParams(modifiedUrl, params);
};

3.3 XYZ

https://github.com/openlayers/openlayers/blob/v4.6.5/src/ol/source/xyz.js
繼承TileImage

3.4 WMTS

https://github.com/openlayers/openlayers/blob/v4.6.5/src/ol/source/wmts.js

  var context = {
    'layer': this.layer_,
    'style': this.style_,
    'tilematrixset': this.matrixSet_
  };

  if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
    ol.obj.assign(context, {
      'Service': 'WMTS',
      'Request': 'GetTile',
      'Version': this.version_,
      'Format': this.format_
    });
  }

// 通過模版生成url
this.createFromWMTSTemplate_ = function(template) {

    // TODO: we may want to create our own appendParams function so that params
    // order conforms to wmts spec guidance, and so that we can avoid to escape
    // special template params

    template = (requestEncoding == ol.source.WMTSRequestEncoding.KVP) ?
      ol.uri.appendParams(template, context) :
      template.replace(/\{(\w+?)\}/g, function(m, p) {
        return (p.toLowerCase() in context) ? context[p.toLowerCase()] : m;
      });

    return (
      /**
       * @param {ol.TileCoord} tileCoord Tile coordinate.
       * @param {number} pixelRatio Pixel ratio.
       * @param {ol.proj.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, pixelRatio, projection) {
        if (!tileCoord) {
          return undefined;
        } else {
          var localContext = {
            'TileMatrix': tileGrid.getMatrixId(tileCoord[0]),
            'TileCol': tileCoord[1],
            'TileRow': -tileCoord[2] - 1
          };
          ol.obj.assign(localContext, dimensions);
          var url = template;
          if (requestEncoding == ol.source.WMTSRequestEncoding.KVP) {
            url = ol.uri.appendParams(url, localContext);
          } else {
            url = url.replace(/\{(\w+?)\}/g, function(m, p) {
              return localContext[p];
            });
          }
          return url;
        }
      });
  };

// 通過Capabilities文檔獲取元信息
ol.source.WMTS.optionsFromCapabilities = function(wmtsCap, config) {
  ……
  return {
    urls: urls,
    layer: config['layer'],
    matrixSet: matrixSet,
    format: format,
    projection: projection,
    requestEncoding: requestEncoding,
    tileGrid: tileGrid,
    style: style,
    dimensions: dimensions,
    wrapX: wrapX,
    crossOrigin: config['crossOrigin']
  };
};

4、總結

1、在加載arcgis server 地圖服務時,根據實際需要,選擇不同的調用方式

  • 需要過濾圖層,使用TileArcGISRest,但會影響加載效率。數據量的地圖服務,不建議使用
  • 使用已緩存好的瓦片資源,使用XYZ,加載效率快、但顯示效果與服務器保持一致,不能修改。
  • 與OGC的服務保持一致,則使用WMTS

2、訪問OGC的WMTS的服務時,以下兩種方式可以任意選擇

  • 獲取所有的元信息,使用WMTS
  • 也可以採用url模版的方式,使用XYZ
    3、TileImage瓦片的獲取,都是通過tileCoord計算得來,
  • TileArcGISRest:tileCoord 計算 BBOX
  • XYZ和WMTS:tileCoord 計算 TileMatrix(zoom)、TileCol、TileRow
    因此,要明確tileCoord是怎麼來的。可以參考ol.source.TileDebug和官網例子
    https://openlayers.org/en/v4.6.5/examples/canvas-tiles.html?q=debug
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章