123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- /**
- * 创建者:王成
- * 操作系统: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<Cesium.Cartesian3>} 坐标串集合
- */
- _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
|