您的位置:首页 > 财经 > 产业 > 网页设计与制作广东开放大学_佛山网页设计制作_站长之家综合查询工具_站长之家的作用

网页设计与制作广东开放大学_佛山网页设计制作_站长之家综合查询工具_站长之家的作用

2025/1/12 2:43:04 来源:https://blog.csdn.net/wanghaoyingand/article/details/144774785  浏览:    关键词:网页设计与制作广东开放大学_佛山网页设计制作_站长之家综合查询工具_站长之家的作用
网页设计与制作广东开放大学_佛山网页设计制作_站长之家综合查询工具_站长之家的作用

在可视化项目中,我们经常需要在球体表面绘制飞线,并在飞线的中间展示一些信息卡片(也可以称为 Label、弹窗等)。比如:攻击事件监控、地理位置说明、某些数据统计信息等。这篇文章就是一步步讲解如何在飞线的中点生成一个卡片,并在需要时进行可视化展示。
在这里插入图片描述
在这里插入图片描述

1. 实现思路

  1. 先计算飞线的中点:在球面上,给定起点(经度/纬度)和终点(经度/纬度),通过数学公式将它们转换为三维坐标,再在三维空间里计算出“中点”。
  2. 把卡片做成 Sprite
    • 在 Three.js 中,常用 Sprite(精灵)来做类似标注、卡片的东西。因为 Sprite 永远面向摄像机,比较适合做平面贴图。
    • 也可以用 PlaneGeometry+Mesh,如果需要更多自定义操作。
  3. 生成卡片纹理
    • 我们可能想把一些文字或 HTML 元素做成“图片纹理”,再贴到 Sprite 上。
    • 为了在 Three.js 中显示文字或复杂的 DOM 样式,这里可以借助 html2canvas 来把指定的 DOM 生成一张 png,再用 TextureLoader 把这张图片加载为贴图。
  4. 将卡片放置在飞线中点上
    • 将上一步得到的 Sprite,设置 sprite.position.set(...) 到刚才计算好的中点位置。
  5. 显示 / 隐藏逻辑
    • 如果你想在鼠标悬停在飞线时显示卡片,可以先默认 sprite.visible = false,在鼠标检测到与飞线相交时再显示出来。
    • 如果想一直显示,直接 sprite.visible = true 即可。
  6. 避免卡片被销毁
    • 有时我们会在一段时间后清除掉飞线和卡片,但如果你的业务想长期留着这条飞线,就不需要删除它,只要隐藏或设置 visible = false 即可。

下面就详细说一下具体步骤和示例代码。


2. 计算球面两点的中点

Three.js 中通常会用到 lon2xyz() 这个工具函数,将经纬度转换为三维坐标(X、Y、Z)。我们可以这样写一个简易函数(根据地球半径 R,和经纬度 lon、lat 计算):

import { Vector3, MathUtils } from 'three';/*** 将经纬度转换为 Three.js 空间坐标* @param radius 球体半径* @param lon 经度* @param lat 纬度* @returns {Vector3} 球面上的三维坐标*/
export function lon2xyz(radius: number, lon: number, lat: number): Vector3 {const phi = MathUtils.degToRad(90 - lat); // 纬度转到球坐标系const theta = MathUtils.degToRad(lon + 180); // 经度转到球坐标系const x = -radius * Math.sin(phi) * Math.cos(theta);const z = radius * Math.sin(phi) * Math.sin(theta);const y = radius * Math.cos(phi);return new Vector3(x, y, z);
}

有了这个函数,就可以很容易获取到起点终点在三维空间的坐标,然后再计算中点:

/*** 计算球面弧线大约中点* @param startE 起点经度* @param startN 起点纬度* @param endE 终点经度* @param endN 终点纬度* @param radius 球半径* @returns 球面大约中点*/
export function calculateMidpoint(startE: number,startN: number,endE: number,endN: number,radius: number
): Vector3 {// 先把经纬度转成三维坐标const startP = lon2xyz(radius, startE, startN);const endP = lon2xyz(radius, endE, endN);// 简单的三维空间中点const midpoint = new Vector3((startP.x + endP.x) / 2,(startP.y + endP.y) / 2,(startP.z + endP.z) / 2);// 把这个中点“归一化”回到球面上(稍微放大一些,让它悬浮在球面之上)midpoint.normalize().multiplyScalar(radius * 1.1);return midpoint;
}

这样就能得到一个中点坐标,用来放我们的“卡片”。


3. 创建卡片:HTML → Canvas → Texture → Sprite

3.1 使用 html2canvas 生成图片

我们先写一个 HTML 片段,一般可以放在一个隐藏或全局的容器里(例如 <div id="html2canvas" style="display: none;"></div>),当我们想生成卡片的时候,往里塞一下 HTML,然后通过 html2canvas 转换成图片 base64,再把它贴到 Sprite 上。

示例:

<!-- 在你的 HTML 中预留一个容器 -->
<div id="html2canvas" style="display: none;"></div>

然后在 JavaScript/TypeScript 里:

import html2canvas from 'html2canvas';
import { Sprite, SpriteMaterial, TextureLoader } from 'three';/*** 生成一个 Sprite,以卡片方式显示* @param cardHTML 需要在卡片上显示的 HTML 字符串* @returns Promise<Sprite>*/
export async function createCardSprite(cardHTML: string): Promise<Sprite> {// 1) 拿到 html2canvas 的容器const shareContent = document.getElementById('html2canvas');if (!shareContent) throw new Error('html2canvas container not found');// 2) 写入 HTMLshareContent.innerHTML = cardHTML;// 3) 用 html2canvas 生成截图const canvas = await html2canvas(shareContent, {backgroundColor: 'transparent',scale: 2,dpi: window.devicePixelRatio,});// 4) 把生成的 canvas 转成 base64,再用 TextureLoader 变成贴图const dataURL = canvas.toDataURL('image/png');const map = new TextureLoader().load(dataURL);// 5) 用这个贴图创建 SpriteMaterial 和 Spriteconst material = new SpriteMaterial({map: map,transparent: true,});const sprite = new Sprite(material);// 6) 设置卡片的大小,后面会继续调整sprite.scale.set(15, 10, 1);return sprite;
}

现在,我们已经有一个 createCardSprite() 函数,只要给它一段 HTML,它就能返回一个写着那段 HTML 的 Sprite


3.2 将卡片放置在飞线中点

假设你已经创建了飞线 arcline,现在我们要做的就是:

  1. 计算中点 midpoint
  2. 生成卡片 Sprite。
  3. 设置位置 & 加到场景或飞线 Group 里。

例如(结合你的需求):

import { Group } from 'three';// 在你的 Earth 类中
createFlyLineLabel(startE: number, startN: number, endE: number, endN: number, radius: number, text: any, flyLineArcGroup: Group
) {// 1) 先拿到中点const midpoint = calculateMidpoint(startE, startN, endE, endN, radius);// 2) 准备 HTML 片段,比如警告信息const cardHTML = `<div class="flyline-card"><div style="font-weight: bold; font-size: 16px;">${text.cardInfo.alarmType || '无'}<span style="color: red; margin-left: 10px;">${text.cardInfo.hazardRating || '无'}</span></div><div>攻击时间:${text.cardInfo.attackTime || '无'}</div><div>攻击来源:${text.cardInfo.attackAddr || '无'}</div><div>被攻击IP:${text.cardInfo.attackDip || '无'}</div><div>攻击IP:${text.cardInfo.attackSip || '无'}</div></div>`;// 3) 生成 SpritecreateCardSprite(cardHTML).then((sprite) => {// 4) 设置 sprite 的位置sprite.position.set(midpoint.x, midpoint.y + 2, midpoint.z);// 5) 把 sprite 添加到 flyLineArcGroup 或其他场景flyLineArcGroup.add(sprite);// 如果你只想在鼠标移到飞线上才显示:sprite.visible = false; // 默认隐藏// 下面可以把 sprite 存到飞线对象的 userData 里,或者自己维护});
}

这样,当你在创建每条飞线时,只要调用 createFlyLineLabel(...),就能在“中间”自动生成一个相应的卡片。


4. 鼠标交互:让卡片只在悬停时显示

如果你希望卡片默认隐藏,并在鼠标悬停飞线时才显示,就需要做以下几点:

  1. 给飞线存下它对应的卡片:比如在 arcline.userData['labelSprite'] = sprite;
  2. 使用 Raycaster 来检测鼠标是否悬停在飞线上:
    • mousemove 事件里记录鼠标坐标;
    • 在每帧 render 时,对飞线进行 intersectObjects
    • 如果相交,设置 arcline.userData['labelSprite'].visible = true,否则设为 false。

这个部分比较常见,代码示例略简要:

// 在 constructor 或 init 时,定义
this.raycaster = new Raycaster();
this.mouseVector = new Vector2();// 监听 DOM 的 mousemove
this.options.dom.addEventListener('mousemove', (event) => {const rect = this.options.dom.getBoundingClientRect();this.mouseVector.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;this.mouseVector.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
}, false);// 在 render() 中检测相交
render() {// 1) 先设置射线this.raycaster.setFromCamera(this.mouseVector, this.options.camera);// 2) 用 flyLineArcGroup.children 作为检测对象const intersects = this.raycaster.intersectObjects(this.flyLineArcGroup.children, true);// 3) 先让所有飞线卡片隐藏this.flyLineArcGroup.children.forEach((child: any) => {if (child.userData && child.userData.labelSprite) {child.userData.labelSprite.visible = false;}});// 4) 如果相交到某个 child,就显示其 labelSpriteif (intersects.length > 0) {const first = intersects[0].object;const arcRoot = this.findArcLineRoot(first);if (arcRoot && arcRoot.userData && arcRoot.userData.labelSprite) {arcRoot.userData.labelSprite.visible = true;}}// ... 其他动画逻辑 ...
}// 辅助函数:找飞线最外层(如果你的结构是 Group 嵌套的话)
findArcLineRoot(obj) {while (obj.parent && obj.parent !== this.flyLineArcGroup) {obj = obj.parent;}return obj;
}

这样当鼠标移动到飞线上,就会自动显示它对应的卡片。移动走后,再次隐藏。


5. 总结

  1. 数学公式:利用 lon2xyz() 将经纬度转换到三维坐标,再结合简单的 (start+end)/2 求出中点,最后归一化到球面半径,从而得到“球面中点”。
  2. 使用 Sprite 做卡片
    • html2canvas 把 DOM 转成截图;
    • TextureLoader + SpriteMaterial + Sprite
    • 设置 sprite.position
  3. 可选的交互
    • 可以一直显示卡片,也可以悬停时显示
    • 如果要做更多复杂的交互,可以配合事件库或手动管理 Raycaster

上述就是在球面飞线中点生成卡片的思路与完整步骤。新手可以直接复制示例代码并替换自己的数据和文本,就能快速跑出一个带有三维地球飞线与卡片标注的场景。希望对你有所帮助,Happy Coding!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com