/** * 创建者:王成 * 操作系统:MAC * 创建日期:2023年1月30日 * 描述:通过矩形渲染tile,从而达到渲染图片可以浮动在实景三维上面的效果 */ /* 扩展系统Date属性Format 用于格式化日期 yyMMdd HH:ss:mm */ Date.prototype.Format = function(fmt) { // author: meizz var o = { "M+": this.getMonth() + 1, // 月份 "d+": this.getDate(), // 日 "h+": this.getHours(), // 小时 "m+": this.getMinutes(), // 分 "s+": this.getSeconds(), // 秒 "q+": Math.floor((this.getMonth() + 3) / 3), // 季度 "S": this.getMilliseconds() // 毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (( "00" + o[k]).substr(("" + o[k]).length))); return fmt; } /* 引入Cesium */ // import * as Cesium from 'cesium'; // import Worker from '../../../public/worker/download.js?worker'; /* 创建类 */ class CrImageServerLayer { /** * 构造函数 * @param {Cesium.Viewer} options.viewer 地图视图容器 * @param {String} options.url 服务地址 * @param {Number} options.opacity 图层透明度[0.0~1.0] [默认0.3] */ constructor({ viewer, url, opacity = 0.75, show = true, } = {}) { /* 地图视图 外部传入 必须参数 */ this._viewer = viewer; /* 服务地址 外部传入 */ this._url = url; /* 透明度 外部传入 */ this._opacity = opacity; /* 图层随机标识 */ this._renderName = this._guid(); /* 实体集合 */ /* 用DataSource 方式 以便进行多图层管理 */ let dataSource = new Cesium.CustomDataSource(this._renderName); this._viewer.dataSources.add(dataSource); this._entities = dataSource.entities; /* 渲染集合 */ this._renderEntities = new Map(); /* 是否渲染标志 */ this._isUpdateTile = show; /* 是否输出测试信息 */ this._isDebug = false; /* 初始化 */ this._init(); } /** * 初始化 */ _init() { let _self = this; /* 创建服务提供者 */ this._provider = new Cesium.ArcGisMapServerImageryProvider({ url: _self._url, }) /* 激活服务提供者 注册刷帧事件 */ this._provider.readyPromise.then(function(result) { /* 服务提供者的数据范围 */ _self._rectangle = _self._provider.rectangle; /* 输出调试信息 */ if (_self._isDebug) _self._printDebug(); /* 注册事件 */ _self._viewer.scene.postRender.addEventListener(() => { if (_self._isUpdateTile) { /* 设置运行标志为false 等待计算完成 */ _self._isUpdateTile = false; /* 投影瓦片集合 */ _self._renderTiles(); } }); }) } /** * 生成GUID随机数 */ _guid() { function S4() { return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4()); } /** * 输出调试信息 */ _printDebug() { /* 数据提供者的切片方案 */ let tiling = this._provider.tilingScheme; /* WGS84坐标系的切片方案 */ let tiling84 = new Cesium.GeographicTilingScheme(); /* 绘制数据提供者的数据范围 */ this._drawDebugRectangle(this._rectangle, Cesium.Color.GREEN); /* 根据行列号和等级创建调试矩形 */ let rect = tiling84.tileXYToRectangle(1696, 312, 10); /* 绘制调试矩形 */ // this._drawDebugRectangle(rect, Cesium.Color.YELLOW); // let rect11 = Cesium.Rectangle.subsection(rect, 0, 0, 0.5, 0.5); // let rect12 = Cesium.Rectangle.subsection(rect, 0.5, 0, 1.0, 0.5); // let rect21 = Cesium.Rectangle.subsection(rect, 0, 0.5, 0.5, 1.0); // let rect22 = Cesium.Rectangle.subsection(rect, 0.5, 0.5, 1.0, 1.0); // this._drawDebugRectangle(rect11, Cesium.Color.RED); // this._drawDebugRectangle(rect12, Cesium.Color.GREEN); // this._drawDebugRectangle(rect21, Cesium.Color.BLUE); // this._drawDebugRectangle(rect22, Cesium.Color.WHITE); } /** * 绘制调试矩形 * @param {Cesium.Rectangle} rectangle 绘制的矩形 * @param {Cesium.Color} color 矩形边框颜色 */ _drawDebugRectangle(rectangle, color) { /* 计算矩形的外包范围 */ let positions = this._calculateRectangleOutlineCoordinates(rectangle); /* 创建矩形实体 */ let rectEntity = new Cesium.Entity({ name: this._renderName, polyline: { positions: positions, material: color, width: 10, clampToGround: true, //开启贴地 如果有模型则贴模型 } }); /* 加入数据集 */ this._entities.add(rectEntity); } /** * 输出消息 * @param {Object} res */ _console(...rest) { if (this._isDebug) console.log('===' + new Date().Format('yyyy-MM-dd HH:mm:ss') + '>>>', rest); } /** * 渲染瓦片集合 */ _renderTiles() { let _self = this; /* 获取当前视图渲染的瓦片集合 */ let tilesToRender = this._viewer.scene.globe._surface._tilesToRender; if (tilesToRender === undefined || tilesToRender.length === 0) { this._isUpdateTile = true; return; } else { new Promise((resolve, reject) => { /* 对瓦片数组按照level进行排序 以保证后续瓦片重投影时的重叠移除无误 */ tilesToRender.sort(function(obj1, obj2) { let level1 = parseInt(obj1.level); let level2 = parseInt(obj2.level); return level1 - level2; }) /* 返回排序后的渲染瓦片数据集 开始异步计算 */ resolve(tilesToRender); }).then(tiles => { /* 异步重投影瓦片数据集 */ _self._asyncProjectionTiles(tiles); }) } } /** * 根据行列和等级生成key * @param {Number} x 行 * @param {Number} y 列 * @param {Number} level 等级 */ _createKey(x, y, level) { let key = `${this._renderName}_${x}_${y}_${level}`; return key; } /** * 投影瓦片集合 * @param {Object} tiles 原始渲染瓦片集合 */ _asyncProjectionTiles(tiles) { let renderTiles = []; /* 循环投影 */ for (let tile of tiles) { /* 对单个瓦片进行重投影 */ let proTiles = this._projectionTile(tile); for (let proTile of proTiles) { /* 瓦片实体唯一标识 */ let key = this._createKey(proTile.x, proTile.y, proTile.level); /* 查找瓦片是否已经存在 存在则过滤 以免重复存在 */ let subTile = renderTiles.find(obj => { return obj.x === proTile.x && obj.y === proTile.y; }) if (subTile === undefined) { /* 重投影瓦片范围与数据范围的叠合计算 */ let isExists = false; for (let eTile of renderTiles) { if (Cesium.Rectangle.intersection(eTile.rectangle, proTile.rectangle)) { /* 追加子集 方便后期进行层级处理 */ eTile.childTiles.push(key); isExists = true; break; } } /* 加入渲染集合 */ if (!isExists) { renderTiles.push({ key: key, x: proTile.x, y: proTile.y, level: proTile.level, rectangle: proTile.rectangle, childTiles: [], }); } } } } /* 清理低层级元素 */ let i = renderTiles.length; let appendTiles = []; while (i--) { let findTile = renderTiles[i]; if (findTile.childTiles.length >= 1) { /* 创建高层级的瓦片 */ let tiles = this._createFourTiles(findTile); for (let tile of tiles) { appendTiles.push(tile); } /* 如果存在高层级 则删除低层级 */ renderTiles.splice(i, 1); } } /* 将四叉树追加的层级数据加入到渲染集合中 */ for (let appendTile of appendTiles) { renderTiles.push(appendTile); } /* 对数组进行排序 */ renderTiles.sort(function(obj1, obj2) { let level1 = parseInt(obj1.level); let level2 = parseInt(obj2.level); return level1 - level2; }) /* 渲染数据到视图中 */ this._renderTilesToViewer(renderTiles); } /** * 根据矩形和登记查询高一等级的行列号 * @param {Cesium.Rectangle} rectangle */ _createTileByRectangleAndLevel(rectangle, level) { /* 获取矩形中心点 */ let center = Cesium.Rectangle.center(rectangle); /* 新层级 */ let nLevel = parseInt(level) + 1; /* 查询高一层级的行列号 */ let query = this._provider.tilingScheme.positionToTileXY(center, nLevel); if (query === undefined) return undefined; /* 返回结果 */ return { key: this._createKey(query.x, query.y, nLevel), x: query.x, y: query.y, level: nLevel, rectangle: rectangle, childTiles: [], } } /** * 创建四个高层级的瓦片 * @param {Object} tile 当前瓦片 */ _createFourTiles(tile) { let rects = []; let results = []; let rectangle = tile.rectangle; /* 将该矩形按照四叉树规则分割为4个高层级的矩形 */ rects.push(Cesium.Rectangle.subsection(rectangle, 0, 0, 0.5, 0.5)); rects.push(Cesium.Rectangle.subsection(rectangle, 0.5, 0, 1.0, 0.5)); rects.push(Cesium.Rectangle.subsection(rectangle, 0, 0.5, 0.5, 1.0)); rects.push(Cesium.Rectangle.subsection(rectangle, 0.5, 0.5, 1.0, 1.0)); for (let rect of rects) { /* 判断高层级的矩形是否与谁范围存在交集 */ if (Cesium.Rectangle.intersection(rect, this._rectangle)) { /* 查询高一层级的Tile */ let newTile = this._createTileByRectangleAndLevel(rect, tile.level); /* 如果存在加入到返回集合中 */ if (newTile !== undefined) results.push(newTile); } } return results; } /** * 渲染瓦片到视图中 * @param {Object} tiles */ _renderTilesToViewer(tiles) { let _self = this; /* 确定哪些渲染实体已失效 对失效的实体进行清理 */ let deleteKeys = []; for (let [key, tile] of this._renderEntities) { let findTile = tiles.find(obj => { return obj.key === key; }) if (findTile === undefined) { deleteKeys.push(key); } } for (let key of deleteKeys) { /* 移除元素 */ this._renderEntities.delete(key); /* 移除渲染元素 */ this._entities.removeById(key); } /* 对过滤后的数据集进行渲染 */ for (let tile of tiles) { /* 判断当前渲染的数据是否已经渲染且有效 */ if (!this._renderEntities.has(tile.key)) { /* 将数据渲染到视图中 */ let entity = this._renderSimpleTileToViewer(tile); /* 将渲染的数据加入到集合中 */ this._renderEntities.set(tile.key, entity); /* 创建下载线程 */ let workBlob = new Blob([`(${downloadWorker.toString ()})()`]); // 把函数转成一个自执行函数 // let workBlob = new Blob([downloadWorker.toLocaleString().match( // /(?:\/\*[\s\S]*?\*\/|\/\/.*?\r?\n|[^{])+\{([\s\S]*)\}$/)[1]]) // 把函数的主体内容拿出来进行转换 let worker = new Worker(URL.createObjectURL(workBlob)); /* 发送下载任务 */ worker.postMessage({ key: tile.key, url: this._url, x: tile.x, y: tile.y, level: tile.level, }) /* 接收下载任务 */ worker.onmessage = function(event) { _self._console(`render x:${event.data.x} y:${event.data.y} level:${event.data.level}`); /* 判断是否存在 */ let renderEntity = _self._entities.getById(event.data.key); if (renderEntity !== undefined) { let key = event.data.key; const canvas = _self._createCanvas(event.data, event.data.image, _self._isDebug); renderEntity.rectangle.material = canvas; } worker.terminate(); } } } /* 重启计算标志 */ this._isUpdateTile = true; } /** * 渲染单个瓦片到视图中 * @param {Object} tile */ _renderSimpleTileToViewer(tile) { /* 创建画布 */ const canvas = this._createCanvas(tile, undefined, this._isDebug); let bjPositions = this._calculateRectangleOutlineCoordinates(tile .rectangle); let tileEntity = new Cesium.Entity({ name: this._renderName, id: tile.key, rectangle: { coordinates: tile.rectangle, material: canvas, }, polyline: { positions: bjPositions, material: Cesium.Color.YELLOW.withAlpha(this._isDebug ? 1 : 0), width: 1, clampToGround: true, //开启贴地 如果有模型则贴模型 } }); return this._entities.add(tileEntity); } /** * 根据瓦片创建画布 * @param {Object} tile 瓦片 * @param {Object} image 绘制的图片 * @param {boolean} islabel 是否绘制标签 */ _createCanvas(tile, image, islabel) { /* 获取服务提供者的切片方案 */ let provider = this._provider; const canvas = document.createElement("canvas"); canvas.width = provider.tileWidth; canvas.height = provider.tileHeight; const context = canvas.getContext("2d"); if (image !== undefined) { context.globalAlpha = this._opacity; context.drawImage(event.data.image, 0, 0, canvas.width, canvas.height); } if (islabel !== undefined && islabel === true) { context.globalAlpha = 1.0; /* 创建标签 */ context.font = "20px Arial"; context.textAlign = "center"; context.fillStyle = 'rgba(255,255,0)'; context.strokeStyle = 'rgba(255,255,255,1)'; context.lineWidth = 2; context.strokeText(`L: ${tile.level}`, 126, 86); context.fillText(`L: ${tile.level}`, 126, 86); context.strokeText(`X: ${tile.x}`, 126, 136); context.fillText(`X: ${tile.x}`, 126, 136); context.strokeText(`Y: ${tile.y}`, 126, 186); context.fillText(`Y: ${tile.y}`, 126, 186); } /* 返回画布 */ return canvas; } /** * 投影当前瓦片 * @param {Object} tile */ _projectionTile(tile) { /* 获取矩形 */ let rectangle = tile._rectangle; // let imageryLevel = parseInt(tile.level) + 1; let imageryLevel = parseInt(tile.level); let mercatorTilingScheme = this._provider.tilingScheme; let res = []; /* 先判断当前的切片范围是否与提供者的数据范围有交集 */ let interRectangle = Cesium.Rectangle.intersection(rectangle, this._rectangle); /* 如果当前计算的瓦片与数据范围无交集 则舍弃 */ if (interRectangle === undefined) return res; /* 判断北西角点的墨卡托投影瓦片信息 */ let northwestTileCoordinates = mercatorTilingScheme.positionToTileXY( Cesium.Rectangle.northwest(rectangle), imageryLevel ); /* 判断南东角点的墨卡托投影瓦片信息 */ let southeastTileCoordinates = mercatorTilingScheme.positionToTileXY( Cesium.Rectangle.southeast(rectangle), imageryLevel ); /* 根据不同类型分别进行计算 */ if (northwestTileCoordinates !== undefined && southeastTileCoordinates !== undefined) { for (let i = northwestTileCoordinates.x; i <= southeastTileCoordinates.x; i++) { for (let j = northwestTileCoordinates.y; j <= southeastTileCoordinates.y; j++) { let _webRectangle = mercatorTilingScheme.tileXYToRectangle(i, j, imageryLevel); if (Cesium.Rectangle.intersection(_webRectangle, this._rectangle)) { res.push({ x: i, y: j, level: imageryLevel, rectangle: _webRectangle, }); } } } } else if (northwestTileCoordinates !== undefined) { let _webRectangle = mercatorTilingScheme.tileXYToRectangle(northwestTileCoordinates.x, northwestTileCoordinates.y, imageryLevel); if (Cesium.Rectangle.intersection(_webRectangle, this._rectangle)) { res.push({ x: northwestTileCoordinates.x, y: northwestTileCoordinates.y, level: imageryLevel, rectangle: _webRectangle, }); } } else if (southeastTileCoordinates !== undefined) { let _webRectangle = mercatorTilingScheme.tileXYToRectangle(southeastTileCoordinates.x, southeastTileCoordinates.y, imageryLevel); if (Cesium.Rectangle.intersection(_webRectangle, this._rectangle)) { res.push({ x: southeastTileCoordinates.x, y: southeastTileCoordinates.y, level: imageryLevel, rectangle: _webRectangle, }); } } return res; } /** * 计算矩形的外围坐标串 * @param {Cesium.Rectangle} rectangle 矩形 * @return {Array} 坐标串集合 */ _calculateRectangleOutlineCoordinates(rectangle) { /* 计算东南角 */ let south_east = Cesium.Rectangle.southeast(rectangle); let se = Cesium.Cartographic.toCartesian(south_east); /* 计算西南角 */ let south_west = Cesium.Rectangle.southwest(rectangle); let sw = Cesium.Cartographic.toCartesian(south_west); /* 计算东北角 */ let north_east = Cesium.Rectangle.northeast(rectangle); let ne = Cesium.Cartographic.toCartesian(north_east); /* 计算西北角 */ let north_west = Cesium.Rectangle.northwest(rectangle); let nw = Cesium.Cartographic.toCartesian(north_west); return [sw, se, ne, nw, sw]; } /** * 根据Entity的名称批量删除Entity * @param {String} entityName 实体名称 */ _removeEntityByName(entityName) { /* 获取实体集合 */ var entities = this._entities; /* 如果不存在实体集合或集合中没有数据 则返回 */ if (!entities || !entities.values) return; var delEntitys = []; /* 循环获取当前集合中的所有实体 */ for (var i = 0; i < entities.values.length; i++) { if (entities.values[i].name == entityName) { delEntitys.push(entities.values[i]); } } /* 删除符合条件的所有实体 */ for (var i = 0; i < delEntitys.length; i++) { entities.remove(delEntitys[i]); } } } /* 对外方法 */ Object.assign(CrImageServerLayer.prototype, /** @lends CrImageServerLayer.prototype */ { /** * 隐藏 */ hide: function() { this._console('隐藏'); this._isUpdateTile = false; /* 清理资源 */ this._removeEntityByName(this._renderName); /* 清理渲染 */ this._renderEntities.clear(); }, /** * 显示 */ show: function() { this._console('显示'); this._isUpdateTile = true; }, /** * 设置透明度 * @param {Number} opacity [0-1] */ setOpacity: function(opacity) { if (opacity === undefined || typeof opacity !== 'number') return; if (opacity >= 1) this._opacity = 1.0; if (opacity <= 0) this._opacity = 0.0; this._opacity = parseFloat(opacity); } }) /* 下载线程函数 */ function downloadWorker() { /* 接收主线程发送的文件下载请求 */ onmessage = function(event) { let data = event.data; /* 创建下载数据链接 */ let url = data.url + '/tile/' + data.level + '/' + data.y + '/' + data.x; /* 创建下载相关 并下载 */ let xhr = new XMLHttpRequest(); xhr.open('get', url, true); xhr.responseType = "blob"; //设置返回类型,此处我用于下载文件 所以返回blob xhr.onload = function() { // 请求完成 if (this.status === 200) { var blob = this.response; var bmpPromise = createImageBitmap(blob, { imageOrientation: "none", premultiplyAlpha: "none", colorSpaceConversion: "default", }); bmpPromise.then(function(image) { let outObj = { key: data.key, x: data.x, y: data.y, level: data.level, image: image, } postMessage(outObj); }) } else { console.log('===>>>', url + ' Not found'); } } /* 发送请求 */ xhr.send(); } } /* 输出类 */ export default CrImageServerLayer