利用IndexedDB缓存图层数据
有时业务里需要在前端缓存图层里的数据来提高图层数据的加载速度和体验,这种需求主要体现在:
- 网络不好
- 数据体积大,尤其是模型数据,3DTile这些模型数据啥的
- 数据基本是静态的,不怎么更新,每次访问服务端没有是么意义,浪费服务器资源
这里我们以缓存TileLayer的数据为例来写个例子,看看maptalks里关于自定义图层加载数据的方式的使用
选择IndexedDB操作工具库
关于是么是IndexedDB这里我就不介绍了,为了操作方便我这里直接使用了工具库 localForage,
关于这个工具库的详细文档请参阅链接里内容
关于ImageBitMap
关于ImageBitmap文档请参阅ImageBitmap
- maptalks的TileLayer是支持我们返回ImageBitmap数据的,并开放了自定义函数
loadTileBitmap
- ImageBitmap 是可以直接存入IndexedDB数据库的
根据以上两点那么操作起来就非常简单了
编码
- 自定义TileLayer加载tile的函数
js
const baseLayer = new maptalks.TileLayer("base", {
urlTemplate:
"https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ["a", "b", "c", "d"],
maxAvailableZoom: 18,
version: 1,
});
baseLayer.on("renderercreate", function (e) {
//load tile image
// img(Image): an Image object
// url(String): the url of the tile
e.renderer.loadTileBitmap = function (url, tile, callback) {};
});
瓦片加载函数里判断当前瓦片是否本地存在
如果存在返回给图层,否则进行网络请求
js
baseLayer.on("renderercreate", function (e) {
//load tile image
// img(Image): an Image object
// url(String): the url of the tile
e.renderer.loadTileBitmap = function (url, tile, callback) {
const { x, y, z } = tile;
const id = baseLayer.getId();
const version = baseLayer.options.version;
const key = `layer_${id}_${version}_${x}_${y}_${z}`;
dataStore
.getItem(key)
.then((image) => {
if (image) {
console.log("cache hit,key:", key);
} else {
}
})
.catch((error) => {
console.error(error);
fetchTile(url, key, callback);
});
};
});
TIP
关于瓦片缓存的key这里我用了图层的id,版本号和瓦片的行列号组成唯一的key
这里只是作为示例使用,自己的业务里请更具需要选择合适 的可以生成方法,版本控制还是比较重要的,当瓦片更新了我们可以通过版本控制的
当然你也可以使用url来作为key,且url里携带版本号,只是url一般都比较长,用其作为key会导致存储的数据体积比较大
- 完整代码
js
var dataStore = localforage.createInstance({
name: "maptalks-layer-data-store",
});
const baseLayer = new maptalks.TileLayer("base", {
urlTemplate:
"https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ["a", "b", "c", "d"],
maxAvailableZoom: 18,
version: 1,
});
baseLayer.on("renderercreate", function (e) {
//load tile image
// img(Image): an Image object
// url(String): the url of the tile
function copyImage(image) {
return createImageBitmap(image);
}
function fetchTile(url, key, callback) {
fetch(url)
.then((res) => res.blob())
.then((blob) => createImageBitmap(blob))
.then((image) => {
callback(image);
copyImage(image).then((imageBit) => {
dataStore.setItem(key, imageBit);
});
})
.catch((error) => {
console.error(error);
//do some things
});
}
e.renderer.loadTileBitmap = function (url, tile, callback) {
const { x, y, z } = tile;
const id = baseLayer.getId();
const version = baseLayer.options.version;
const key = `layer_${id}_${version}_${x}_${y}_${z}`;
dataStore
.getItem(key)
.then((image) => {
if (image) {
console.log("cache hit,key:", key);
copyImage(image).then((imageBit) => {
callback(imageBit);
});
} else {
fetchTile(url, key, callback);
}
})
.catch((error) => {
console.error(error);
fetchTile(url, key, callback);
});
};
});
WARNING
ImageBitmap的保存和返回我们应该是副本,即Copy,因为TileLayer里当一个瓦片不被使用了会将资源销毁的,如果我们不使用副本, 一旦资源被TileLayer内部销毁了,这个资源就不在能使用了,会导致出错了
打开例子,然后刷新页面,看看瓦片值请求一次,后续都会走本地的缓存的
总结
这里只是用TileLayer瓦片的前端缓存做个引子,其他的图层数据你可以照葫芦画瓢,尤其是模型的数据非常适合
WARNING
- 缓存是个好东西,但是不是没有代价的,数据一致性是非常令人头疼的问题,所以不要随便滥用,根据自己的业务需要看看是否需要, 尤其是版本号和缓存key的策略很重要的,一定要小心
- 例子的代码没有考虑浏览器兼容性,所以使用时要考虑到IndexedDB和ImageBitMap的兼容性判断