一、需求
将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构建。
上述方法描述起来比较拗口,其实用程序实现的时候,只需要进行递归调用即可实现,部分实现代码如下:
QSharedPointer<scially::OSGIndexNode> Build3dtiles(const std::vector<PointCI>* pointSet,
std::vector<unsigned 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 le