一、问题产生
使用openlayer加载自己公司的wmts服务,要求根据用户权限、瓦片存在与否等进行直观的瓦片替换。例如某张瓦片不存在,则替换该瓦片为404的一张图片,某个区域用户无权限查看,则替换为无权限(401)图片(如下图);
二、问题分析&解决
首先贴上加载服务的代码。关于tileGrid
和projection
等属性与本人影像服务器计算有关,也与本博客无关,故不细说。
在这里,我们使用的是ol.layer.tile
类来加载瓦片,瓦片行列号计算方式满足wmts标准。故采用ol.source.WMTS
来进行加载。
let wmtsLayer = new ol.layer.Tile({
source: new ol.source.WMTS({
url: url,
projection: projection, //数据的投影坐标系
tileGrid: tileGrid,
format: "image/png",
crossOrigin: 'anonymous'
})
});
map.addLayer(wmtsLayer)
在加载服务之后,根据控制台的ajax请求,定位到瓦片请求的具体代码,找到源文件TileImage.js
中的tileLoadFunction
。查阅api也可以得到。
该类如下定义:
super({
//--其余代码
tileLoadFunction ?
options.tileLoadFunction : defaultTileLoadFunction,
//--其余代码
});
找到defaultTileLoadFunction
定义:
这里我们发现,最终生成的wmts格式的url,被赋值给了一个Image对象。我们的思路就有了。于是我们跟踪这个tileLoadFunction
,发现其可以在new ol.source.WMTS()
中进行配置。于是我们进行配置:
let wmtsLayer = new ol.layer.Tile({
source: new ol.source.WMTS({
url: url,
projection: projection, //数据的投影坐标系
tileGrid: tileGrid,
format: "image/png",
crossOrigin: 'anonymous',
tileLoadFunction: function (imageTile, src) {
//--调用xmlhttprequst,请求src获取状态码。
//--如果状态码为401替换imageTile的image为本地链接的某幅图片
//--如果状态码200,则直接替换为将src赋值给imageTile
}
})
});
map.addLayer(wmtsLayer)
然后这样做。满心欢喜去测试,发现代码替换后全部黑屏。瓦片无法正常加载。直觉就是异步的问题。在defaultTileLoadFunction
中,使用的是同步的方式,将src赋值给image。由image去完成图片的请求。但是上述示例中采用的是先请求在异步回调中给src赋值.于是做如下测试:
//--其余代码
tileLoadFunction: function (imageTile, src) {
setTimeout(function(){
(imageTile.getImage()).src = src
},200)
}
//--其余代码
果然一片黑屏。即,该函数无法支持异步请求。必然是上层逻辑在调用该函数后立马有对imageTile的处理过程。于是跟踪源码。发现调用的地方是在ol/ImageTile.js
中的load函数:
果然在下面有额外的逻辑。到这里,我们不能修改源码。该怎么办呢。
查阅api+跟踪源码,发现对于创建Tile的类也可以自己定义(佩服ol的灵活设计):
即,我可以自定义一个类,让瓦片按照我自己定义的类的加载规则来加载。
于是我们实现一个自己的类,继承默认的ImageTile.js
,修改load函数,实现异步加载瓦片。
/**
* @module ol/ImageTile
*/
import ImageTile from '../ol/ImageTile.js';
import TileState from '../ol/TileState.js';
import {
listenImage
} from '../ol/Image.js';
class ImageTileAnsyc extends ImageTile {
/**
* @param {import("./tilecoord.js").TileCoord} tileCoord Tile coordinate.
* @param {TileState} state State.
* @param {string} src Image source URI.
* @param {?string} crossOrigin Cross origin.
* @param {import("./Tile.js").LoadFunction} tileLoadFunction Tile load function.
* @param {import("./Tile.js").Options=} opt_options Tile options.
*/
constructor(tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options) {
super(tileCoord, state, src, crossOrigin, tileLoadFunction, opt_options);
}
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_).then(() => {
this.unlisten_ = listenImage(
this.image_,
this.handleImageLoad_.bind(this),
this.handleImageError_.bind(this)
);
});
}
}
}
export default ImageTileAnsyc;
什么都没变,只是修改了load函数,将tileLoadFunction配置为异步加载。在tileLoadFunction内的内容执行完毕时,再执行接下来的逻辑。
然后修改wmts的定义。完整代码如下:
let wmtsLayer = new ol.layer.Tile({
source: new ol.source.WMTS({
url: url,
projection: projection, //数据的投影坐标系
tileGrid: tileGrid,
format: "image/png",
crossOrigin: 'anonymous',
tileClass: ImageTileAnsyc,
tileLoadFunction: function (imageTile, src) {
return new Promise((resolve) => {
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200 || this.status == 304) {
imageTile.getImage().src = src
}
if (this.status == 401) {
imageTile.getImage().src = tile_401
}
if (this.status == 402) {
imageTile.getImage().src = tile_402
}
if (this.status == 404) {
// imageTile.getImage().src = tile_404
}
if (this.status == 423) {
imageTile.getImage().src = tile_423
}
resolve()
}
}
xhr.send();
})
}
})
});
map.addLayer(wmtsLayer)·
测试,通过。
当然,看到这里,应该是可以解决问题了。但是补充一点,大家一定会看出来,这里一张瓦片请求了2次。一次是xhr请求,另一次是对image的src赋值。能否进行优化?
当然可以。下面是我使用blob类型请求二进制流图片,然后直接将二进制流图片赋值给image的过程:
tileLoadFunction: function (imageTile, src) {
return new Promise((resolve) => {
var xmlhttp;
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", src, true);
xmlhttp.responseType = "blob";
xmlhttp.onload = function () {
if (this.status == 200) {
var blob = this.response;
(imageTile.getImage()).src = window.URL.createObjectURL(blob);
imageTile.setState(2);
}else if (this.status == 404) {
(imageTile.getImage()).src = './static/images/401.png'
}
resolve();
}
xmlhttp.send();
})
}
这里是最终代码。测试,一切正常。