前提:需要對網格地圖服務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_
流程:
// 根據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_
流程:
// 圖層的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