UNPKG

@vuemap/amap-xyz-layer

Version:

高德地图 JSAPI v2.0 自定义瓦片图层,支持瓦片纠偏,支持海拔

923 lines (827 loc) 34.8 kB
import earcut from 'earcut'; import {Matrix4} from "@math.gl/core"; import { lonLatToTileNumbers, tileNumbersToLonLat, gcj02_To_gps84, gps84_To_gcj02, gcj02_To_bd09, bd09_To_gcj02 } from '../support/coordConver' import TransformClassBaidu from '../support/transform-class-baidu' import {template} from '../support/Util.js' import type { ResultLngLat } from '../support/coordConver' // 掩膜数据结构 type MaskType = number[][] | number[][][] | number[][][][] export interface XyzLayerOptions{ url: string subdomains?: string[] tileType?: 'xyz' | 'bd09' proj?: 'wgs84' | 'gcj02' | 'bd09' zooms?: [number, number] opacity?: number visible?: boolean zIndex?: number debug?: boolean mask?: MaskType cacheSize?: number tileMaxZoom?: number // 瓦片在服务器的最大层级,当地图zoom超过该层级后直接使用该层级作为做大层级瓦片 altitude?: number // 瓦片海拔 } interface XYZ { x: number y: number z: number } interface PosParamType { size: number stride: number offset: number } interface TileType { xyz: XYZ xyzKey: string buffer?: WebGLBuffer PosParam?: PosParamType TextCoordParam?: PosParamType texture?: WebGLTexture isLoad: boolean url: string image?: HTMLImageElement imageCanceled: boolean imageError?: boolean } // mask缓存数据 interface MaskCacheType { FSIZE: number vertexBuffer: WebGLBuffer indexBuffer: WebGLBuffer deviationLength: number } class CustomXyzLayer { options: XyzLayerOptions map: any // 地图对象 gl: any layer: any customCoords: any center: any //着色器程序 program = null as any; //存放当前显示的瓦片 showTiles: TileType[] = []; isReadRender = false; //记录当前图层是否在显示 isLayerShow = false; //存放所有加载过的瓦片 tileCache: TileType[] = []; //存放瓦片号对应的经纬度 gridCache: Record<string, ResultLngLat> = {} transformBaidu = new TransformClassBaidu() a_Pos: GLint | undefined; a_TextCoord: GLint | undefined mapCallback: any maskCache: MaskCacheType[] = [] // 掩膜的着色器程序 maskProgram: any mask_Pos: GLint | undefined; // 以下两个为透视投影使用矩阵 // 投影矩阵 _projectionMatrix = new Matrix4() // 视图矩阵 _viewMatrix4 = new Matrix4(); // 正交投影使用 _mvpMatrix4 = new Matrix4(); // 是否是2D的正交投影 isOrtho = false; constructor(map: any, options: XyzLayerOptions) { if (!map) { throw new Error('请传入地图实例') } this.validate(options); this.map = map this.center = map.getCenter().toArray(); this.options = Object.assign(this.getDefaultGlLayerOptions(), options); this.customCoords = map.customCoords; // 数据使用转换工具进行转换,这个操作必须要提前执行(在获取镜头参数 函数之前执行),否则将会获得一个错误信息。 this.customCoords.lngLatsToCoords([ this.center ]); this.layer = new AMap.GLCustomLayer({ // 图层的层级 zooms: this.options.zooms, opacity: this.options.opacity, visible: this.options.visible, zIndex: this.options.zIndex, // 初始化的操作,创建图层过程中执行一次。 init: (gl) => { this.gl = gl; const vertexSource = ` uniform mat4 u_ProjectionMatrix; uniform mat4 u_ViewMatrix4; uniform mat4 u_MvpMatrix4; uniform bool u_isOrtho; attribute vec2 a_pos; attribute vec2 a_TextCoord; varying vec2 v_TextCoord; void main() { if(u_isOrtho){ gl_Position = u_MvpMatrix4 * vec4(a_pos,0.0, 1.0); }else{ gl_Position = u_ProjectionMatrix * u_ViewMatrix4 * vec4(a_pos,0.0, 1.0); } v_TextCoord = a_TextCoord; }`; const fragmentSource = ` precision mediump float; uniform sampler2D u_Sampler; uniform bool u_isFirst; varying vec2 v_TextCoord; void main() { if(u_isFirst){ gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0); }else{ gl_FragColor = texture2D(u_Sampler, v_TextCoord); } }`; //初始化顶点着色器 const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, vertexSource); gl.compileShader(vertexShader); //初始化片元着色器 const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); gl.compileShader(fragmentShader); //初始化着色器程序 this.program = gl.createProgram(); gl.attachShader(this.program, vertexShader); gl.attachShader(this.program, fragmentShader); gl.linkProgram(this.program); //获取顶点位置变量 this.a_Pos = gl.getAttribLocation(this.program, "a_pos"); this.a_TextCoord = gl.getAttribLocation(this.program, 'a_TextCoord'); //掩膜处理 const maskVertexSource = "" + "uniform mat4 u_ProjectionMatrix;" + "uniform mat4 u_ViewMatrix4;" + "uniform mat4 u_MvpMatrix4;" + "uniform bool u_isOrtho;"+ "attribute vec2 a_pos;" + "void main() {" + " if(u_isOrtho){"+ " gl_Position = u_MvpMatrix4 * vec4(a_pos,0.0, 1.0);" + " }else{"+ " gl_Position = u_ProjectionMatrix * u_ViewMatrix4 * vec4(a_pos,0.0, 1.0);" + " }"+ "}"; const maskFragmentSource = "" + "void main() {" + " gl_FragColor = vec4(0.0, 1.0, 0.0, 0.0);" + "}"; //初始化掩膜顶点着色器 const maskVertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(maskVertexShader, maskVertexSource); gl.compileShader(maskVertexShader); //初始化掩膜片元着色器 const maskFragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(maskFragmentShader, maskFragmentSource); gl.compileShader(maskFragmentShader); //初始化着色器程序 this.maskProgram = gl.createProgram(); gl.attachShader(this.maskProgram, maskVertexShader); gl.attachShader(this.maskProgram, maskFragmentShader); gl.linkProgram(this.maskProgram); //获取顶点位置变量 this.mask_Pos = gl.getAttribLocation(this.maskProgram, "a_pos"); if(this.options.visible){ this.isLayerShow = true; } this.mapCallback = () => { if (this.isLayerShow) { this.update() } } map.on('dragging', this.mapCallback) map.on('moveend', this.mapCallback) map.on('zoomchange', this.mapCallback) map.on('rotatechange', this.mapCallback) this._createMask(this.options.mask); this.update() }, render: (gl) => { if(!this.isReadRender){ return; } const zooms = this.options.zooms as [number, number]; if (this.map.getZoom() < zooms[0] || this.map.getZoom() > zooms[1]) { return } this.customCoords.setCenter(this.center); if (map.getView().type === '3D') { this.isOrtho = false; const {near, far, fov, up, lookAt, position} = this.customCoords.getCameraParams() as { near: number; far: number; fov: number; up: [number, number, number]; lookAt: [number, number, number]; position: [number, number, number] }; this._viewMatrix4.lookAt({ eye: position, center: lookAt, up }).translate([0,0,this.options.altitude as number]); this._projectionMatrix.perspective({ fovy: fov * Math.PI / 180, far, near, aspect: gl.drawingBufferWidth / gl.drawingBufferHeight }) }else{ this.isOrtho = true; const MVPMatrix = this.customCoords.getMVPMatrix(); this._mvpMatrix4.copy(MVPMatrix); } if(this.maskCache.length > 0){ // 清除模板缓存 gl.clearStencil(0); gl.clear(gl.STENCIL_BUFFER_BIT); // 开启模板测试 gl.enable(gl.STENCIL_TEST); // 设置模板测试参数 gl.stencilFunc(gl.ALWAYS, 1, 0xFF); gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); this._renderMask(gl); // ----- 模板方法 begin ----- //设置模板测试参数 gl.stencilFunc(gl.EQUAL, 1, 0xFF); //设置模板测试后的操作 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); // ----- 模板方法 end ----- // 关闭深度检测 gl.disable(gl.DEPTH_TEST); this._renderTile(gl); // 开启深度检测 gl.enable(gl.DEPTH_TEST); // ----- 模板方法 begin ----- // 关闭模板测试 gl.disable(gl.STENCIL_TEST); }else{ this._renderTile(gl); } }, }); map.add(this.layer); } _renderMask(gl){ if(!this.maskCache.length){ return; } this.customCoords.setCenter(this.center); //应用着色程序 //必须写到这里,不能写到onAdd中,不然gl中的着色程序可能不是上面写的,会导致下面的变量获取不到 gl.useProgram(this.maskProgram); // 设置位置的顶点参数 this.setVertex(gl, this.maskProgram) for (const mask of this.maskCache) { gl.bindBuffer(gl.ARRAY_BUFFER, mask.vertexBuffer); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, mask.indexBuffer); //激活顶点数据缓冲区 gl.vertexAttribPointer(this.mask_Pos as GLint, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(this.mask_Pos as GLint); //绘制图形 gl.drawElements(gl.TRIANGLES, mask.deviationLength, gl.UNSIGNED_INT, 0); } // gl.drawElements(gl.TRIANGLES, this.maskCache.deviationLength, gl.UNSIGNED_INT, 0); } _renderTile(gl) { this.customCoords.setCenter(this.center); //应用着色程序 //必须写到这里,不能写到onAdd中,不然gl中的着色程序可能不是上面写的,会导致下面的变量获取不到 gl.useProgram(this.program); // 设置位置的顶点参数 this.setVertex(gl, this.program) let index = 0; for (const tile of this.showTiles) { if (!tile.isLoad || tile.imageError) continue; //向target绑定纹理对象 gl.bindTexture(gl.TEXTURE_2D, tile.texture as WebGLTexture); //开启0号纹理单元 gl.activeTexture(gl.TEXTURE0); //配置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT); // 获取纹理的存储位置 // eslint-disable-next-line camelcase const u_Sampler = gl.getUniformLocation(this.program, 'u_Sampler'); //将0号纹理传递给着色器 gl.uniform1i(u_Sampler, 0); // 设置是否是第一个瓦片,如果是第一个瓦片就显示为透明 gl.uniform1f(gl.getUniformLocation(this.program, "u_isFirst"), index === 0); gl.bindBuffer(gl.ARRAY_BUFFER, tile.buffer as WebGLBuffer); //设置从缓冲区获取顶点数据的规则 gl.vertexAttribPointer(this.a_Pos, tile.PosParam?.size, gl.FLOAT, false, tile.PosParam?.stride, tile.PosParam?.offset); gl.vertexAttribPointer(this.a_TextCoord, tile.TextCoordParam?.size, gl.FLOAT, false, tile.TextCoordParam?.stride, tile.TextCoordParam?.offset); //激活顶点数据缓冲区 gl.enableVertexAttribArray(this.a_Pos); gl.enableVertexAttribArray(this.a_TextCoord); //开启阿尔法混合,实现注记半透明效果 gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); //绘制图形 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); index++; } } validate(options: XyzLayerOptions) { if (!options.url) { throw new Error('请传入url'); } if (options.url.includes('{s}') && (!options.subdomains || options.subdomains.length === 0)) { throw new Error('请传入subdomains'); } } getDefaultGlLayerOptions(): XyzLayerOptions { return { url: '', zooms: [2, 18], opacity: 1, visible: true, zIndex: 120, proj: 'gcj02', tileType: 'xyz', debug: false, cacheSize: 512, tileMaxZoom: 18, altitude: 0 } } _createMask(mask?: MaskType) { if(!mask || mask.length === 0){ this.maskCache = []; return } const deep = this.getMaskDeep(mask); if(deep < 2 || deep>4){ // eslint-disable-next-line no-console console.warn('mask数据格式不正确') return; } if(deep === 2){ mask = [[mask]] as any; }else if(deep === 3){ mask = [mask] as any; } for(let maskItem of mask as number[][][][]){ maskItem = this._convertMaskLnglatToCoords(maskItem); const data = earcut.flatten(maskItem); // console.log('earcut: ', earcut) const triangles = earcut(data.vertices, data.holes, data.dimensions); const gl = this.gl; //创建顶点缓冲区对象 const vertexArray = new Float32Array(data.vertices); const vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertexArray , gl.STATIC_DRAW); gl.bindBuffer(gl.ARRAY_BUFFER, null); // 创建索引缓冲区对象 const indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(triangles), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); this.maskCache.push({ FSIZE: vertexArray.BYTES_PER_ELEMENT, vertexBuffer, indexBuffer, deviationLength: triangles.length }) } } getMaskDeep(mask: any, deepNumber = 1): number { if(!mask.length){ return -1; } if(typeof mask[0] === 'number'){ return deepNumber; } return this.getMaskDeep(mask[0], deepNumber+1); } _convertMaskLnglatToCoords(mask: any){ if(!mask || mask.length === 0){ return mask; } if(typeof mask[0] === 'number'){ return this._convertLnglatToCoords([mask[0], mask[1]]); } return mask.map(item => this._convertMaskLnglatToCoords(item)) } _convertLnglatToCoords(lnglat: [number, number]) { this.customCoords.setCenter(this.center); return this.customCoords.lngLatsToCoords([lnglat])[0]; } update() { if(!this.gl){ return; } this.isReadRender = false; const gl = this.gl; const map = this.map; const center = map.getCenter(); let zoom = Math.ceil(map.getZoom()); if(zoom > (this.options.tileMaxZoom as number)){ zoom = this.options.tileMaxZoom as number; } const bounds = map.getBounds(); let minTile: [number, number], maxTile: [number, number]; let northWestLngLat: [number, number]; let sourceEastLngLat: [number, number]; if (this.options.tileType === 'xyz') { // zoom = parseInt(map.getZoom() + 1.4); //解决瓦片上文字偏大的问题 northWestLngLat = bounds.getNorthWest().toArray() as [number, number]; sourceEastLngLat = bounds.getSouthEast().toArray() as [number, number]; } else { // zoom = parseInt(map.getZoom() + 1.8); //解决瓦片上文字偏大的问题 northWestLngLat = bounds.getSouthWest().toArray() as [number, number]; sourceEastLngLat = bounds.getNorthEast().toArray() as [number, number]; } if (this.options.proj === 'wgs84') { //把当前显示范围做偏移,后面加载瓦片时会再偏移回来 //如果不这样做的话,大比例尺时,瓦片偏移后,屏幕边缘会有空白区域 const northWest = gcj02_To_gps84(...northWestLngLat) const southEast = gcj02_To_gps84(...sourceEastLngLat) //算出当前范围的瓦片编号 minTile = lonLatToTileNumbers(northWest.lng, northWest.lat, zoom) maxTile = lonLatToTileNumbers(southEast.lng, southEast.lat, zoom) } else if (this.options.proj === 'bd09') { // zoom = parseInt(map.getZoom() + 1.8); //解决瓦片上文字偏大的问题 const southWest = gcj02_To_bd09(...northWestLngLat) const northEast = gcj02_To_bd09(...sourceEastLngLat) minTile = this.transformBaidu.lnglatToTile(southWest.lng, southWest.lat, zoom) maxTile = this.transformBaidu.lnglatToTile(northEast.lng, northEast.lat, zoom) } else { minTile = lonLatToTileNumbers(northWestLngLat[0], northWestLngLat[1], zoom) maxTile = lonLatToTileNumbers(sourceEastLngLat[0], sourceEastLngLat[1], zoom) } const currentTiles: XYZ[] = []; for (let x = minTile[0]; x <= maxTile[0]; x++) { for (let y = minTile[1]; y <= maxTile[1]; y++) { const xyz: XYZ = { x, y, z: zoom } currentTiles.push(xyz) //把瓦片号对应的经纬度缓存起来, //存起来是因为贴纹理时需要瓦片4个角的经纬度,这样可以避免重复计算 //行和列向外多计算一个瓦片数,这样保证瓦片4个角都有经纬度可以取到 this.addGridCache(xyz, 0, 0) if (x === maxTile[0]) { this.addGridCache(xyz, 1, 0) } if (y === maxTile[1]) { this.addGridCache(xyz, 0, 1) } if (x === maxTile[0] && y === maxTile[1]) { this.addGridCache(xyz, 1, 1) } } } let centerTile: [number, number] //瓦片设置为从中间向周边的排序 if (this.options.tileType === 'xyz') { centerTile = lonLatToTileNumbers(center.getLng(), center.getLat(), zoom) } //计算中心点所在的瓦片号 else{ centerTile = this.transformBaidu.lnglatToTile(center.getLng(), center.getLat(), zoom) } currentTiles.sort((a, b) => { return this.tileDistance(a, centerTile) - this.tileDistance(b, centerTile); }); this._cancelOutViewImage(currentTiles); //加载瓦片 this._clearShowTile(); for (const xyz of currentTiles) { //走缓存或新加载 const tileKey = this.createTileKey(xyz); const tileCache = this.getTileCache(tileKey) if (tileCache) { if(tileCache.imageError){ continue } if(!tileCache.isLoad && tileCache.image && tileCache.imageCanceled){ tileCache.image.src = tileCache.url; tileCache.imageCanceled = false; } this.showTiles.push(tileCache); } else { const tile = this.createTile(gl, xyz) this.showTiles.push(tile); this.pushTileCache(tile); } } if(this.showTiles.length > 0){ this.showTiles.unshift(this.showTiles[0]); } this.isReadRender = true; } _cancelOutViewImage(currentTiles: XYZ[]){ this.tileCache.forEach(tile => { const index = currentTiles.findIndex(xyz => this.createTileKey(xyz) === tile.xyzKey) if(index === -1 && !tile.isLoad){ if(tile.image){ tile.image.src = '' tile.imageCanceled = true; } } }) } getTileCache(key: string){ return this.tileCache.find(item => item.xyzKey === key) } pushTileCache(tile: TileType){ const cacheSize = this.options.cacheSize as number; if(cacheSize > 0 && this.tileCache.length >= cacheSize){ if(this.showTiles.findIndex(item => item.xyzKey === this.tileCache[0].xyzKey) < 0){ this._destroyTile(this.tileCache[0]); } this.tileCache.splice(0, 1); } this.tileCache.push(tile); } //缓存瓦片号对应的经纬度 addGridCache(xyz: XYZ, xPlus: number, yPlus: number) { const key = this.createTileKey(xyz.x + xPlus, xyz.y + yPlus, xyz.z) if (!this.gridCache[key]) { if (this.options.proj === 'wgs84') { const lnglat = gps84_To_gcj02(...tileNumbersToLonLat(xyz.x + xPlus, xyz.y + yPlus, xyz.z)); const result = this._convertLnglatToCoords([lnglat.lng, lnglat.lat]); this.gridCache[key] = { lng: result[0], lat: result[1] } } else if (this.options.tileType === 'bd09') { const lnglat = bd09_To_gcj02(...this.transformBaidu.pixelToLnglat(0, 0, xyz.x + xPlus, xyz.y + yPlus, xyz.z)); const result = this._convertLnglatToCoords([lnglat.lng, lnglat.lat]); this.gridCache[key] = { lng: result[0], lat: result[1] } } else { const lnglat = this._convertLnglatToCoords(tileNumbersToLonLat(xyz.x + xPlus, xyz.y + yPlus, xyz.z)); this.gridCache[key] = { lng: lnglat[0], lat: lnglat[1] } } } } //计算两个瓦片编号的距离 tileDistance(tile1: XYZ, tile2: [number, number]) { //计算直角三角形斜边长度,c(斜边)=√(a²+b²)。(a,b为两直角边) return Math.sqrt(Math.pow((tile1.x - tile2[0]), 2) + Math.pow((tile1.y - tile2[1]), 2)) } //创建瓦片id createTileKey(xyz: XYZ | number, y?: number, z?: number): string { if (xyz instanceof Object) { return `${xyz.z}/${xyz.x}/${xyz.y}`; } else { const x = xyz; return `${z}/${x}/${y}`; } } deepFormatTileNumber(num: number, maxNum: number){ if(num>=0 && num<maxNum){ return num; } if(num>0 && num >= maxNum){ num = num - maxNum; }else if(num < 0){ num = num + maxNum; } return this.deepFormatTileNumber(num, maxNum); } //创建瓦片 createTile(gl, xyz: XYZ) { let zoom = Math.ceil(this.map.getZoom()); if(zoom > (this.options.tileMaxZoom as number)){ zoom = this.options.tileMaxZoom as number; } const maxTileNumber = Math.pow(2, zoom); let x = xyz.x; let y = xyz.y; x = this.deepFormatTileNumber(x, maxTileNumber); y = this.deepFormatTileNumber(y, maxTileNumber); const templateData = { x, y, z: xyz.z } if (this.options.subdomains) { templateData['s'] = this.options.subdomains[Math.abs(xyz.x + xyz.y) % this.options.subdomains.length] } //替换请求地址中的变量 const _url = template(this.options.url, templateData); const tile: TileType = { xyz, xyzKey: this.createTileKey(xyz), isLoad: false, url: _url, imageCanceled: false }; //瓦片编号转经纬度,并进行偏移 let leftTop: ResultLngLat, rightTop: ResultLngLat, leftBottom: ResultLngLat, rightBottom: ResultLngLat; if (this.options.tileType === 'xyz') { leftTop = this.gridCache[this.createTileKey(xyz)] rightTop = this.gridCache[this.createTileKey(xyz.x + 1, xyz.y, xyz.z)] leftBottom = this.gridCache[this.createTileKey(xyz.x, xyz.y + 1, xyz.z)] rightBottom = this.gridCache[this.createTileKey(xyz.x + 1, xyz.y + 1, xyz.z)] } else { leftTop = this.gridCache[this.createTileKey(xyz.x, xyz.y + 1, xyz.z)] rightTop = this.gridCache[this.createTileKey(xyz.x + 1, xyz.y + 1, xyz.z)] leftBottom = this.gridCache[this.createTileKey(xyz)] rightBottom = this.gridCache[this.createTileKey(xyz.x + 1, xyz.y, xyz.z)] } //顶点坐标+纹理坐标 const attrData = new Float32Array([ leftTop.lng, leftTop.lat, 0.0, 1.0, leftBottom.lng, leftBottom.lat, 0.0, 0.0, rightTop.lng, rightTop.lat, 1.0, 1.0, rightBottom.lng, rightBottom.lat, 1.0, 0.0 ]) // console.log('attrData: ', attrData) // var attrData = new Float32Array([ // 116.38967958133532, 39.90811009556515, 0.0, 1.0, // 116.38967958133532, 39.90294980726742, 0.0, 0.0, // 116.39486013141436, 39.90811009556515, 1.0, 1.0, // 116.39486013141436, 39.90294980726742, 1.0, 0.0 // ]) const FSIZE = attrData.BYTES_PER_ELEMENT; //创建缓冲区并传入数据 const buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, attrData, gl.STATIC_DRAW); tile.buffer = buffer; //从缓冲区中获取顶点数据的参数 tile.PosParam = { size: 2, stride: FSIZE * 4, offset: 0 } //从缓冲区中获取纹理数据的参数 tile.TextCoordParam = { size: 2, stride: FSIZE * 4, offset: FSIZE * 2 } //加载瓦片 const img = new Image(256, 256); img.onload = () => { // 创建纹理对象 tile.texture = gl.createTexture(); //向target绑定纹理对象 gl.bindTexture(gl.TEXTURE_2D, tile.texture); //对纹理进行Y轴反转 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); if(this.options.debug){ // 测试瓦片编号 const canvas = document.createElement('canvas'); canvas.width = 256; canvas.height = 256; const cxt = canvas.getContext('2d') as CanvasRenderingContext2D; cxt.drawImage(img,0,0) cxt.font = "25px Verdana"; cxt.fillStyle = "#ff0000"; cxt.strokeStyle = "#FF0000"; cxt.strokeRect(0, 0, 256, 256); cxt.fillText(`(${ [xyz.x, xyz.y, xyz.z].join(',') })`, 10, 30); //配置纹理图像 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas); }else{ //配置纹理图像 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); } tile.isLoad = true; if(this.showTiles.findIndex(item => item === tile) >= 0){ this.requestRender() //主动让地图重绘 } }; img.onerror = () => { if(!tile.imageCanceled){ tile.imageError = true } } img.crossOrigin = 'anonymous'; img.src = _url; tile.image = img; return tile; } // 设置位置的顶点参数 //参考:https://github.com/xiaoiver/custom-mapbox-layer/blob/master/src/layers/PointCloudLayer2.ts setVertex(gl, program: any) { gl.uniformMatrix4fv(gl.getUniformLocation(program, "u_ProjectionMatrix"), false, this._projectionMatrix.toArray()); gl.uniformMatrix4fv(gl.getUniformLocation(program, "u_ViewMatrix4"), false, this._viewMatrix4.toArray()); gl.uniformMatrix4fv(gl.getUniformLocation(program, "u_MvpMatrix4"), false, this._mvpMatrix4.toArray()); gl.uniform1f(gl.getUniformLocation(program, "u_isOrtho"), this.isOrtho); } requestRender(){ if(this.map){ this.map.getContext().setDirty() this.map.render(); } } show(){ this.isLayerShow = true; this.update(); this.layer.show() } hide() { this.isLayerShow = false; this.layer.hide() } getzIndex(): number{ return this.layer.getzIndex(); } setzIndex(zIndex: number){ this.options.zIndex = zIndex; this.layer.setzIndex(zIndex) } getOpacity(): number { return this.layer.getOpacity() } setOpacity(opacity: number){ this.options.opacity = opacity; this.layer.setOpacity(opacity); } getZooms(): [number, number]{ return this.layer.getZooms() } setZooms(zooms: [number, number]){ this.options.zooms = zooms; this.layer.setZooms(zooms) } setMask(mask?: MaskType) { this._destroyMaskCache(); this._createMask(mask); this.options.mask = mask; this.requestRender(); } getMask() { return this.options.mask; } getMap(){ return this.map; } _destroyMaskCache(){ this.maskCache.forEach(item => { this.gl.deleteBuffer(item.vertexBuffer); this.gl.deleteBuffer(item.indexBuffer); }) this.maskCache = []; } _destroyTile(tile: TileType){ if(tile.buffer){ this.gl.deleteBuffer(tile.buffer); } if(tile.texture){ this.gl.deleteTexture(tile.texture); } } _clearAllCacheTile() { this.tileCache.forEach(tile => { this._destroyTile(tile); }) this.tileCache = []; } _clearShowTile() { this.showTiles.forEach(item => { if(this.tileCache.findIndex(cache => cache.xyzKey === item.xyzKey) < 0){ this._destroyTile(item); } }); this.showTiles = []; } destroy() { this.isLayerShow = false; this.map.remove(this.layer); this.map.off('dragging', this.mapCallback); this.map.off('moveend', this.mapCallback) this.map.off('zoomchange', this.mapCallback); this.map.off('rotatechange', this.mapCallback); this._destroyMaskCache(); this._clearShowTile(); this._clearAllCacheTile(); this.gridCache = {}; this.transformBaidu = null as any; this.mapCallback = null; this.gl.deleteProgram(this.program); this.gl.deleteProgram(this.maskProgram); this.program = null; this.maskProgram = null; this.options = undefined as any; this.customCoords = undefined; this.center = undefined; this.layer = null; this.gl = null; this.map = null; } } export {CustomXyzLayer}