一. 插件说明
该插件核心代码来源于github上的一个开源项目lodToolkit,侵删。该项目的作者除了完成las->osgb的转换,还有很多其它的格式转换功能。
二. las转osgb的步骤
-
将las读入程序中
① 创建一个结构体/类用于存储点云的重要信息;
② 读取点云的点坐标、点颜色、点强度、点总数以及坐标系等信息;
③ 将b读取到的信息载入到a中的结构体/类中,用于下一步操作。
-
将las数据分为若干瓦片
① 由用户指定每个瓦片的可包含的最大的点的数目tile_size;
② 根据tile_size将点集划分为若干瓦片。
-
将每个瓦片转为若干lod并写出到文件中
① 设置每个lod所能容纳的最大点数maxPointNumPerOneNode;
② 根据maxPointNumPerOneNode将一个瓦片划分成若干lod,这些lod的组织形式为一棵完全二叉树;
③ 写文件注意文件命名清晰易读即可。
-
写xml文件
①文件中的信息主要有两点,坐标系信息以及一个参考点。
三. 关键代码
- 1-a 存储点信息的类
class PointCI
{
public:
PointCI() : P(0,0,0), C(0, 0, 0), I(255) {}
osg::Vec3 P; //点坐标
osg::Vec3ub C; //点颜色
unsigned char I; //点强度
};
- 1-b&1-c 读取点云信息并存储
//此为读取一个点的操作:点云中的信息通过laszip这个库读出,然后写入POintCI中
bool LazReader::ReadNextPoint(PointCI& pt)
{
if (_currentPointId < _pointCount)
{
// read a point
if (laszip_read_point(_laszipReader))
{
/*seed::log::DumpLog(seed::log::Critical, "An error occured in reading point %I64d", _currentPointId);*/
return false;
}
// init offset
if (_currentPointId == 0)
{
_offsetCenter[0] = (_laszipHeader->min_x + _laszipHeader->max_x) / 2.0;
_offsetCenter[1] = (_laszipHeader->min_y + _laszipHeader->max_y) / 2.0;
_offsetCenter[2] = (_laszipHeader->min_z + _laszipHeader->max_z) / 2.0;
//记录srs参考点
_srsPoint = _offsetCenter;
}
//存储相对坐标
pt.P[0] = _pointRead->X * _laszipHeader->x_scale_factor + _laszipHeader->x_offset - _offsetCenter[0];
pt.P[1] = _pointRead->Y * _laszipHeader->y_scale_factor + _laszipHeader->y_offset - _offsetCenter[1];
pt.P[2] = _pointRead->Z * _laszipHeader->z_scale_factor + _laszipHeader->z_offset - _offsetCenter[2];
//存储rgb
auto& rgb = _pointRead->rgb;
pt.C[0] = Color8Bits(rgb[0]);
pt.C[1] = Color8Bits(rgb[1]);
pt.C[2] = Color8Bits(rgb[2]);
pt.I = Color8Bits(_pointRead->intensity);
_currentPointId++;
return true;
}
return false;
}
- 2-a&2-b划分瓦片
//从点集中提取一个瓦片
bool PointCloudToLOD::LoadPointsForOneTile(std::shared_ptr<PointVisitor> pointVisitor,
std::vector<PointCI>& lstPoints, size_t tileSize, size_t processedPoints)
{
lstPoints.clear();
size_t count = 0;
//最后一个瓦片(未加载的点数<1.5* tileSize时,扩充瓦片大小)
if (pointVisitor->GetNumOfPoints() - processedPoints <= 1.5 * tileSize)
{
tileSize = pointVisitor->GetNumOfPoints() - processedPoints;
}
PointCI point;
while (count < tileSize)
{
int l_nFlag = pointVisitor->NextPoint(point);
if (l_nFlag > 0)
{
lstPoints.push_back(point);
++count;
if (l_nFlag == 2)
{
break;
}
}
else
{
break;
}
}
if (count > 0)
{
return true;
}
else
{
return false;
}
}
- 3-a&3-b&3-c 将瓦片转为lod树并写出
//生成lod结点树
bool TileToLOD::BuildNode(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)
{
// format
std::string format;
if (exportMode == ExportMode::OSGB)
{
format = ".osgb";
}
else
{
/*ccLog::Error(QString("Mode %1 is NOT support!").arg(exportMode));*/
return false;
}
// filename of self, left, right
std::string saveFileName, leftPageName, rightPageName;
if (level == 0)
{
saveFileName = saveFilePath + "/" + strBlock + format;
}
else
{
char tmpSaveFileName[100];
sprintf(tmpSaveFileName, "%s%s%d%s%d%s", strBlock.c_str(), "_L", level, "_", childNo, format.c_str());
saveFileName.assign(tmpSaveFileName);
saveFileName = saveFilePath + "/" + saveFileName;
}
char tmpLeftPageName[100], tmpRightPageName[100];
sprintf(tmpLeftPageName, "%s%s%d%s%d%s", strBlock.c_str(), "_L", level + 1, "_", childNo * 2, format.c_str());
leftPageName.assign(tmpLeftPageName);
sprintf(tmpRightPageName, "%s%s%d%s%d%s", strBlock.c_str(), "_L", level + 1, "_", childNo * 2 + 1, format.c_str());
rightPageName.assign(tmpRightPageName);
// 递归终止条件:到达叶节点
if (pointIndex.size() < _maxPointNumPerOneNode || level >= _maxTreeLevel)
{
//构建一个叶节点
osg::ref_ptr<osg::Geode> nodeGeode = MakeNodeGeode(pointSet, pointIndex, exportMode);
if (exportMode == ExportMode::OSGB)
{
//将结点数据写入文件中
if (osgDB::writeNodeFile(*(nodeGeode.get()), saveFileName, new osgDB::ReaderWriter::Options("precision 20")) == false)
{
/* ccLog::Error(QString("Write node file %1 failed!").arg(saveFileName.c_str()));*/
return false;
}
}
else
{
/*ccLog::Error(QString("Mode %d is NOT support!").arg(exportMode));*/
return false;
}
return true;
}
// prepare box
AxisInfo maxAxisInfo;
osg::BoundingBox leftBoundingBox;
osg::BoundingBox rightBoundingBox;
//获取最大的轴线?,同时初始化根左右三个盒子
maxAxisInfo = FindMaxAxis(boundingBox, leftBoundingBox, rightBoundingBox);
double mid = (maxAxisInfo.max + maxAxisInfo.min) / 2;
// split self, left, right
float interval = (float)pointIndex.size() / (float)_maxPointNumPerOneNode; //瓦片大小/每个lod结点的最大点数
int count = -1;
std::vector<unsigned int> selfPointSetIndex;
std::vector<unsigned int> leftPointSetIndex;
std::vector<unsigned int> rightPointSetIndex;
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[int(maxAxisInfo.aixType)] > mid)
{
rightPointSetIndex.push_back(pointIndex[i]);
}
else
{
leftPointSetIndex.push_back(pointIndex[i]);
}
}
}
// export
{
//创建根节点
osg::ref_ptr<osg::Group> mt(new osg::Group);
osg::ref_ptr<osg::Geode> nodeGeode = MakeNodeGeode(pointSet, selfPointSetIndex, exportMode);
mt->addChild(nodeGeode.get());
selfPointSetIndex.swap(std::vector<unsigned int>());
double rangeRatio = 4.;
double rangeValue = boundingBoxLevel0.radius() * 2.f * _lodRatio * rangeRatio;
//初始化左孩子
if (leftPointSetIndex.size())
{
osg::ref_ptr<osg::PagedLOD> leftPageNode = new osg::PagedLOD;
leftPageNode->setRangeMode(osg::PagedLOD::PIXEL_SIZE_ON_SCREEN);
leftPageNode->setFileName(0, leftPageName);
leftPageNode->setRange(0, rangeValue, FLT_MAX);
leftPageNode->setCenter(leftBoundingBox.center());
leftPageNode->setRadius(leftBoundingBox.radius());
mt->addChild(leftPageNode.get());
}
//初始化右孩子
if (rightPointSetIndex.size())
{
osg::ref_ptr<osg::PagedLOD> rightPageNode = new osg::PagedLOD;
rightPageNode->setRangeMode(osg::PagedLOD::PIXEL_SIZE_ON_SCREEN);
rightPageNode->setFileName(0, rightPageName);
rightPageNode->setRange(0, rangeValue, FLT_MAX);
rightPageNode->setCenter(rightBoundingBox.center());
rightPageNode->setRadius(rightBoundingBox.radius());
mt->addChild(rightPageNode.get());
}
//根结点写入文件
if (exportMode == ExportMode::OSGB)
{
if (osgDB::writeNodeFile(*(mt.get()), saveFileName, new osgDB::ReaderWriter::Options("precision 20")) == false)
{
/*ccLog::Error(QString("Write node file %s failed!").arg(saveFileName.c_str()));*/
return false;
}
}
else
{
/*ccLog::Error(QString("Mode %d is NOT support!").arg(exportMode));*/
return false;
}
}
//递归处理左右孩子结点
if (leftPointSetIndex.size())
{
BuildNode(pointSet, leftPointSetIndex, leftBoundingBox, boundingBoxLevel0, saveFilePath, strBlock, level + 1, childNo * 2, exportMode);
leftPointSetIndex.swap(std::vector<unsigned int>());
}
if (rightPointSetIndex.size())
{
BuildNode(pointSet, rightPointSetIndex, rightBoundingBox, boundingBoxLevel0, saveFilePath, strBlock, level + 1, childNo * 2 + 1, exportMode);
rightPointSetIndex.swap(std::vector<unsigned int>());
}
return true;
}
}
4-a 写xml文件
bool PointCloudToLOD::ExportSRS(const std::string& srs, osg::Vec3d &srsPoint, const std::string& filePath)
{
std::ofstream outfile(filePath);
if (outfile.bad())
{
/*seed::log::DumpLog(seed::log::Critical, "Can NOT open file %s!", filePath.c_str());*/
return false;
}
outfile << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
outfile << "<ModelMetadata version=\"1\">\n";
outfile << " <SRS>"<< srs <<"</SRS>\n";
outfile << " <SRSOrigin>"<<std::to_string(srsPoint[0])<<"," << std::to_string(srsPoint[1])<<","<<std::to_string(srsPoint[2])<<"</SRSOrigin>\n";
outfile << " <Texture>\n";
outfile << " <ColorSource>Visible</ColorSource>\n";
outfile << " </Texture>\n";
outfile << "</ModelMetadata>\n";
if (outfile.bad())
{
/*seed::log::DumpLog(seed::log::Critical, "An error has occurred while writing file %s!", filePath.c_str());*/
return false;
}
return true;
}
四. 插件下载
百度网盘链接:https://pan.baidu.com/s/18jTQJRm2Ta0a-iMKZM9lrQ
提取码:ufhg