map3d 道路渲染拆解:用 react-three-drei 的 Line 组件绘制城市路网的 3 个关键步骤
【免费下载链接】map3d🗺️ Generate City 3D map with R3F. Include building and road information.项目地址: https://gitcode.com/gh_mirrors/map/map3d
如果你想在浏览器里生成一张真实城市的 3D 地图,map3d 是一个很值得拆解的开源项目:它基于React-Three-Fiber(R3F)构建,通过 OpenStreetMap 数据自动生成带建筑物和道路信息的城市三维场景,并支持一键导出 GLB 模型。本文聚焦其中最有趣的部分——城市道路渲染:拆解 map3d 如何用react-three-drei的Line组件,把经纬度数据一步步变成屏幕上那条条绿色的三维路网。
🗺️ 先认识 Road 组件:路网的"总指挥"
map3d 的道路渲染全部集中在 src/three/Space.tsx 的Roads组件里。它接收一个矩形区域(由用户在二维地图上拖选产生),然后完成三件事:
| 步骤 | 做什么 | 对应源码位置 |
|---|---|---|
| ① 取数据 | 请求 Overpass API,获取区域内所有道路 | Space.tsx#L327-L343 |
| ② 投影坐标 | 经纬度 → 3D 场景本地坐标 | Space.tsx#L321-L325 |
| ③ 绘制线条 | 用 drei 的<Line>逐条渲染道路 | Space.tsx#L347-L365 |
下面逐个拆解。
第一步:用 Overpass API 查询城市道路数据
Roads组件里用useEffect监听选中的区域,一旦用户框选完成,就向 Overpass API(OpenStreetMap 的查询接口)发出一条查询:取出该矩形范围内所有带highway标签的 way(道路),并以out body geom拿到完整几何点序列。
const query = `[out:json][timeout:25];(way"highway";);out body geom;`;返回的数据直接存入roads状态,后续的渲染全部由 React 响应式驱动——这正是 R3F 项目的惯用思路:数据进状态,渲染交给声明式 JSX。
💡 选区范围由 SelectMap.tsx 的 Leaflet 地图提供,区域范围存放在 areaStore.ts 的 Zustand store 中,实现了二维选区与三维场景的解耦。
第二步:把经纬度投影到 3D 本地平面
三维场景无法直接理解经纬度,map3d 用了一个很经典的技巧——局部平面投影:
- 取区域中心点作为参考基准
refLat / refLng; - 经度差乘以
scale(51000)再乘cos(refLat)做纬度方向压缩修正; - 最后映射为
THREE.Vector3(x, 0.1, -y)。
function project(lat, lng) { const x = (lng - refLng) * scale * Math.cos((refLat * Math.PI) / 180); const y = (lat - refLat) * scale; return new THREE.Vector2(x, y); }两个值得注意的细节:
y轴固定为0.1:让道路整体略微抬升,避免与地面建筑模型发生深度冲突(z-fighting);-v.y取反:OpenStreetMap 的 y 轴向上,而 three.js 场景的 z 轴方向与之相反,取反后才能让地图"方向正确"地铺在场景里。
这套投影公式在 Space.tsx 的Space组件中同样出现一次——建筑和道路共用同一套坐标系统,保证两者严丝合缝地"叠"在一起。
第三步:用 drei 的 Line 组件逐条绘制道路
数据准备就绪后,渲染本身只有几行:
{roads.map((road, index) => { const points = road.geometry.map((pt) => { const v = project(pt.lat, pt.lon); return new THREE.Vector3(v.x, 0.1, -v.y); }); return ( <Line points={points} color="#33f516" lineWidth={1} userData={{ exportToGLB: true }} /> ); })}Line来自 react-three-drei,它是对 three.js 线条的 React 化封装:
points:直接把投影后的Vector3数组丢进去,drei 内部自动处理BufferGeometry和Line2材质,你完全不用碰底层几何体;lineWidth:屏幕空间线宽,保证道路在远景下依然清晰可见(普通LineBasicMaterial的线宽在高分屏上会细到看不见);color:那抹标志性的荧光绿#34f516,让路网在灰色建筑群中一目了然。
这正是Line组件最大的价值:用声明式 JSX 拿到"可编程线宽"的抗锯齿线条,一行配置换掉几十行 three.js 样板代码。
📦 隐藏细节:userData标记让道路可被导出为 GLB
注意每条<Line>都带了userData={{ exportToGLB: true }}。这是一个非常巧妙的设计:
Space.tsx 的Export函数在导出时遍历整个场景,只克隆打了这个标记的对象,再交给GLTFExporter打包——于是建筑物和道路会被完整导出,而天空、灯光、小车等装饰元素则被自动排除。
一句话总结这个模式:用 userData 做"导出白名单",让三维场景与资产导出解耦。
🚀 快速上手本地运行
如果你想亲手跑一遍这个项目(依赖清单见 package.json):
git clone https://gitcode.com/gh_mirrors/map/map3d cd map3d npm install npm run dev打开后在地图上框选一个小区域 → Next Step 拉取建筑数据 → 进入 3D 场景即可看到绿色路网;右下角的Export GLB按钮能把道路与建筑打包成三维模型文件。
写在最后
map3d 的道路渲染看似简单,实际浓缩了三维地图开发的三个核心环节:地理数据获取(Overpass API)→ 局部平面投影(经纬度转场景坐标)→ 声明式线条渲染(drei Line)。三步走下来,一条完整的"路网生产线"就搭起来了。
想继续深挖?推荐顺着这份阅读路径探索:
- 道路渲染主体:src/three/Space.tsx
- 区域状态管理:src/state/areaStore.ts
- 二维选区交互:src/components/map/SelectMap.tsx
掌握这套"取数 → 投影 → 渲染"的方法论,你再去做数字孪生、无人机测绘可视化、GPS 轨迹标记这类项目时,会发现自己已经站在了正确的起点上。🚀
【免费下载链接】map3d🗺️ Generate City 3D map with R3F. Include building and road information.项目地址: https://gitcode.com/gh_mirrors/map/map3d
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考