news 2026/10/4 5:14:15

map3d 道路渲染拆解:用 react-three-drei 的 Line 组件绘制城市路网的 3 个关键步骤

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
map3d 道路渲染拆解:用 react-three-drei 的 Line 组件绘制城市路网的 3 个关键步骤

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); }

两个值得注意的细节:

  1. y轴固定为0.1:让道路整体略微抬升,避免与地面建筑模型发生深度冲突(z-fighting);
  2. -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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/4 5:13:16

从插件机制到报错排查:插件加载失败实战指南

一个多月前&#xff0c;我接手一个前端项目&#xff0c;首次运行构建命令就被一行报错砸懵了&#xff1a;failed to load plugins web boot: 2 entries did not activate那会儿我连“plugins”在这个项目里指什么都没搞明白&#xff0c;只看到一堆“did not activate”&#xf…

作者头像 李华
网站建设 2026/10/4 5:13:10

插件机制深度解析:从Webpack到IAR,彻底搞定plugins加载失败

你有没有发现&#xff0c;这两年技术圈里几乎所有难缠的问题&#xff0c;最后都绕到同一个词上——plugins。从IAR里编译器的扩展工具&#xff0c;到前端打包时各种报错&#xff0c;再到MusicFree这类音乐App的扩展源&#xff0c;插件机制几乎无处不在。我最近在排查一个"…

作者头像 李华
网站建设 2026/10/4 5:12:36

插件机制全解析:从加载激活到故障排查

1. 三类热搜词背后&#xff0c;是同一套宿主与插件模型最近我整理技术社区的搜索热词时&#xff0c;发现 plugins 这个词长期占着一个很显眼的位置。进一步去看细节&#xff0c;搜这个词的人大概分成三类&#xff1a;一类在问"IAR plugins 是干什么的"&#xff0c;一…

作者头像 李华
网站建设 2026/10/4 5:10:52

OpenShell终端工作台:分屏、SSH会话与快速命令实战

1. OpenShell 到底解决了什么问题做开发或者运维的朋友&#xff0c;应该都有过这种经历&#xff1a;Windows 上工作&#xff0c;终端开了七八个窗口&#xff0c;任务栏里挤成一团。想找一个跑着日志的窗口&#xff0c;鼠标点半天没找着&#xff1b;想同时看本地编译和远程服务器…

作者头像 李华
网站建设 2026/10/4 5:09:54

Java学生选课管理系统:从数据库设计到Spring Boot部署的完整实战

简介&#xff1a;这份资源面向高校计算机相关专业学生与Java Web初学者&#xff0c;提供一套可直接运行的学生选课管理系统完整项目&#xff0c;帮助读者理解从需求分析到部署上线的全流程开发思路。压缩包共5个文件&#xff0c;包含2个zip源码与数据库脚本、2个mp4部署演示视频…

作者头像 李华
网站建设 2026/10/4 5:09:37

插件加载失败?从entries did not activate到IAR与MusicFree排查实战

用了这么多年的各种软件和开发环境&#xff0c;我越来越觉得plugins这个词背后藏着的东西&#xff0c;比大多数教程里写的都要实在。它不是一个“高级功能开关”&#xff0c;而是整个软件生态能够活起来的关键机制。最近我连续碰到了两条有点吓人的报错&#xff0c;一条是faile…

作者头像 李华