当前位置: 首页 > news >正文

【Cesium 开发实战教程】第五篇:空间分析实战:缓冲区、可视域与工程测量

一、开篇衔接​

大家好!前四篇我们从环境搭建、核心对象、静态 Entity 到动态轨迹,逐步实现了 Cesium 场景的 “可视化能力”。但在真实业务中,三维场景不仅要 “好看”,更要 “有用”—— 比如:​

  • 应急指挥:需要快速计算 “事故点周边 1 公里的影响范围”(缓冲区分析);​
  • 通信规划:需要判断 “某基站能覆盖哪些区域”(可视域分析);​
  • 工程施工:需要测量 “两点之间的距离”“山体的高度差”(工程测量)。​

这些 “基于空间位置的计算” 就是 空间分析,也是 Cesium 区别于普通地图库的核心价值之一。本篇将通过三个实战场景,带你掌握 Cesium 空间分析的核心 API 与落地技巧,让你的三维应用真正具备 “决策支持” 能力。

二、空间分析基础概念(先理清逻辑)​

在动手前,先明确 Cesium 空间分析的核心逻辑与关键 API,避免盲目写代码。​

1. 什么是空间分析?​

空间分析是 “基于三维空间位置,对地理数据进行计算、判断的过程”,核心目标是 “从空间关系中提取有用信息”。Cesium 虽未提供专门的 “空间分析模块”,但通过底层几何计算(Geometry)、地形交互(Terrain)、鼠标事件(ScreenSpaceEventHandler)等 API,可灵活实现各类分析功能。​

2. 关键 API 梳理​

后续实战会用到以下核心类,先提前熟悉:​

类名 / API​

作用​

场景关联​

CircleGeometry​

创建圆形几何图形(用于缓冲区基础形状)​

缓冲区分析​

PolygonGeometry​

创建多边形几何图形(用于可视域结果展示)​

可视域分析​

TerrainProvider​

加载地形数据(分析结果贴地依赖地形)​

所有场景(需贴地时)​

ScreenSpaceEventHandler​

监听鼠标点击、移动事件(用于测量时选点)​

工程测量​

Cartographic​

经纬度坐标对象(计算距离、高度差的基础)​

工程测量​

Cesium.Math​

提供角度转换、距离计算等工具函数​

所有场景​

三、实战场景(从需求到代码落地)​

以下场景均在 CesiumViewer.vue 中实现,需先引入新增的 Cesium 类,并确保已加载地形数据(空间分析常依赖地形高度,默认地形可能精度不足)。​

前置准备:加载高精度地形(可选但推荐)​

空间分析(如可视域、高度测量)的准确性依赖地形数据,先加载 Cesium 官方的高精度地形(免费):

