ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从NASA数据到交互式3D网格:地球重力场可视化完整教程

从NASA数据到交互式3D网格:地球重力场可视化完整教程 这次我们来看 NASA 公开的一张地球 3D 模型。它不是我们熟悉的蓝色星球而是一个被画得“坑坑洼洼”的球体像一颗不规则的土豆。NASA 管这种效果叫 Earth Is a Lumpy Mess翻译过来就是“地球是个高低不平的鬼东西”。第一次看到这张图你可能会觉得是数据出了问题但恰恰相反它比普通卫星影像更接近地球的真实物理形态。这里先给结论这张图不是地形高程图而是地球重力场模型的可视化。它把大地水准面相对参考椭球面的起伏放大到了肉眼可见的程度。对于 GIS 开发者、三维可视化工程师、地球科学专业的学生以及想做科普内容的人来说这套工作流非常值得复刻。本文会带你从 NASA Earthdata 获取公开数据用 Python 生成三维网格在 Blender 里渲染出效果再发布成网页端可交互的 3D 模型。整个流程不依赖高配显卡普通办公电脑就可以完成小规模预览。1. 核心能力速览能力项说明项目类型地球重力场 / 大地水准面 3D 可视化数据源NASA Earthdata 公开数据常见任务包括 GRACE/GRACE-FO 重力卫星产品、静态重力场模型等核心功能将大地水准面起伏、重力异常数据转换为可交互 3D 网格硬件要求CPU 即可无强 GPU 依赖复杂网格建议 8GB 以上内存显存占用不适用默认走 CPU 网格生成WebGL 展示用浏览器自动管理显存支持平台Windows / Linux / macOS常用工具Python、Blender、Three.js / CesiumJS启动方式脚本执行、Blender 工程、Web 页面访问接口能力Earthdata API 可下载批量数据但需要注册账号与 token批量任务支持可遍历多年度 GeoTIFF 批量生成网格输出格式GeoTIFF → OBJ / GLB / PLY → Blender 渲染 / 网页显示适合场景科普展览、教学演示、科研配图、WebGIS 可视化2. 从“Lumpy Mess”说起模型在表达什么要理解这个模型先要分清三组概念地球地形、参考椭球体、大地水准面。我们平时看到的地球照片表现的是地形起伏比如喜马拉雅山、马里亚纳海沟这些是固体地球表面的高低。参考椭球体则是一个理想化的数学球面用来近似地球形状GPS 定位和地图投影都会用到它。而大地水准面是指不考虑潮汐、风浪等因素时全球静止海平面在陆地下的延伸面。由于地球内部质量分布不均匀大地水准面并不是一个光滑的椭球面而是有高低起伏的。NASA 这张 3D 模型正是把大地水准面和参考椭球面的差值也就是“大地水准面高”或“重力异常”作为模型表面的径向变形画了出来。别小看这些起伏。从全球范围看大地水准面高相对参考椭球面的偏差通常在百米量级和地球 6371 公里级别的半径相比微不足道。所以在可视化时必须把径向方向放大多倍通常要放大数百到上万倍最终才能得到“土豆”一样的视觉效果。这个“lumpy mess”的意义在于它直观展示了地球内部质量分布的不均匀性。地壳厚度变化、地幔对流、大型山脉、深海沟、冰盖消融、地下水变化都会在重力场里留下痕迹。理解这一点之后再看 NASA 的图就不会觉得它只是夸张的科普图而是一个可以用于科学解释的物理模型。3. 适用场景与合规边界这套可视化方案适合以下几类人地理信息科学或地球物理相关专业的学生用来理解大地水准面和重力场的概念。三维可视化工程师想把遥感或地球物理数据从二维 GeoTIFF 变成三维网格。科普内容创作者用来制作地球重力场、卫星重力任务相关的视频和图文。WebGIS 开发者想在地图产品中嵌入地球重力场模型作为数据叠加层。但它不适合用于高精度工程测量。大地水准面模型虽然能表达重力场分布但不能直接替代国家或地区的官方高程基准。做工程建设项目、无人机航测高程转换时应该使用当地官方发布的大地水准面模型而不是把可视化模型当作精度依据。同时要注意数据合规问题。NASA 公开数据通常可以用于教学、科研和非商用展示但发布成果时最好注明数据来源例如 GRACE/GRACE-FO 任务产品或 EGM2008 模型。如果要做商业项目需要在 NASA Earthdata 或具体数据产品的许可页面确认使用范围。涉及其他国家或地区的重力数据也要遵循对应机构的授权要求。还有一个边界本文流程呈现的是经过放大处理的“可视化模型”不是真实形状公开传播时最好明确标注“垂直夸大显示”避免被误解为地球真实几何形状。4. 数据获取与环境准备开始之前先准备基础环境。推荐的软件组合是 Python GDAL 生态 Blender 前端 Three.js。Python 用于处理 GeoTIFF、生成网格Blender 负责渲染出图Three.js 负责网页端交互。Python 版本建议使用 3.9 以上。核心依赖包括 rasterio、numpy、trimesh、matplotlib。如果你只做渲染不追求 Python 内预览matplotlib 可以省。安装命令如下pip install rasterio numpy trimesh matplotlib在 Linux 系统上如果 rasterio 安装失败可能需要先安装底层 GDAL 库sudo apt update sudo apt install gdal-bin libgdal-devWindows 用户建议直接使用 conda 环境安装可以避免不少编译问题conda create -n gravity python3.11 conda activate gravity conda install -c conda-forge rasterio numpy trimesh matplotlib除了代码环境还需要从 NASA Earthdata 下载数据。访问 NASA Earthdata 门户后需要注册一个 Earthdata Login 账号。搜索关键词可以参考这些geoid undulationearth gravitational modelgravity anomalyGRACE/GRACE-FO gravity field数据格式优先选择 GeoTIFF因为这个格式可以直接被 rasterio 读取坐标系和范围信息都已经嵌入文件头。如果下载到的是 netCDF 或 HDF5 格式也没有关系需要额外用 xarray 做一次转换但本文流程以 GeoTIFF 为例。下载时注意数据分辨率和覆盖范围。全球范围的数据通常比较大从几十 MB 到几百 MB 不等。第一次测试不用追求高分辨率选一个步长可以覆盖全球的 GeoTIFF 即可等流程跑通后再换高分辨率数据。5. 用 Python 把重力数据变成 3D 网格拿到 GeoTIFF 文件后第一步是读取数据并观察数值范围。新建一个convert_geoid.py写入下面的代码import numpy as np import rasterio import trimesh def read_geoid(path): with rasterio.open(path) as src: data src.read(1, maskedTrue).filled(np.nan) transform src.transform crs src.crs return data, transform, crs def build_mesh(data, transform, step8, radius6371.0, vertical_scale1.0): data: 大地水准面高或重力异常值 transform: rasterio transform step: 降采样步长控制顶点密度 radius: 球体半径单位 km vertical_scale: 垂直放大系数 sub data[::step, ::step] nrows, ncols sub.shape rows np.arange(0, data.shape[0], step) cols np.arange(0, data.shape[1], step) col_grid, row_grid np.meshgrid(cols, rows) lon, lat rasterio.transform.xy(transform, row_grid, col_grid) lon np.asarray(lon, dtypenp.float64) lat np.asarray(lat, dtypenp.float64) lon_r np.deg2rad(lon) lat_r np.deg2rad(lat) height np.nan_to_num(sub, nan0.0) rr radius height * vertical_scale x rr * np.cos(lat_r) * np.cos(lon_r) y rr * np.cos(lat_r) * np.sin(lon_r) z rr * np.sin(lat_r) vertices np.stack([x.ravel(), y.ravel(), z.ravel()], axis1) faces [] for i in range(nrows - 1): for j in range(ncols - 1): idx i * ncols j faces.append([idx, idx 1, idx ncols]) faces.append([idx 1, idx ncols 1, idx ncols]) mesh trimesh.Trimesh(verticesvertices, facesnp.array(faces)) return mesh if __name__ __main__: data, transform, crs read_geoid(geoid.tif) print(数据范围, np.nanmin(data), np.nanmax(data)) print(分辨率, data.shape) mesh build_mesh(data, transform, step8, vertical_scale0.005) mesh.export(earth_gravity.obj) mesh.export(earth_gravity.glb) print(网格顶点数, len(mesh.vertices))这段代码做的事情很直接。第一步读取 GeoTIFF 和仿射变换参数第二步用rasterio.transform.xy把像素坐标换算成经纬度第三步把经纬度转换为三维球面坐标并叠加当前数据值作为径向变形第四步生成三角形网格并导出 OBJ 和 GLB。这里特别要说明的是vertical_scale参数。如果你的数据是大地水准面高单位是米那么数值范围可能只在 -100 到 100 左右直接作用在 6371 公里的球体上肉眼几乎看不出变化。你需要根据导出的球体显示尺寸把变形量放大几百到几千倍。上面代码里的0.005只是示例实际需要根据自己的数据范围和渲染效果调整。另外要注意不推荐把重力异常数据 mGal 直接当作几何位移。重力异常单位是加速度单位不是长度单位。如果要把它展示成模型变形要么先转换成大地水准面高要么仅用颜色映射的方式在表面着色避免在物理含义上出错。6. 在 Blender 中渲染出效果图用 Python 导出earth_gravity.obj或earth_gravity.glb之后接下来的核心工作交给 Blender。先打开 Blender新建一个默认工程。然后删除默认的立方体、灯光和相机选择菜单里的 File → Import导入刚才生成的 OBJ 或 GLB。导入后模型可能会非常大也可能非常小先用快捷键 N 打开侧边栏在 Transform 面板里统一缩放。为了让效果图更接近 NASA 那类展示效果可以按下面几步调整给模型添加一个材质球使用 Principled BSDF。如果想要表面颜色表示重力场大小需要把顶点色从网格导入。OBJ 不包含顶点颜色建议使用 GLB 或 PLY 格式导入。在 Shading 编辑器里添加 Vertex Color 节点连到 Base Color 上。添加一个 Sun Light亮度调到 3 到 5 左右。相机使用正焦投影对准模型中心构图才更像科学示意图。渲染输出建议设置成 PNG 或 OpenEXR分辨率 2K 到 4K 足够。渲染完成后重点验证几个标准模型表面是否出现了凹凸起伏而不是一个光滑球体。高低起伏区域是否和数据原始值分布一致比如重力高值区和低值区是否对应。旋转模型确认没有破洞、法线翻转或局部尖刺。如果模型有面片朝向问题回到 Edit Mode全选网格后使用 Mesh → Normals → Recalculate Outside。Blender 的优势在于后续调整方便。你可以在同一个工程里做多种配色方案也可以叠加一个真实地球纹理作对比让读者直观看到“光滑球体”和“重力场模型”的差异。7. 发布成可交互的 Web 3D 模型如果只是出一张静态图Blender 已经够用。但如果你想放到网页上让读者自由旋转、缩放观察推荐使用 Three.js 直接加载 GLB 文件。先创建一个简单的 HTML 页面引用 Three.js 和 OrbitControls 控制器!DOCTYPE html html langzh-CN head meta charsetUTF-8 / title地球重力场 3D 模型/title style body { margin: 0; overflow: hidden; background: #111; } /style /head body div idcontainer/div script typeimportmap { imports: { three: https://unpkg.com/three0.160.0/build/three.module.js, three/addons: https://unpkg.com/three0.160.0/examples/jsm/ } } /script script typemodule import * as THREE from three; import { OrbitControls } from three/addons/controls/OrbitControls.js; import { GLTFLoader } from three/addons/loaders/GLTFLoader.js; const scene new THREE.Scene(); const camera new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); camera.position.set(4, 2, 6); const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.getElementById(container).appendChild(renderer.domElement); const controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; scene.add(new THREE.AmbientLight(0xffffff, 1.2)); const loader new GLTFLoader(); loader.load(earth_gravity.glb, (gltf) { scene.add(gltf.scene); animate(); }); function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } /script /body /html把earth_gravity.glb和这个 HTML 文件放在同一个目录然后启动一个本地静态服务即可访问。如果你有 Python 环境可以直接在目录下运行python -m http.server 8080浏览器打开http://127.0.0.1:8080就能看到可交互的地球重力场模型。验证是否成功主要看三点模型是否正常加载鼠标是否能够旋转和缩放页面控制台有没有 GLB 加载报错。如果你已经部署了 CesiumJS 服务也可以把 GLB 转成 3D Tiles 后叠加在地球表面上。这样就能把重力场模型和真实地形、影像数据放在同一个场景里形成更强的空间对比。8. 批量处理、性能观察与资源占用实际项目中很少只处理一个 GeoTIFF。比如你想对比不同年份的 GRACE/GRACE-FO 数据或者想处理多个区域的模型就需要批量流程。最简单的批量方式是遍历目录下的所有 GeoTIFF逐个生成 OBJ 或 GLB同时写日志。示例片段import pathlib import traceback input_dir pathlib.Path(data) output_dir pathlib.Path(output) output_dir.mkdir(exist_okTrue) for tif_path in input_dir.glob(*geoid*.tif): out_name tif_path.stem .glb out_path output_dir / out_name try: data, transform, crs read_geoid(str(tif_path)) mesh build_mesh(data, transform, step16, vertical_scale0.005) mesh.export(str(out_path)) print(f[OK] {tif_path.name} - {out_name}) except Exception as e: print(f[FAIL] {tif_path.name}: {e}) traceback.print_exc()批量处理时最需要关注的是网格顶点数。顶点数量由原始 GeoTIFF 分辨率和降采样步长决定。假设数据有 2160 行、4320 列步长为 8那么实际生成的网格行列数是 270 × 540顶点数大约是 14.58 万对应三角形面数约 29 万左右。如果步长缩小到 4顶点数会变成 58.3 万内存占用也随之显著上升。观察资源占用时Windows 可以使用任务管理器Linux 可以使用top或htop。由于默认流程没有 GPU 推理环节显存占用不是瓶颈CPU 和内存才是重点。如果单次处理出现内存溢出优先调整step参数把网格密度降下来。比如从 8 改成 16顶点数会变成原来的四分之一内存压力会小很多。另外一个容易忽略的问题是输出目录管理。建议把原始 GeoTIFF、中间网格、最终渲染文件分目录存放并且命名规则带上数据版本和年份。否则多个数据文件堆在一起后续很容易搞混。9. 常见问题与排查方法问题现象可能原因排查方式解决方案NASA Earthdata 下载界面打不开或登录失败账号未注册、token 未配置检查 Earthdata Login 账号状态提前注册账号在个人资料中生成下载 tokenrasterio 读取 GeoTIFF 报错GDAL 版本不匹配或文件损坏检查文件大小和格式打印 transform用 conda 重装 rasterio或补装 gdal-bin模型导出后看起来是光滑球体数据值太小vertical_scale 未放大打印数据 min/max 和网格 z 轴范围增大 vertical_scale直到起伏肉眼可见模型局部出现尖刺或破洞原始数据有 NaN 或异常值检查数据数组中的 NaN 比例用 nan_to_num 或中值滤波处理异常值Blender 导入后法线方向混乱网格面片朝向不一致进入 Edit Mode 查看法线Recalculate OutsideWeb 页面加载 GLB 白屏跨域问题或模型过大打开浏览器控制台查看报错本地使用 http.server远程需配置 CORS内存占用过高导致卡死网格顶点数过大观察任务管理器内存占用量提高降采样步长降低网格分辨率同一区域不同年份数据差异不明显数据单位或投影不一致检查元数据和单位说明统一使用大地水准面高数据并做标准化如果遇到输出模型和真实地球形状看起来差距过大不要急着调代码。先确认你下载的数据到底是什么字段。本文的核心流程面向“大地水准面高”或“经过处理的重力场网格数据”这些字段可以直接作为径向偏移。如果是原始重力异常测量值先做单位转换或只做颜色映射。10. 最佳实践与下一步最后整理几条实战建议。第一第一次跑通全流程时不要用最高分辨率。先下载一个全球范围、中等分辨率的 GeoTIFF用 step16 或更大的步长生成网格确认整套工具链没有报错再逐步提高数据分辨率。第二保留一份最小的可运行配置。把 Python 脚本、参数、模型文件路径固化下来后续新增数据时只需要改输入目录和输出目录不容易出错。第三垂直放大系数要写进文档。同样一个 GeoTIFF不同放大系数会带来完全不同的视觉观感。如果不记录参数后续复现不同年份数据时很容易出现比例不一致的情况。第四公开发布研究成果或科普内容时标注清楚数据来源和可视化方式。NASA、GRACE/GRACE-FO、相关静态重力场模型都需要说明。对于“放大后的变形”尽量注明“vertical exaggeration”这是科学可视化领域的常见做法也能避免读者产生误解。第五批量任务一定要加日志和失败重试机制。多个文件同时处理时单条失败不影响整个批次但需要知道哪条失败、失败原因是什么。最简单的方式是像上面代码那样把成功和失败分别打印到控制台同时写进一个日志文件。如果你还想继续深入下一步可以考虑两个方向。第一个方向是把时间序列数据做成动画比如用 GRACE/GRACE-FO 的月度重力场数据展示特定区域地下水储量变化对重力场的扰动。第二个方向是接入 CesiumJS 的 3D Tiles把重力场模型叠加到全球影像地图上形成一个更完整的地球科学数据平台。从 NASA 的“lumpy mess”到本地跑通一个可交互的 3D 模型整个过程其实不复杂。核心无非是数据获取、网格生成、渲染发布三步。建议你先拿一份数据跑通最小流程再按照实际需求扩展批量处理和 Web 展示。这套能力以后做地震数据、水文数据、行星重力场可视化时也可以复用值得收藏备用。
返回列表