las点云数据转3dtiles实现(c++)
一、需求将las格式的点云数据转换为cesium能加载渲染的3dtiles格式(pnts)。二、具体实现步骤2.1 依赖开源库gdal库点云数据多为投影坐标系cesium渲染需要的是地理坐标系所以需要gdal库进行坐标转换。LASzip库读写las的库。Qt 、osg等库不是必须可以不用。我的代码其他地方需要这两个库所以就用上了。2.2点云读取使用LASzip库读取点云到内存。这里在具体程序实现的时候需要注意分块策略因为有些点云数据动则几十个G如果不进行分块处理对硬件压力太大了。具体的分块思路应该是现对点云数据构建索引我代码中用的四叉树构建的点云索引然后根据点云数量及范围对点云进行分块每次处理的时候针对某一块进行处理。而且分块后方便后续并行加速处理。2.3 lod构建第一步根据2.2中的分块结果读取其中的某一块点云数据统计其点个数和外界包围盒。第二步判断统计的点云数据个数是否大于指定的阈值(该值是3dtiles中每个pnts文件的最大点数量)。如果小于指定阈值则该块不需要进行分块将该块中的全部点写入pnts。如果小于指定阈值则以该块外界包围盒(矩形)中心为分界点创建四个子节点块该块则被称为根节点。第三步针对第二步中得到的根节点块和子节点块继续按照步骤二中的规则对四个子节点进行分块。具体是否分块还是按照节点中的点云数与指定阈值的大小来决定。以此循环直到所有的节点都不能再分块为止。这样就完成了整个点云的lod构建。上述方法描述起来比较拗口其实用程序实现的时候只需要进行递归调用即可实现部分实现代码如下QSharedPointerscially::OSGIndexNode Build3dtiles(const std::vectorPointCI* pointSet, std::vectorunsigned int pointIndex, osg::BoundingBox boundingBox, osg::BoundingBox boundingBoxLevel0, const std::string saveFilePath, const std::string strBlock, unsigned int level, unsigned int childNo, ExportMode exportMode, const scially::SpatialTransform transform, const scially::TileStorage storage) { // filename of self, left, right std::string saveFileName, pageName1, pageName2, pageName3, pageName4; if (level 0) { saveFileName strBlock; } else { char tmpSaveFileName[100]; sprintf(tmpSaveFileName, %s%s%d%s%d, strBlock.c_str(), _L, level, _, childNo); saveFileName.assign(tmpSaveFileName); saveFileName /*saveFilePath / */ saveFileName; } //********** 1 2 ********** //********** 3 4 ********** char tmpPageName1[100], tmpPageName2[100], tmpPageName3[100], tmpPageName4[100]; sprintf(tmpPageName1, %s%s%d%s%d, strBlock.c_str(), _L, level 1, _, childNo * 4); pageName1.assign(tmpPageName1); sprintf(tmpPageName2, %s%s%d%s%d, strBlock.c_str(), _L, level 1, _, childNo * 4 1); pageName2.assign(tmpPageName2); sprintf(tmpPageName3, %s%s%d%s%d, strBlock.c_str(), _L, level 1, _, childNo * 4 2); pageName3.assign(tmpPageName3); sprintf(tmpPageName4, %s%s%d%s%d, strBlock.c_str(), _L, level 1, _, childNo * 4 3); pageName4.assign(tmpPageName4); // handle leaf case if (pointIndex.size() _maxPointNumPerOneNode || level _maxTreeLevel) { //double rangeValue boundingBox.radius() / scially::SPLIT_PIXEL / 32; double geometricError scially::osgBoundingSize(boundingBoxLevel0) / scially::SPLIT_PIXEL / 16 / (pow(2.0, level)); auto pntsNode QSharedPointerscially::PntsTile::create(); pntsNode-tileName() QString::fromStdString(saveFileName); pntsNode-fileName() QString::fromStdString(strBlock); pntsNode-geometricError() geometricError; pntsNode-tileFolder() QString::fromStdString(saveFilePath); pntsNode-m_suffix .pnts; pntsNode-m_refine ADD; pntsNode-boundingBox() boundingBox; QString outFileName pntsNode-relativeNodePath(pntsNode-m_suffix); QByteArray pntsBuffer; osg::Vec3d tileCenter transform.transform(boundingBox.center()); if (write2Pnts(pointSet, pointIndex, osg::Vec3d(0, 0, 0), pntsBuffer)) { if (!storage.saveFile(outFileName, pntsBuffer)) { qWarning(saveFile pnts failed!); return nullptr; } return pntsNode; } else { qWarning(write2Pnts failed!); return nullptr; } } // prepare box AxisInfo midAxisInfo; osg::BoundingBox boundingBox1; osg::BoundingBox boundingBox2; osg::BoundingBox boundingBox3; osg::BoundingBox boundingBox4; midAxisInfo FindMidAxis(boundingBox, boundingBox1, boundingBox2, boundingBox3, boundingBox4); // split self, child1, child2, child3, child4 float interval (float)pointIndex.size() / (float)_maxPointNumPerOneNode; int count -1; std::vectorunsigned int selfPointSetIndex; std::vectorunsigned int child1PointSetIndex; std::vectorunsigned int child2PointSetIndex; std::vectorunsigned int child3PointSetIndex; std::vectorunsigned int child4PointSetIndex; for (int i 0; i pointIndex.size(); i) { int tmp int((float)i / interval); if (tmp count selfPointSetIndex.size() _maxPointNumPerOneNode) { count tmp; selfPointSetIndex.push_back(pointIndex[i]); } else { PointCI tmpPoint pointSet-at(pointIndex[i]); if (tmpPoint.P[0]midAxisInfo.midx tmpPoint.P[1]midAxisInfo.midy) { child1PointSetIndex.push_back(pointIndex[i]); } else if (tmpPoint.P[0] midAxisInfo.midx tmpPoint.P[1] midAxisInfo.midy) { child2PointSetIndex.push_back(pointIndex[i]); } else if (tmpPoint.P[0] midAxisInfo.midx tmpPoint.P[1] midAxisInfo.midy) { child3PointSetIndex.push_back(pointIndex[i]); } else { child4PointSetIndex.push_back(pointIndex[i]); } } } // export //double rangeValue boundingBox.radius() / scially::SPLIT_PIXEL / 32; double geometricError scially::osgBoundingSize(boundingBoxLevel0) / scially::SPLIT_PIXEL / 16 / (pow(2.0, level)); auto pntsNode QSharedPointerscially::PntsTile::create(); pntsNode-tileName() QString::fromStdString(saveFileName); pntsNode-fileName() QString::fromStdString(strBlock); pntsNode-geometricError() geometricError; pntsNode-tileFolder() QString::fromLocal8Bit(saveFilePath.c_str()); pntsNode-m_suffix .pnts; pntsNode-m_refine ADD; pntsNode-boundingBox() boundingBox; QString outFileName pntsNode-relativeNodePath(pntsNode-m_suffix); QByteArray pntsBuffer; osg::Vec3d tileCenter transform.transform(boundingBox.center()); if (write2Pnts(pointSet, selfPointSetIndex, osg::Vec3d(0, 0, 0), pntsBuffer)) { if (!storage.saveFile(outFileName, pntsBuffer)) { qWarning(saveFile pnts failed!); return nullptr; } } else { qWarning(write2Pnts failed!); return false; } // recursive left if (child1PointSetIndex.size()) { auto n Build3dtiles(pointSet, child1PointSetIndex, boundingBox1, boundingBoxLevel0, saveFilePath, strBlock, level 1, childNo * 4, exportMode, transform, storage); if (n) { pntsNode-append(n); } child1PointSetIndex.swap(std::vectorunsigned int()); } if (child2PointSetIndex.size()) { auto n Build3dtiles(pointSet, child2PointSetIndex, boundingBox2, boundingBoxLevel0, saveFilePath, strBlock, level 1, childNo * 4 1, exportMode, transform, storage); if (n) { pntsNode-append(n); } child2PointSetIndex.swap(std::vectorunsigned int()); } if (child3PointSetIndex.size()) { auto n Build3dtiles(pointSet, child3PointSetIndex, boundingBox3, boundingBoxLevel0, saveFilePath, strBlock, level 1, childNo * 4 2, exportMode, transform, storage); if (n) { pntsNode-append(n); } child3PointSetIndex.swap(std::vectorunsigned int()); } if (child4PointSetIndex.size()) { auto n Build3dtiles(pointSet, child4PointSetIndex, boundingBox4, boundingBoxLevel0, saveFilePath, strBlock, level 1, childNo * 4 3, exportMode, transform, storage); if (n) { pntsNode-append(n); } child4PointSetIndex.swap(std::vectorunsigned int()); } return pntsNode; }2.4 写pnts按照2.3构建起点云的lod后我们得到的是一个包含多个节点的四叉树四叉树中每个节点代表一块点数量不超过指定阈值的点云数据。我们要将所有的节点写入pnts文件。例如根节点点云数量为s指定的阈值是t则我们在写根节点的pnts文件时需要从s中均匀的抽取出t个点。只需要计算出间隔取样距离即可float interval s/t;按interval间隔取点写入pnts。下面的代码是单个pnts文件的写入实现完全按照cesium官方给出的pnts格式进行写入的。写颜色的时候有强度渲染和RGB渲染之分。下面是写pnts代码截取。//写pnts代码 bool write2Pnts(const std::vectorPointCI* pointSet, std::vectorunsigned int pointIndex, const osg::Vec3d center, QByteArray buffer) { if (pointIndex.size() 0) { return false; } osg::ref_ptrosg::Vec3Array pointArray new osg::Vec3Array; osg::ref_ptrosg::Vec4Array colorArray new osg::Vec4Array; for (std::vectorunsigned int::iterator i pointIndex.begin(); i ! pointIndex.end(); i) { PointCI tmpPoint pointSet-at(*i); pointArray-push_back(tmpPoint.P); if (_colorMode ColorMode::Debug) { colorArray-push_back(_colorBar[0]); } else if (_colorMode ColorMode::RGB) { colorArray-push_back( osg::Vec4(Color8BitsToFloat(tmpPoint.C[0]), Color8BitsToFloat(tmpPoint.C[1]), Color8BitsToFloat(tmpPoint.C[2]), 1.f)); } else if (_colorMode ColorMode::IntensityGrey) { colorArray-push_back(_colorBar[tmpPoint.I]); } else if (_colorMode ColorMode::IntensityBlueWhiteRed) { colorArray-push_back(_colorBar[tmpPoint.I]); } else if (_colorMode ColorMode::IntensityHeightBlend) { float x (tmpPoint.P.z() - this-_boundingBoxGlobal.zMin()) / (this-_boundingBoxGlobal.zMax() - this-_boundingBoxGlobal.zMin()); //// sigmoid //x (x - 0.5) * 4; //x 1. / (1. exp(-5 * x)); int index x * 255; index std::max(0, std::min(255, index)); osg::Vec4 color _colorBar[index]; color color * 0.9 color * (tmpPoint.I / 255.) * 0.1; color.w() 1.0; colorArray-push_back(color); } } QDataStream dataStream(buffer, QIODevice::WriteOnly); dataStream.setByteOrder(QDataStream::LittleEndian); // TODO: batch table // feature table QByteArray featureTableJsonByte; { QJsonObject featureTableJson; featureTableJson[POINTS_LENGTH] (int)pointArray-size(); if (center ! osg::Vec3d(0, 0, 0)) { featureTableJson[RTC_CENTER] QJsonArray({ center.x(), center.y(), center.z() }); } QJsonObject posjson; posjson.insert(byteOffset, 0); featureTableJson[POSITION] posjson; if (colorArray-size() ! 0) { QJsonObject colorjson; long long length pointArray-size() * 3 * 4; colorjson.insert(byteOffset, length); featureTableJson[RGB] colorjson; } featureTableJsonByte QJsonDocument(featureTableJson) .toJson(QJsonDocument::Compact); while (featureTableJsonByte.size() % 8 ! 0) { featureTableJsonByte.append( ); } } QByteArray featureTableBinary; { std::ostringstream oss; for (int i 0; i pointArray-size(); i) { osg::Vec3f pt pointArray-at(i); oss.write((char*)(pt[0]), sizeof(float)); oss.write((char*)(pt[1]), sizeof(float)); oss.write((char*)(pt[2]), sizeof(float)); } for (int i 0; i colorArray-size(); i) { osg::Vec4f color colorArray-at(i); uint8_t r color[0] * 255; uint8_t g color[1] * 255; uint8_t b color[2] * 255; oss.write((char*)(r), sizeof(uint8_t)); oss.write((char*)(g), sizeof(uint8_t)); oss.write((char*)(b), sizeof(uint8_t)); //oss.write((char*)(colorn[3]), sizeof(int)); } oss.flush(); std::string str oss.str(); featureTableBinary.append(str.data(), str.size()); while (featureTableJsonByte.size() % 8 ! 0) { featureTableJsonByte.append( ); } } uint32_t version 1; uint32_t featureTableJSONByteLength featureTableJsonByte.size(); uint32_t featureTableBinaryByteLength featureTableBinary.size(); uint32_t batchTableJSONByteLength 0; uint32_t batchTableBinaryByteLength 0; uint32_t headerLength 28 featureTableJSONByteLength featureTableBinaryByteLength batchTableJSONByteLength batchTableBinaryByteLength; //header { dataStream.writeRawData(pnts, 4); dataStream version; dataStream headerLength; dataStream featureTableJSONByteLength; dataStream featureTableBinaryByteLength; dataStream batchTableJSONByteLength; dataStream batchTableBinaryByteLength; } dataStream.writeRawData(featureTableJsonByte.data(), featureTableJsonByte.size()); dataStream.writeRawData(featureTableBinary.data(), featureTableBinary.size()); return true; }2.5写json按照3dtiles数据格式要求需要有一个总的tileset.json文件然后每个tile中需要有根节点的json文件。

相关新闻

募资295亿,估值2-4万亿!长鑫科技上市,“造富”员工却引加班争议

募资295亿,估值2-4万亿!长鑫科技上市,“造富”员工却引加班争议

295亿募资,长鑫科技开启科创板申购 7月16日,长鑫科技将正式开启科创板新股申购,此次计划募资295亿元,是今年A股募资规模最大、科创板历史第二大IPO项目,仅次于中芯国际。市场对其估值预期在2万亿到3万亿元,…

2026/7/15 15:04:13阅读更多 →
从单体到微服务的演进:Java外卖霸王餐系统的服务拆分边界与通信协议选型

从单体到微服务的演进:Java外卖霸王餐系统的服务拆分边界与通信协议选型

从单体到微服务的演进:Java外卖霸王餐系统的服务拆分边界与通信协议选型 当外卖霸王餐CPS(Cost Per Sale)业务从日订单量几百增长到数万时,最初那个“小而美”的单体Java应用往往会迅速演变成难以维护的“巨石”。代码耦合、数据库…

2026/7/15 15:04:13阅读更多 →
android-性能-分析

android-性能-分析

以下是Android性能分析的完整指南,涵盖核心工具、优化方向及实操方法,适用于2025年开发环境: ⚙️ 一、性能分析工具分类 1. 官方工具链 工具用途关键能力版本适配Android Profiler实时监测CPU/内存/网络/能耗火焰图分析、内存泄漏检测&…

2026/7/15 15:04:13阅读更多 →
Privasis-Cleaner-4B社区贡献指南:如何参与这个开源隐私保护项目

Privasis-Cleaner-4B社区贡献指南:如何参与这个开源隐私保护项目

Privasis-Cleaner-4B社区贡献指南:如何参与这个开源隐私保护项目 【免费下载链接】Privasis-Cleaner-4B 项目地址: https://ai.gitcode.com/hf_mirrors/nvidia/Privasis-Cleaner-4B 想要为Privasis-Cleaner-4B这个开源隐私保护项目做出贡献吗?&a…

2026/7/15 16:04:26阅读更多 →
破解大众点评动态字体加密:构建高效智能的数据采集系统

破解大众点评动态字体加密:构建高效智能的数据采集系统

破解大众点评动态字体加密:构建高效智能的数据采集系统 【免费下载链接】dianping_spider 大众点评爬虫(全站可爬,解决动态字体加密,非OCR)。持续更新 项目地址: https://gitcode.com/gh_mirrors/di/dianping_spider…

2026/7/15 16:04:26阅读更多 →
视频平台级联到底难不难?国标GB28181视频监控平台EasyCVR万路视频汇聚,构建省-市-县多级视频防控网

视频平台级联到底难不难?国标GB28181视频监控平台EasyCVR万路视频汇聚,构建省-市-县多级视频防控网

以智慧城市建设为例,前端设备数量动辄数万甚至数十万路,单一服务器集群无论如何也承载不了如此庞大的视频接入规模。传统的做法是各地自建平台,结果形成了一个个"视频孤岛"——市局想调阅区县的画面,要么跨网专线直连摄…

2026/7/15 16:04:26阅读更多 →
Notepad++ Markdown实时预览插件终极指南:5分钟掌握高效文档编写技巧

Notepad++ Markdown实时预览插件终极指南:5分钟掌握高效文档编写技巧

Notepad Markdown实时预览插件终极指南:5分钟掌握高效文档编写技巧 【免费下载链接】MarkdownViewerPlusPlus A Notepad Plugin to view a Markdown file rendered on-the-fly 项目地址: https://gitcode.com/gh_mirrors/ma/MarkdownViewerPlusPlus 你是否经…

2026/7/15 16:04:26阅读更多 →
如何快速实现职业教育平台自动学习:3步操作的完整指南

如何快速实现职业教育平台自动学习:3步操作的完整指南

如何快速实现职业教育平台自动学习:3步操作的完整指南 【免费下载链接】auto-play-course 简单好用的刷课脚本[支持平台:职教云,智慧职教,资源库] 项目地址: https://gitcode.com/gh_mirrors/hc/auto-play-course 还在为职业教育平台的重复性学习任务而烦恼吗…

2026/7/15 16:04:26阅读更多 →
开发者必看:SingGuard-8b-GGUF的Fast与Fast-Slow模式应用场景与代码示例

开发者必看:SingGuard-8b-GGUF的Fast与Fast-Slow模式应用场景与代码示例

开发者必看:SingGuard-8b-GGUF的Fast与Fast-Slow模式应用场景与代码示例 【免费下载链接】SingGuard-8b-GGUF 项目地址: https://ai.gitcode.com/hf_mirrors/inclusionAI/SingGuard-8b-GGUF SingGuard-8b-GGUF是一款策略自适应的多模态安全护栏模型&#xf…

2026/7/15 15:59:25阅读更多 →
VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异

VSCode TypeScript 环境配置对比:全局安装 vs 项目本地安装的4个关键差异当你在VSCode中启动一个新的TypeScript项目时,第一个技术决策往往从安装方式开始。这个看似简单的选择——全局安装还是项目本地安装——实际上会深刻影响你的开发流程、团队协作和…

2026/7/15 6:42:19阅读更多 →
智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手

智慧树刷课插件:5分钟实现自动化学习的智能助手 【免费下载链接】zhihuishu 智慧树刷课插件,自动播放下一集、1.5倍速度、无声 项目地址: https://gitcode.com/gh_mirrors/zh/zhihuishu 智慧树刷课插件是一款专为智慧树在线教育平台设计的Chrome浏…

2026/7/15 6:12:45阅读更多 →
Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案

Steam创意工坊下载器WorkshopDL:跨平台游戏模组获取的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 你是否在GOG或Epic Games Store购买了心仪的游戏…

2026/7/15 10:54:00阅读更多 →
AI框架决定企业AI能走多远

AI框架决定企业AI能走多远

企业AI建设的第一性原理 企业搞AI,最关键的决定是什么?不是选哪家大模型,不是先做哪个场景,不是招多少AI人才——而是选哪个AI开发框架。 为什么?因为框架决定了企业AI能力的"天花板"。选对了框架&#xff0…

2026/7/15 0:01:30阅读更多 →
Java企业为什么需要AI框架

Java企业为什么需要AI框架

Java企业在AI时代的尴尬处境 Java是全球企业级应用开发的主流语言——全球超过一半的企业系统跑在Java上。但在AI浪潮面前,很多Java企业感到尴尬:大模型的接口是各种语言的,AI开发社区以其他语言为主流,似乎Java在AI时代"掉队…

2026/7/15 0:01:30阅读更多 →
CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

CC3230x嵌入式开发实战:SD主机、定时器与低功耗模式深度解析

1. 项目概述:为什么需要关注CC3230x的SD主机、定时器与低功耗?在物联网和嵌入式设备开发领域,我们常常面临一个核心矛盾:设备需要具备强大的连接能力、可靠的数据存储和实时控制功能,同时又必须严格控制功耗以延长电池…

2026/7/15 0:01:30阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/15 15:50:47阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/15 8:52:38阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/15 14:06:23阅读更多 →