一. 插件說明
該插件核心代碼來源于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