// 在初始化 Viewer 前添加(onMounted 中)
import { IonResource, createWorldTerrain } from 'cesium'// 1. 加载 Cesium 官方高精度地形(需要 Cesium Ion 账号,免费注册)
// 注册地址:https://cesium.com/ion/,获取默认 Token(新建项目后自动生成)
Cesium.Ion.defaultAccessToken = "你的 Cesium Ion Token";// 2. 初始化 Viewer 时配置地形
viewer = new Viewer(cesiumContainer.value, {terrainProvider: createWorldTerrain({requestWaterMask: true, // 显示水面效果(可选)requestVertexNormals: true // 支持地形光照(可选,提升视觉效果)}),// ...其他配置(隐藏不需要的控件)
});

场景 1:缓冲区分析(影响范围计算)​

需求说明​:

“点击地图上任意点,生成以该点为中心、半径可调整的圆形缓冲区(如事故点周边 1 公里影响范围)”,支持缓冲区颜色、透明度配置,且缓冲区需贴地(随地形起伏)。​

代码实现:

// 引入需要的类
import {CircleGeometry,GeometryInstance,Primitive,Material,Color,Cartesian3,Cartographic,ScreenSpaceEventType
} from 'cesium'// 缓冲区分析核心函数
const bufferAnalysis = () => {let bufferPrimitive = null; // 存储缓冲区Primitive(用于后续删除/更新)let bufferRadius = 1000; // 默认缓冲区半径(1000米)// 1. 添加“调整半径”的控件(在template中添加输入框)// 此处简化:用console.log提示,实际项目可添加UI输入框console.log("当前缓冲区半径:1000米,调用 updateBufferRadius(2000) 可调整为2000米");// 2. 监听鼠标左键点击事件(点击地图生成缓冲区)const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction((event) => {// 获取点击位置的世界坐标(贴地)const clickPosition = viewer.scene.pickPosition(event.position);if (!clickPosition) return;// 删除旧缓冲区(避免重复)if (bufferPrimitive) {viewer.scene.primitives.remove(bufferPrimitive);}// 3. 创建圆形缓冲区几何(基于点击位置)const circleGeometry = new CircleGeometry({center: clickPosition, // 缓冲区中心(点击位置)radius: bufferRadius, // 半径(米)vertexFormat: MaterialAppearance.VERTEX_FORMAT, // 支持材质着色extrudedHeight: 0 // 缓冲区高度(0表示贴地,>0表示立体)});// 4. 创建几何实例(绑定材质)const geometryInstance = new GeometryInstance({geometry: circleGeometry,attributes: {color: ColorGeometryInstanceAttribute.fromColor(Color.RED.withAlpha(0.3) // 缓冲区颜色:红色半透明)}});// 5. 创建Primitive并添加到场景bufferPrimitive = new Primitive({geometryInstances: geometryInstance,appearance: new MaterialAppearance({material: new Material({fabric: {type: "Color", // 纯色材质uniforms: {color: Color.RED.withAlpha(0.3)}}}),transparent: true // 开启透明(避免遮挡底图)})});viewer.scene.primitives.add(bufferPrimitive);// 6. 相机飞到缓冲区位置(方便查看)viewer.flyTo(bufferPrimitive, {offset: new HeadingPitchRange(0, -0.5, bufferRadius * 2) // 距离缓冲区2倍半径});}, ScreenSpaceEventType.LEFT_CLICK);// 7. 暴露“更新缓冲区半径”的方法(外部可调用)window.updateBufferRadius = (newRadius) => {if (newRadius <= 0) {console.error("半径必须大于0");return;}bufferRadius = newRadius;console.log(`缓冲区半径已更新为:${bufferRadius}米`);// 重新点击地图生效(实际项目可自动重新生成)};return handler; // 返回事件处理器,方便组件卸载时销毁
};// 在onMounted中调用
onMounted(() => {// ...之前的初始化代码(加载地形、Viewer等)const bufferHandler = bufferAnalysis();// 组件卸载时销毁事件处理器(避免内存泄漏)onUnmounted(() => {if (bufferHandler) {bufferHandler.destroy();}});
});

效果与优化​

  • 基础效果:点击地图任意位置,生成红色半透明圆形缓冲区,相机自动聚焦;​
  • 进阶优化:​
  1. 增加 UI 输入框:在 template 中添加 <input type="number" v-model="radius" @change="updateBufferRadius(radius)" />,实现可视化调整半径;​
  2. 立体缓冲区:将 extrudedHeight 设为 100(单位:米),生成 “圆柱状” 立体缓冲区(适合展示 “高度影响范围”,如烟雾扩散);​
  3. 多缓冲区叠加:支持同时生成多个缓冲区(需存储多个 bufferPrimitive,避免删除时误删)。

场景 2:可视域分析(视野范围计算)​

需求说明​:

“在地图上指定一个观测点(如山顶观景台),计算该点能看到的区域(可视域)和看不到的区域(不可视域)”,用不同颜色区分,且结果需贴合地形(如山脉遮挡会导致部分区域不可见)。​

代码实现​:

可视域分析的核心是 “模拟观测点的视线,判断地形是否遮挡”,Cesium 需通过自定义射线检测实现(或使用第三方库如 cesium-visibility,此处用原生 API 实现基础版)

// 引入需要的类
import {Ray,IntersectionTests,Cartographic,PolygonGeometry,ColorGeometryInstanceAttribute
} from 'cesium'// 可视域分析核心函数(简化版:基于观测点和半径,生成可视域多边形)
const visibilityAnalysis = () => {let visibilityPrimitive = null; // 存储可视域结果const viewRadius = 5000; // 观测半径(5000米)const viewHeight = 20; // 观测点高度(相对于地面,如20米高的观景台)// 1. 监听鼠标右键点击(区分缓冲区的左键,避免冲突)const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);handler.setInputAction((event) => {// 获取点击位置的地面坐标(不包含高度)const ray = viewer.camera.getPickRay(event.position);const hitPosition = viewer.scene.globe.pick(ray, viewer.scene);if (!hitPosition) return;// 删除旧可视域结果if (visibilityPrimitive) {viewer.scene.primitives.remove(visibilityPrimitive);}// 2. 计算观测点坐标(地面坐标 + 观测高度)const cartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(hitPosition);const viewPosition = Cartesian3.fromRadians(cartographic.longitude,cartographic.latitude,cartographic.height + viewHeight // 观测点高度 = 地面高度 + 观景台高度);// 3. 生成观测范围内的采样点(圆形,36个方向,确保结果平滑)const samplePoints = [];const sampleCount = 36; // 采样方向数量(越多越精确,性能越差)for (let i = 0; i < sampleCount; i++) {// 计算每个方向的角度(弧度)const angle = (i / sampleCount) * 2 * Math.PI;// 计算采样点的经纬度(基于观测点和半径)const sampleCartographic = new Cartographic(cartographic.longitude + (viewRadius / viewer.scene.globe.ellipsoid.maximumRadius) * Math.cos(angle),cartographic.latitude + (viewRadius / viewer.scene.globe.ellipsoid.maximumRadius) * Math.sin(angle),0 // 初始高度设为0,后续获取地形高度);// 获取采样点的地形高度(确保贴地)viewer.scene.globe.sampleHeightMostDetailed([sampleCartographic]);// 转换为世界坐标const samplePosition = Cartesian3.fromRadians(sampleCartographic.longitude,sampleCartographic.latitude,sampleCartographic.height);// 4. 射线检测:判断观测点到采样点是否被地形遮挡const ray = new Ray(viewPosition, Cartesian3.subtract(samplePosition, viewPosition, new Cartesian3()));const intersection = IntersectionTests.rayEllipsoid(ray, viewer.scene.globe.ellipsoid);let isVisible = true;if (intersection) {// 计算射线与地形的交点距离const intersectionPosition = Cartesian3.add(ray.origin, Cartesian3.multiplyByScalar(ray.direction, intersection.start, new Cartesian3()), new Cartesian3());const intersectionDistance = Cartesian3.distance(viewPosition, intersectionPosition);const sampleDistance = Cartesian3.distance(viewPosition, samplePosition);// 如果交点距离 < 采样点距离,说明被地形遮挡if (intersectionDistance < sampleDistance - 1) { // 1米误差容忍isVisible = false;}}// 5. 记录采样点及可见性samplePoints.push({position: samplePosition,visible: isVisible});}// 6. 分离可视/不可视点,生成多边形const visiblePoints = samplePoints.filter(p => p.visible).map(p => p.position);const invisiblePoints = samplePoints.filter(p => !p.visible).map(p => p.position);// 生成可视域多边形(绿色半透明)if (visiblePoints.length > 3) { // 至少3个点才能构成多边形const visibleGeometry = new PolygonGeometry({polygonHierarchy: new PolygonHierarchy(visiblePoints),vertexFormat: MaterialAppearance.VERTEX_FORMAT});const visibleInstance = new GeometryInstance({geometry: visibleGeometry,attributes: {color: ColorGeometryInstanceAttribute.fromColor(Color.GREEN.withAlpha(0.4))}});visibilityPrimitive = new Primitive({geometryInstances: visibleInstance,appearance: new MaterialAppearance({material: new Material({fabric: { type: "Color", uniforms: { color: Color.GREEN.withAlpha(0.4) } }}),transparent: true})});viewer.scene.primitives.add(visibilityPrimitive);}// 生成不可视域多边形(红色半透明)if (invisiblePoints.length > 3) {const invisibleGeometry = new PolygonGeometry({polygonHierarchy: new PolygonHierarchy(invisiblePoints),vertexFormat: MaterialAppearance.VERTEX_FORMAT});const invisibleInstance = new GeometryInstance({geometry: invisibleGeometry,attributes: {color: ColorGeometryInstanceAttribute.fromColor(Color.RED.withAlpha(0.2))}});const invisiblePrimitive = new Primitive({geometryInstances: invisibleInstance,appearance: new MaterialAppearance({material: new Material({fabric: { type: "Color", uniforms: { color: Color.RED.withAlpha(0.2) } }}),transparent: true})});viewer.scene.primitives.add(invisiblePrimitive);visibilityPrimitive = invisiblePrimitive; // 统一管理(实际项目需存储多个)}// 7. 相机飞到观测点viewer.flyTo(viewPosition, {offset: new HeadingPitchRange(0, -0.6, viewRadius * 1.5)});}, ScreenSpaceEventType.RIGHT_CLICK); // 右键触发可视域分析return handler;
};// 在onMounted中调用
onMounted(() => {// ...之前的代码const bufferHandler = bufferAnalysis();const visibilityHandler = visibilityAnalysis(); // 新增可视域分析onUnmounted(() => {bufferHandler.destroy();visibilityHandler.destroy(); // 销毁可视域事件});
});

关键说明与优化​

  • 核心逻辑:通过 “36 个方向的射线检测” 模拟观测点视线,判断每个方向是否被地形遮挡,再用多边形拟合可视 / 不可视区域;​
  • 精度与性能平衡:sampleCount(采样方向数)越多,结果越精确,但计算量越大(建议普通场景用 36-72,高精度场景用 108);​
  • 第三方库推荐:原生 API 实现的可视域功能较基础,复杂场景(如多观测点、动态障碍物)可使用 cesium-visibility(GitHub 开源库),封装更完善、性能更优。

场景 3:工程测量(距离 / 高度差计算)​

需求说明:​

“支持鼠标点击选点,实时计算两点之间的平面距离(水平距离)、空间距离(直线距离)和高度差”,并在地图上显示测量结果标签,支持连续测量(选多个点,计算总距离)。​

代码实现:

// 工程测量核心函数
const engineeringMeasurement = () => {let measurementPoints = []; // 存储测量点let measurementEntity = null; // 存储测量线和标签let totalDistance = 0; // 总距离(连续测量时)// 1. 监听鼠标左键选点,右键结束测量const handler = new ScreenSpaceEventHandler(viewer.scene.canvas);// 左键选点handler.setInputAction((event) => {// 获取选点的世界坐标(贴地)const pickPosition = viewer.scene.pickPosition(event.position);if (!pickPosition) return;// 转换为经纬度坐标(方便计算)const pickCartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(pickPosition);const pointData = {cartesian: pickPosition,cartographic: pickCartographic,height: pickCartographic.height // 点的地形高度};// 添加选点measurementPoints.push(pointData);console.log(`已选点 ${measurementPoints.length}:`, pointData);// 至少2个点才计算测量结果if (measurementPoints.length >= 2) {calculateMeasurement();}}, ScreenSpaceEventType.LEFT_CLICK);// 右键结束测量(重置)handler.setInputAction(() => {if (measurementEntity) {viewer.entities.remove(measurementEntity);}measurementPoints = [];totalDistance = 0;console.log("测量已结束,总距离:", (totalDistance / 1000).toFixed(2), "公里");}, ScreenSpaceEventType.RIGHT_CLICK);// 2. 计算测量结果(距离、高度差)const calculateMeasurement = () => {// 删除旧测量结果if (measurementEntity) {viewer.entities.remove(measurementEntity);}// 获取最后两个点(连续测量时,每次计算最新两点)const lastPoint = measurementPoints[measurementPoints.length - 1];const prevPoint = measurementPoints[measurementPoints.length - 2];// 3. 计算距离(单位:米)const spatialDistance = Cartesian3.distance(lastPoint.cartesian, prevPoint.cartesian); // 空间距离const horizontalDistance = Cartesian3.distance(Cartesian3.fromRadians(prevPoint.cartographic.longitude, prevPoint.cartographic.latitude, 0),Cartesian3.fromRadians(lastPoint.cartographic.longitude, lastPoint.cartographic.latitude, 0)); // 平面距离(高度设为0)// 4. 计算高度差(单位:米)const heightDiff = lastPoint.height - prevPoint.height;// 5. 累加总距离(连续测量)totalDistance += spatialDistance;// 6. 创建测量线(连接两点)const linePositions = [prevPoint.cartesian, lastPoint.cartesian];// 7. 创建测量标签(显示结果,放在两点中间)const midPosition = Cartesian3.lerp(prevPoint.cartesian, lastPoint.cartesian, 0.5, new Cartesian3()); // 两点中点const labelText = `平面距离:${(horizontalDistance / 1000).toFixed(3)}公里\n空间距离:${(spatialDistance / 1000).toFixed(3)}公里\n高度差:${heightDiff.toFixed(1)}米\n总距离:${(totalDistance / 1000).toFixed(3)}公里`;// 8. 创建测量Entity(线+标签)measurementEntity = viewer.entities.add({name: "工程测量",polyline: {positions: linePositions,width: 3,color: Color.BLUE,clampToGround: true // 线贴地},label: {position: midPosition,text: labelText,font: "14px sans-serif",fillColor: Color.WHITE,backgroundColor: Color.BLUE.withAlpha(0.7),padding: new Cartesian2(10, 5),pixelOffset: new Cartesian2(0, -20), // 标签在 line 上方showBackground: true}});// 9. 相机聚焦到测量区域viewer.flyTo(measurementEntity);};return handler;
};// 在onMounted中调用
onMounted(() => {// ...之前的代码const bufferHandler = bufferAnalysis();const visibilityHandler = visibilityAnalysis();const measurementHandler = engineeringMeasurement(); // 新增工程测量onUnmounted(() => {bufferHandler.destroy();visibilityHandler.destroy();measurementHandler.destroy(); // 销毁测量事件});
});

操作说明与扩展​

  • 基础操作:​
  1. 左键点击地图选第一个点,再左键点击选第二个点,自动显示两点的距离和高度差;​
  2. 继续左键点击选第三个点,自动计算 “第二个点→第三个点” 的距离,并累加总距离;​
  3. 右键点击结束测量,清空结果。​
  • 扩展功能:​
  1. 面积测量:选 3 个以上点,用 Cesium.EllipsoidGeodesic.computeSurfaceDistance 计算多边形面积;​
  2. 角度测量:计算三个点构成的夹角(用向量点积公式);​
  3. 测量样式自定义:支持切换线颜色、标签样式(如不同测量类型用不同颜色)。

四、常见问题与解决方案​

空间分析场景容易遇到 “结果不准确”“性能差”“交互冲突” 等问题,以下是高频问题的解决思路:​

1. 问题 1:缓冲区 / 测量线不贴地(悬浮在地形上方)​

  • 原因:使用了 “世界坐标(Cartesian3)” 而非 “地形坐标”,或未考虑地形高度。​
  • 解决方案:
    • 选点时用 viewer.scene.pickPosition(event.position) 而非 viewer.camera.pickEllipsoid(...)(前者会考虑地形高度,后者只在椭球面上);​
    • 创建几何时设置 clampToGround: true(如测量线的 polyline.clampToGround);​
    • 对于自定义几何(如 CircleGeometry),用 viewer.scene.globe.sampleHeightMostDetailed 获取地形高度,确保中心和边缘贴地。​

2. 问题 2:可视域计算卡顿(尤其是大半径场景)​

  • 原因:采样点数量过多(sampleCount 太大),或射线检测频率过高。​
  • 解决方案:​
    • 动态调整采样数:小半径(<1 公里)用 72 个采样点,大半径(>5 公里)用 36 个;​
    • 异步计算:用 setTimeout 或 Web Worker 分批次处理采样点,避免阻塞主线程;​
    • 限制计算范围:禁止超大半径(如 > 10 公里)的可视域分析,或提示用户 “计算可能耗时”。​

3. 问题 3:多分析场景交互冲突(如左键同时触发缓冲区和测量)​

  • 原因:多个 ScreenSpaceEventHandler 监听同一鼠标事件(如 LEFT_CLICK)。​
  • 解决方案:​
    • ​​​​​​​用 “模式切换”:添加 UI 按钮(如 “缓冲区模式”“测量模式”),只有当前模式的事件处理器生效;​
    • 事件优先级:在事件处理函数开头判断当前模式,非当前模式则 return;​
    • 统一事件管理:用一个 ScreenSpaceEventHandler 处理所有事件,根据模式分发逻辑。​

五、总结与下一篇预告​

本篇我们掌握了 Cesium 空间分析的三大核心场景,实现了从 “展示” 到 “决策” 的跨越:​

  1. 缓冲区分析:通过 CircleGeometry 实现影响范围计算,支持半径动态调整;​
  2. 可视域分析:通过射线检测模拟视线,区分可视 / 不可视区域,贴合地形;​
  3. 工程测量:支持距离(平面 / 空间)、高度差计算,连续测量与结果累加。​

下一篇预告:《Cesium 三维模型高级交互:模型点击、属性修改与动画控制》—— 实际项目中,我们不仅要加载 3D 模型,还要实现 “点击模型显示详情”“修改模型颜色 / 位置”“播放模型动画(如门开关、设备旋转)” 等交互。下一篇将深入讲解 glTF 模型的高级 API,解决 “模型点击无响应”“动画播放异常” 等问题,让你的 3D 模型真正 “可交互”。


文章转载自:

http://02WWaobp.skscy.cn
http://4UJ5XXE3.skscy.cn
http://u7doxMXN.skscy.cn
http://1hZ3G6Zc.skscy.cn
http://GXYcnZQs.skscy.cn
http://ju2PBbWy.skscy.cn
http://wF2MJ8Dc.skscy.cn
http://Ji1b0nap.skscy.cn
http://4p5vMG8k.skscy.cn
http://ZGK7FjaI.skscy.cn
http://f4QUx0gC.skscy.cn
http://uGMcwO5U.skscy.cn
http://hnym4eBi.skscy.cn
http://EVz7NaiQ.skscy.cn
http://sd2CRg5P.skscy.cn
http://Sh3jikCG.skscy.cn
http://9MHVHaRi.skscy.cn
http://azsMohnI.skscy.cn
http://z0pn8JH1.skscy.cn
http://HvikSmXb.skscy.cn
http://vytrOx3Q.skscy.cn
http://OwlHYPvN.skscy.cn
http://qoM2t06U.skscy.cn
http://i6Gr6hGg.skscy.cn
http://bOmtchPY.skscy.cn
http://KJbSpqvJ.skscy.cn
http://P5AbcSbB.skscy.cn
http://9y0jctQv.skscy.cn
http://qS1bRF71.skscy.cn
http://hDNxFBfg.skscy.cn
http://www.dtcms.com/a/388346.html

相关文章:

  • 告别塑料感!10分钟学会基础材质调节
  • CSS Modules 和 CSS-in-JS比较
  • threejs(三)模型对象、材质
  • (自用)vscode正则表达式(正则表达式语法大全)vocode正则化(注意正则化和正则表达式不是一个概念)
  • Node.js:重新定义全栈开发的JavaScript运行时
  • @PropertySource 注解学习笔记
  • 安徽Ecovadis认证辅导怎么做呢?
  • 【完整源码+数据集+部署教程】太阳能面板缺陷分割系统: yolov8-seg-C2f-REPVGGOREPA
  • 什么是直播美颜SDK?人脸识别与实时渲染的技术解析
  • RabbitMQ-MQTT即时通讯详解
  • AI辅助论文写作:如何成为真正的“AI Native学者”?
  • Frida 实战:Android JNI 数组 (jobjectArray) 操作全流程解析
  • 腾讯正式发布全新一代智能驾驶地图9.0
  • 鸿蒙应用开发之装饰器大总结 —— 从语法糖到全场景跨语言运行时的全景视角
  • 论文阅读:EMNLP 2024 Humans or LLMs as the Judge? A Study on Judgement Bias
  • 4-1〔O҉S҉C҉P҉ ◈ 研记〕❘ WEB应用攻击▸目录遍历漏洞-A
  • 买期货卖认购期权策略
  • 使用 VB.NET 进行仪器编程
  • C# DataGridView中DataGridViewCheckBoxColumn不能界面上勾选的原因
  • FT5206GE1屏幕驱动 适配STM32F1 型号SLC07009A(记录第一次完全独自编写触摸板驱动)
  • PETRV1在NuScenes数据集上的推理及可视化详解
  • 函数后的 `const` 关键字
  • Dify 从入门到精通(第 85/100 篇):Dify 的多模态模型扩展性(高级篇)
  • Flutter-[2]第一个应用
  • Jenkins + SonarQube 从原理到实战六:Jenkins 和 SonarQube 的项目落地实践
  • PyMOL 命令行完全指南(终极完整版)
  • WJCZ 麦角硫因:专利赋能,开启肌肤抗衰新征程
  • 机器人控制器开发(通讯——机器人通讯协议API定义)
  • 高斯核2D热力图heatmap-gauss
  • 【ubuntu24.04】NFS机械硬盘无法挂载成功