我们来解析一下ol对于切片的计算与加载过程
创建图层
一切的出发点都是初始化地图:
const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: [0, 0],
zoom: 2
})
});
new TileLayer()
时定义了渲染切片图片的方法
创建切片数据源
new OSM()
的过程基本在各级父类中实现,创建实例并定义了很多方法,
TileImage
中定义的主要方法:
- createTile_() 创建单个切片
- getTile() 获取单个切片
- getTileInternal() 获取内存缓存单个切片
TileSource
中定义的主要方法:
- forEachLoadedTile() 遍历加载切片
图层渲染
在切片图层渲染的renderFrame()
方法中,开始计算切片、请求、渲染
计算切片
CanvasTileLayerRenderer
renderFrame(frameState, target) {
...
// ---- 计算切片 ----
// 根据范围和级别计算切片编号的范围,在TileGrid中实现
const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
// 嵌套循环遍历将要加载的切片索引
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
// ---- 加载切片 ----
// 实际上尝试是从缓存中读取,若没有则定义切片加载完成事件,等待切片队列请求图片
const tile = this.getTile(z, x, y, frameState);
if (this.isDrawableTile(tile)) {
const uid = getUid(this);
if (tile.getState() == TileState.LOADED) {
// 如果切片加载完成则在canvas上绘制切片
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
...
}
}
...
// 通过这个方法将所需切片加入切片请求队列中
this.manageTilePyramid(frameState, tileSource, tileGrid, pixelRatio,
projection, extent, z, tileLayer.getPreload());
}
}
}
manageTilePyramid( ... ) {
const tileSourceKey = getUid(tileSource);
if (!(tileSourceKey in frameState.wantedTiles)) {
frameState.wantedTiles[tileSourceKey] = {};
}
const wantedTiles = frameState.wantedTiles[tileSourceKey];
//读取队列
const tileQueue = frameState.tileQueue;
const minZoom = tileGrid.getMinZoom();
let tile, tileRange, tileResolution, x, y, z;
// 遍历所有需要的切片
for (z = minZoom; z <= currentZ; ++z) {
tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
tileResolution = tileGrid.getResolution(z);
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
if (currentZ - z <= preload) {
tile = tileSource.getTile(z, x, y, pixelRatio, projection);
if (tile.getState() == TileState.IDLE) {
wantedTiles[tile.getKey()] = true;
if (!tileQueue.isKeyQueued(tile.getKey())) {
// 插入队列
tileQueue.enqueue([tile, tileSourceKey,
tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
}
}
if (opt_tileCallback !== undefined) {
opt_tileCallback(tile);
}
} else {
tileSource.useTile(z, x, y, projection);
}
}
}
}
}
TileGrid
// 分别计算x方向和y方向上的范围
getTileRangeForExtentAndZ(extent, z, opt_tileRange) {
const tileCoord = tmpTileCoord;
// 获取min点的切片索引
this.getTileCoordForXYAndZ_(extent[0], extent[3], z, false, tileCoord);
const minX = tileCoord[1];
const minY = tileCoord[2];
// 获取max点的切片索引
this.getTileCoordForXYAndZ_(extent[2], extent[1], z, true, tileCoord);
return createOrUpdateTileRange(minX, tileCoord[1], minY, tileCoord[2], opt_tileRange);
}
// 通过座标起点和当前位置的距离,除以分辨率得到像素距离,除以切片像素大小,得到索引
getTileCoordForXYAndZ_(x, y, z, reverseIntersectionPolicy, opt_tileCoord) {
// 座标起点
const origin = this.getOrigin(z);
// 当前级别下的像素分辨率
const resolution = this.getResolution(z);
// 切片图片的像素大小
const tileSize = toSize(this.getTileSize(z), this.tmpSize_);
// 根据需要扩0.5度
const adjustX = reverseIntersectionPolicy ? 0.5 : 0;
const adjustY = reverseIntersectionPolicy ? 0.5 : 0;
// 当前位置距离起点像素距离
const xFromOrigin = Math.floor((x - origin[0]) / resolution + adjustX);
const yFromOrigin = Math.floor((origin[1] - y) / resolution + adjustY);
//除以每张切片的像素大小就是像素的位置索引了
let tileCoordX = xFromOrigin / tileSize[0];
let tileCoordY = 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 createOrUpdateTileCoord(z, tileCoordX, tileCoordY, opt_tileCoord);
}
请求切片
请求切片的过程是在PluggableMap
中用队列批量请求图片
handlePostRender() {
const tileQueue = this.tileQueue_;
if (!tileQueue.isEmpty()) {
let maxTotalLoading = this.maxTilesLoading_;
let maxNewLoads = maxTotalLoading;
if (frameState) {
const hints = frameState.viewHints;
if (hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]) {
const lowOnFrameBudget = !IMAGE_DECODE && Date.now() - frameState.time > 8;
maxTotalLoading = lowOnFrameBudget ? 0 : 8;
maxNewLoads = lowOnFrameBudget ? 0 : 2;
}
}
if (tileQueue.getTilesLoading() < maxTotalLoading) {
tileQueue.reprioritize(); // FIXME only call if view has changed
// 请求切片--
tileQueue.loadMoreTiles(maxTotalLoading, maxNewLoads);
}
}
if (frameState && this.hasListener(RenderEventType.RENDERCOMPLETE) && !frameState.animate &&
!this.tileQueue_.getTilesLoading() && !this.getLoading()) {
this.renderer_.dispatchRenderEvent(RenderEventType.RENDERCOMPLETE, frameState);
}
const postRenderFunctions = this.postRenderFunctions_;
for (let i = 0, ii = postRenderFunctions.length; i < ii; ++i) {
postRenderFunctions[i](this, frameState);
}
postRenderFunctions.length = 0;
}
TileQueue
loadMoreTiles(maxTotalLoading, maxNewLoads) {
let newLoads = 0;
let abortedTiles = false;
let state, tile, tileKey;
while (this.tilesLoading_ < maxTotalLoading && newLoads < maxNewLoads &&
this.getCount() > 0) {
tile = /** @type {import("./Tile.js").default} */ (this.dequeue()[0]);
tileKey = tile.getKey();
state = tile.getState();
if (state === TileState.ABORT) {
abortedTiles = true;
} else if (state === TileState.IDLE && !(tileKey in this.tilesLoadingKeys_)) {
this.tilesLoadingKeys_[tileKey] = true;
++this.tilesLoading_;
++newLoads;
// ---- 终于到load了 ---
// 在 ImageTile 中实现
tile.load();
}
}
if (newLoads === 0 && abortedTiles) {
// Do not stop the render loop when all wanted tiles were aborted due to
// a small, saturated tile cache.
this.tileChangeCallback_();
}
}
ImageTile
load() {
if (this.state == TileState.ERROR) {
this.state = TileState.IDLE;
this.image_ = new Image();
if (this.crossOrigin_ !== null) {
this.image_.crossOrigin = this.crossOrigin_;
}
}
if (this.state == TileState.IDLE) {
this.state = TileState.LOADING;
this.changed();
// 执行加载
this.tileLoadFunction_(this, this.src_);
// 绑定加载成功事件
this.unlisten_ = listenImage(
this.image_,
this.handleImageLoad_.bind(this),
this.handleImageError_.bind(this)
);
}
}
handleImageLoad_() {
// 成功加载到图片了
const image = /** @type {HTMLImageElement} */ (this.image_);
if (image.naturalWidth && image.naturalHeight) {
this.state = TileState.LOADED;
} else {
this.state = TileState.EMPTY;
}
// 停止监听
this.unlistenImage_();
// 派发成功事件,通知渲染
this.changed();
}