import RBush from "rbush";
import { readEncWkt_4326 } from "@/utils/geoUtil";
import { allLayers, totalLoadLayers } from "@/utils/layerUtil";
import SphericalMercator from "@mapbox/sphericalmercator";
import request from "@/utils/request";
import { transformExtent } from "ol/proj";
import { openDB, deleteDB } from 'idb';
import { isEqArrays } from '@/utils/index';
let globalIndexDB;
export const MapTileMixin = {
computed: {
},
data() {
return {
rtree: undefined,
rectangles: [],
tiles: {},
moveTiles: [],
queryTiles: [],
lastQueryTiles:[],
loadTiles: [],
maptiles: {},
mapDataDbName: "interiorDataDbName-" + this.internalPackageId
}
},
created() {
this.rtree = new RBush();
},
methods: {
deleteIndexDB() {
deleteDB(this.mapDataDbName, {
blocked() {
try {
if(globalIndexDB){
globalIndexDB.close();
}
} catch (error) {
console.info("db删除完毕",e);
}
}
});
},
initDB(cb) {
this.deleteIndexDB();
const dbPromise = openDB(
this.mapDataDbName,
undefined,
{
upgrade(db) {
//创建表,每个矢量图层一张表
allLayers.forEach(layerCode => {
db.createObjectStore(layerCode, { keyPath: 'id' });
});
}
}
);
dbPromise.then((db) => {
globalIndexDB = db;
}).finally(()=>{
cb();
});
},
clearTilesAll() {
this.maptiles = {};
this.mapObj.getLayers().getArray().forEach(layer => {
if (layer.getSource && layer.getSource() && layer.getSource().clear) {
layer.getSource().clear();
}
})
},
clearTilesByLayerCode(layerCode) {
this.clearCacheTile(layerCode);
this.mapObj.getLayers().getArray().forEach(layer => {
if (layer.getProperties().code == layerCode) {
if (layer.getSource && layer.getSource() && layer.getSource().getFeatures) {
console.error("当前缓存清除图层:" + layerCode + " 矢量元素数量:" + layer.getSource().getFeatures().length);
}
layer.getSource().clear();
}
})
},
clearCacheTile(layerCode){
let _this = this;
Object.keys(this.maptiles).forEach((key) => {
if (key.split("-")[0] == layerCode) {
delete _this.maptiles[key];
}
})
},
clearTileByCoord(coord) {
let _this = this;
Object.keys(this.maptiles).forEach((key) => {
let bbox = _this.maptiles[key];
if (coord[0] >= bbox[0] && coord[0] <= bbox[2]
&& coord[1] >= bbox[1] && coord[1] <= bbox[3]) {
delete _this.maptiles[key];
}
})
},
clearTileAndDbByExtent(layerCodes) {
let _this = this;
layerCodes.forEach(async code => {
for (let key in _this.maptiles) {
let tileId = key.split('-')[1] + "-" + key.split('-')[2] + "-" + key.split('-')[3];
if (_this.queryTiles.indexOf(tileId) > -1) {
delete _this.maptiles[key];
globalIndexDB.delete(code, tileId);
}
}
})
},
clearTreeAndDbByCode(layerObjArr) {
new Promise(r => {
this.rtree.all().forEach(async item => {
for (let i = 0, len = layerObjArr.length; i < len; i++) {
if (layerObjArr[i].layerCode == item.storeName) {
this.rtree.remove(item);
const storeData = await globalIndexDB.getAll(item.storeName);
for (let d of storeData) {
await globalIndexDB.delete(item.storeName, d.id);
}
break;
}
}
})
})
},
updateTreeAndDbData() {
new Promise(r => {
this.rtree.all().forEach(async item => {
for (let i = 0, len = layerObjArr.length; i < len; i++) {
if (layerObjArr[i].layerCode == item.storeName) {
this.rtree.remove(item);
const storeData = await globalIndexDB.getAll(item.storeName);
for (let d of storeData) {
await globalIndexDB.delete(item.storeName, d.id);
}
break;
}
}
})
})
},
tilesByLyerCodeOutMax(layerCodes, Max) {
let _this = this;
layerCodes.forEach(code => {
let cacheLayerData = _this.getTilesCacheCountByLayerCode(code);
if (cacheLayerData.tileCacheNum > Max) {
console.error("当前缓存清除图层:" + code + " 瓦片数量:" + cacheLayerData.tileCacheNum);
_this.clearTilesByLayerCode(code);
}
})
},
getTilesCacheCountByLayerCode(layerCode) {
let cacheLayerData = {
tileCacheNum: 0,
cacheDataNum: 0
}
Object.keys(this.maptiles).forEach(key => {
if (key.split("-")[0] == layerCode) {
cacheLayerData.tileCacheNum = cacheLayerData.tileCacheNum + 1;
}
})
this.mapObj.getLayers().getArray().forEach(layer => {
if (layer.getProperties().code == layerCode) {
cacheLayerData.cacheDataNum = layer.getSource().getFeatures().length;
}
})
return cacheLayerData;
},
isQueryLayerData(layerInfo) {
let isQuery = true;
if (layerInfo.layerCode) {
let layers = this.mapObj.getLayers().getArray();
let count = 0;
for (let k = 0, l = layerInfo.layerCode.length; k < l; k++) {
for (let i = 0, len = layers.length; i < len; i++) {
if (layerInfo.layerCode[k] == layers[i].getProperties().code && !layers[i].getVisible()) {
count = count + 1;
}
}
}
if (count == layerInfo.layerCode.length) {
isQuery = false;
}
}
return isQuery;
},
getLoadTiles() {
// 设置缩放小于15的时候不加载
const zoom = Math.floor(this.mapObj.getView().getZoom());
if (zoom < 18) {
this.queryTiles = []
this.lastQueryTiles = []
return
}
this.lastQueryTiles = this.queryTiles
this.queryTiles = this.getTilesByZoom(17);
},
// 根据zoom和bounds获取屏幕范围内的网格
getTilesByZoom(queryZoom) {
let mapBounds = transformExtent(this.mapObj.getView().calculateExtent(), "EPSG:3857", "EPSG:4326");
const southWest = [mapBounds[0], mapBounds[1]]
const northEast = [mapBounds[2], mapBounds[3]]
// -1为了使网格范围大,请求次数减少
const swTiles = this.queryTilesByLatlng(southWest, queryZoom);
const neTiles = this.queryTilesByLatlng(northEast, queryZoom);
const tileNos = this.containTiles(swTiles, neTiles, queryZoom);
return tileNos
},
/*
* 根据坐标和缩放级别获取所在瓦片
* 【param】 latlng 坐标
* 【param】 zoom 缩放级别
* return
*/
queryTilesByLatlng(latlng, zoom) {
const p = this.project(latlng)
const scale1 = this.scale(zoom)
const _a = 0.5 / (Math.PI * 6378137)
const _b = 0.5
const _c = -0.5 / (Math.PI * 6378137)
const _d = 0.5
const x = scale1 * (_a * p[0] + _b)
const y = scale1 * (_c * p[1] + _d)
const tileX = Math.floor(x / 256)
const tileY = Math.floor(y / 256)
return [tileX, tileY]
},
project(latlng) {
const [x, y] = latlng
const R = 6378137
const MAX_LATITUDE = 85.0511287798
const d = Math.PI / 180
const max = MAX_LATITUDE
const lat = Math.max(Math.min(max, y), -max)
const sin = Math.sin(lat * d)
return [R * x * d, (R * Math.log((1 + sin) / (1 - sin))) / 2]
},
scale(zoom) {
return 256 * Math.pow(2, zoom)
},
/*
* 计算屏幕范围所覆盖所有瓦片
* 【param】 lb 左下瓦片号
* 【param】 rt 右上瓦片号
* return
*/
containTiles(lb, rt, zoom) {
const [l, b] = lb
const [r, t] = rt
const tiles = []
let dx = l
while (dx <= r) {
let dy = b
while (dy >= t) {
tiles.push(dx + '-' + dy + '-' + zoom)
dy--
}
dx++
}
return tiles
},
/**
* tile 转 BBOX
* @param {*} x
* @param {*} y
* @param {*} z
*/
zxyToBbox(x, y, z) {
let merc = new SphericalMercator({
size: 256
});
let bbox = merc.bbox(x, y, z, false, "WGS84")
return {
// bbox: transformExtent(bbox, "EPSG:3857", "EPSG:4326"),
bbox: bbox,
id: x + "-" + y + "-" + z
}
},
totalLoadData(url, requestParams, layerInfo, storeName) {
// 图层主键字段,用于存indexDB库时作主键和存rtree的关键字段
const { layerPrimaryKey, layerGeometry, layerBbox } = layerInfo;
return new Promise(async resolve => {
let dataList;
// 若indexDB有当前图层的数据,则直接获取并装载rtree
// 每次挪动地图触发TileLoadLayer时走这个逻辑
const totalData = await globalIndexDB.getAll(storeName);
if (totalData && totalData.length) {
// 取出所有的表内真实数据
dataList = totalData.map(v => v.data);
// 计算每条数据的extend并装载rtree
let insertions = [];
dataList.forEach((entity) => {
// 计算每条数据的extend并装载rtree
const extend = this.entityExtent(entity, layerGeometry);
extend && insertions.push({
...extend,
storeName,
id: entity[layerPrimaryKey]
})
});
if (insertions.length > 0) {
this.rtree.load(insertions);
}
resolve(dataList);
return
}
// 全量请求internalPackageId所有数据,所以范围传全球[-180, -90, 180, 90]
// 后续可考虑后端增加参数ignoreBbox来处理达到同样的效果
const tile = {
bbox: layerBbox || [-180, -90, 180, 90]
}
this.loadData(url, tile, requestParams, layerInfo.isPost)
.then(async ({ data }) => {
dataList = data;
let insertions = [];
for (let i = 0; i < dataList.length; i++) {
let entity = dataList[i];
// 将加载的数据逐条存入indexDB
// TODO 可以考虑在这里创建索引
await globalIndexDB.put(storeName, {
id: entity[layerPrimaryKey],
data: entity
});
// 计算每条数据的extend并装载rtree
const extend = this.entityExtent(entity, layerGeometry);
extend && insertions.push({
...extend,
storeName,
id: entity[layerPrimaryKey]
})
}
if (insertions.length > 0) {
this.rtree.load(insertions);
}
resolve(dataList)
})
})
},
totalInMapViewData(layerInfo, storeName) {
const mapObj = this.mapObj;
const { layerPrimaryKey } = layerInfo;
const extent = transformExtent(mapObj.getView().calculateExtent(mapObj.getSize()), "EPSG:3857", "EPSG:4326")
return new Promise(async resolve => {
const result = [];
// 根据地图的extent从rtree计算数据的主键列表
const viewData = this.intersects(extent).filter(item => item.storeName === storeName);
for (let item of viewData) {
// 根据数据的主键列表,从indexDB读出所有数据
const { data } = await globalIndexDB.get(storeName, item[layerPrimaryKey]) || {};
data && result.push(data)
}
resolve({
data: result
});
})
},
handleTreeData(dataList, layerInfo) {
const { layerCode, layerFilter, beforeProcess, postProcess } = layerInfo
return new Promise(async resolve => {
// 返回对象
const result = {};
// 前置处理
beforeProcess ? beforeProcess(dataList) : null;
// 遍历图层处理:
for (const code of layerCode) {
// 将数据由图层过滤后,组装到返回对象和存储对象内
result[`${code}List`] = [];
dataList.forEach((data) => {
const filter = (layerFilter || {})[code];
const layerData = filter ? filter(data.data || []) : data.data;
result[`${code}List`] = result[`${code}List`].concat(...layerData)
});
}
// 后置处理
postProcess ? postProcess(dataList) : null;
resolve(result);
})
},
totalLoadLayer(url, requestParams, layerInfo, cb) {
// 库的名字,注意不能使用LayerCode.join(','),因为初始创建都是用单个code创建的
// 所以这里可以固定取第一个code即可
const storeName = layerInfo.layerCode[0];
let promise;
// 先尝试rtree内是否有数据,如果有,则不需要请求及装载
const hasData = this.rtree.all().findIndex(x => x.storeName === storeName);
if (hasData < 0) {
// 如果rtree内没有数据,则加载全量一次数据
promise = this.totalLoadData(url, requestParams, layerInfo, storeName);
} else {
// 如果rtree没有数据,则跳过
promise = new Promise(resolve => setTimeout(() => { resolve() }));
}
// 确保rtree内有数据后,获取当前视图范围内数据
promise
.then(data => {
// 当前地图视图范围,过滤出需要渲染的数据
return this.totalInMapViewData(layerInfo, storeName)
})
.then((data) => {
// 对数据做分图层处理
return this.handleTreeData([data], layerInfo)
})
.then((data) => {
this.mapObj.getLayers().getArray().forEach(layer => {
if (layerInfo.layerCode.indexOf(layer.getProperties().code) > -1) {
layer.getSource().clear();
}
})
cb(data, layerInfo)
})
.catch(ignore => { })
},
/**
* 矢量数据请求
* @param {} extent
* @param {*} url
* @param {*} requestParams
* @param {*} layerInfo
* @param {*} cb
*/
TileLoadLayer(url, requestParams, layerInfo, cb) {
// if(layerInfo.layerCode[0] != 'road'){return}
let _this = this;
if (!this.isQueryLayerData(layerInfo)) {
return;
}
// 配置全量请求图层,且有内包ID时,一次请求整包数据
const isTotalLayer = totalLoadLayers.findIndex(v => isEqArrays(v, layerInfo.layerCode));
if (requestParams.internalPackageId && isTotalLayer > -1) {
this.totalLoadLayer(url, requestParams, layerInfo, cb);
return;
}
let newTileNos = [];
this.queryTiles.forEach(newTile => {
let xyz = newTile.split("-");
let tile = _this.zxyToBbox(Number(xyz[0]), Number(xyz[1]), Number(xyz[2]));
let count = 0;
for (let i = 0, len = layerInfo.layerCode.length; i < len; i++) {
let code = layerInfo.layerCode[i];
if (!_this.maptiles[code + "-" + newTile]) {
_this.maptiles[code + "-" + newTile] = tile.bbox;
count = count + 1;
}
}
if (count == layerInfo.layerCode.length) {
newTileNos.push(tile);
}
})
if (newTileNos.length == 0) {
// console.error("开始读取数据库缓存数据");
// console.log(this.existChangeTile())
if(this.existChangeTile()){
this.getCacheDataList(this.queryTiles,layerInfo.layerCode).then(result=>{
cb(result, layerInfo)
})
}
return;
}
Promise.all(
newTileNos.map(tile =>
this.loadData(url, tile, requestParams, layerInfo.isPost)
)
).then(resArr => {
let dataList = [];
for (let idx = 0; idx < resArr.length; idx++) {
dataList.push(
{
id: newTileNos[idx].id,
data: resArr[idx].data
}
);
}
return this.handleData(dataList, layerInfo, false)
}).then(() => {
this.getCacheDataList(this.queryTiles,layerInfo.layerCode).then(result=>{
cb(result, layerInfo)
})
}).catch(function (err) {
newTileNos.map(tile => {
layerInfo.layerCode.forEach(code => {
if (_this.maptiles[code + tile.id]) {
delete _this.maptiles[code + tile.id];
}
})
}
)
});
},
existChangeTile(){
return this.queryTiles.find(q=>!this.lastQueryTiles.find(l=>l ==q))
},
async getCacheDataList(tiles,layerCodes){
let result = {}
for (const code of layerCodes) {
// ①将数据由图层过滤后,组装到返回对象和存储对象内
result[`${code}List`] = [];
// ①将组装的每个瓦片数据存储到indexDB,并获取所有应该查询瓦片数据
if (globalIndexDB.objectStoreNames.contains(code)) {
for (const tileId of tiles) {
try {
const row = await globalIndexDB.get(code, tileId)
if (row && row.data) {
result[`${code}List`] = result[`${code}List`].concat(row.data)
}
} catch (error) {
console.error(code + "---------------------");
console.error(tileId);
}
}
}
}
return result;
},
loadData(url, tile, requestParams, isPost) {
requestParams = requestParams || {};
let params = {};
params.swLng = tile.bbox[0].toFixed(6);
params.neLng = tile.bbox[2].toFixed(6);
params.swLat = tile.bbox[1].toFixed(6);
params.neLat = tile.bbox[3].toFixed(6);
params = Object.assign({}, params, requestParams);
if (isPost) {
return request({
url,
data: params,
method: "post"
});
}
return request({
url,
params: params
});
},
handleData(dataList, layerInfo, isQueryTilesAll) {
const { layerCode, layerFilter, beforeProcess, postProcess } = layerInfo
return new Promise(async resolve => {
// 返回对象
const result = {};
// 存储对象
const tables = {};
// 前置处理
beforeProcess ? beforeProcess(dataList) : null;
// 遍历图层处理:
for (const code of layerCode) {
// ①将数据由图层过滤后,组装到返回对象和存储对象内
result[`${code}List`] = [];
tables[`${code}`] = [];
dataList.forEach((data) => {
const filter = (layerFilter || {})[code];
const layerData = filter ? filter(data.data || []) : data.data;
tables[`${code}`].push({
id: data.id,
data: layerData
});
});
// ①将组装的每个瓦片数据存储到indexDB,并获取所有应该查询瓦片数据
if (globalIndexDB.objectStoreNames.contains(code)) {
for (const tile of tables[code]) {
await globalIndexDB.put(code, tile)
}
if (isQueryTilesAll) {
for (const tileId of this.queryTiles) {
try {
const row = await globalIndexDB.get(code, tileId)
if (row) {
result[`${code}List`] = result[`${code}List`].concat(row.data)
}
} catch (error) {
console.error(code + "---------------------");
console.error(tileId);
}
}
} else {
for (const tile of tables[code]) {
result[`${code}List`] = result[`${code}List`].concat(tile.data)
}
}
}
}
// 后置处理
postProcess ? postProcess(dataList) : null;
resolve(result);
})
},
entityExtent(entity, layerGeometry) {
const { type, field } = layerGeometry;
let extent = [];
switch (type) {
// 如果是点数据,直接取点坐标,且extent就是本身这个点的范围
case 'Point':
extent = [
Number(entity[field[0]]),
Number(entity[field[1]]),
Number(entity[field[0]]),
Number(entity[field[1]]),
];
break;
// 如果是线或者面数据,直接取对应的wkt字段,并计算extent
case 'Polyline':
case 'Polygon':
let geom = entity[field];
if (geom) {
extent = readEncWkt_4326(geom).getGeometry().getExtent();
}
break;
default:
break;
}
// 如果计算失败,返回空,针对null则不装载rtree即可
if (extent.length < 4) {
return null
}
return {
minX: extent[0],
minY: extent[1],
maxX: extent[2],
maxY: extent[3]
}
},
intersects(extent) {
const bbox = {
minX: extent[0],
minY: extent[1],
maxX: extent[2],
maxY: extent[3]
};
return this.rtree.search(bbox);
},
},
watch: {
}
}