ccVolumeCalcTool.cpp 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccVolumeCalcTool.h"
  18. #include "ui_volumeCalcDlg.h"
  19. //Local
  20. #include "ccPersistentSettings.h"
  21. #include "mainwindow.h"
  22. //qCC_db
  23. #include <ccPointCloud.h>
  24. #include <ccProgressDialog.h>
  25. #include <ccScalarField.h>
  26. //qCC_gl
  27. #include <ccGLWindowInterface.h>
  28. //Qt
  29. #include <QClipboard>
  30. #include <QMessageBox>
  31. #include <QSettings>
  32. //System
  33. #include <cassert>
  34. ccVolumeCalcTool::ccVolumeCalcTool(ccGenericPointCloud* cloud1, ccGenericPointCloud* cloud2, QWidget* parent/*=nullptr*/)
  35. : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint)
  36. , cc2Point5DimEditor()
  37. , m_cloud1(cloud1)
  38. , m_cloud2(cloud2)
  39. , m_ui( new Ui::VolumeCalcDialog )
  40. {
  41. m_ui->setupUi(this);
  42. connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &ccVolumeCalcTool::saveSettingsAndAccept);
  43. connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &ccVolumeCalcTool::reject);
  44. connect(m_ui->gridStepDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::updateGridInfo);
  45. connect(m_ui->gridStepDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  46. connect(m_ui->groundMaxEdgeLengthDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  47. connect(m_ui->ceilMaxEdgeLengthDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  48. connect(m_ui->groundEmptyValueDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  49. connect(m_ui->ceilEmptyValueDoubleSpinBox, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  50. connect(m_ui->projDimComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::projectionDirChanged);
  51. connect(m_ui->updatePushButton, &QPushButton::clicked, this, &ccVolumeCalcTool::updateGridAndDisplay);
  52. connect(m_ui->heightProjectionComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::gridOptionChanged);
  53. connect(m_ui->fillGroundEmptyCellsComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::groundFillEmptyCellStrategyChanged);
  54. connect(m_ui->fillCeilEmptyCellsComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::ceilFillEmptyCellStrategyChanged);
  55. connect(m_ui->swapToolButton, &QToolButton::clicked, this, &ccVolumeCalcTool::swapRoles);
  56. connect(m_ui->groundComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::groundSourceChanged);
  57. connect(m_ui->ceilComboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &ccVolumeCalcTool::ceilSourceChanged);
  58. connect(m_ui->clipboardPushButton, &QPushButton::clicked, this, &ccVolumeCalcTool::exportToClipboard);
  59. connect(m_ui->exportGridPushButton, &QPushButton::clicked, this, &ccVolumeCalcTool::exportGridAsCloud);
  60. connect(m_ui->precisionSpinBox, qOverload<int>(&QSpinBox::valueChanged), this, &ccVolumeCalcTool::setDisplayedNumberPrecision);
  61. if (m_cloud1 && !m_cloud2)
  62. {
  63. //the existing cloud is always the second by default
  64. std::swap(m_cloud1, m_cloud2);
  65. }
  66. assert(m_cloud2);
  67. //custom bbox editor
  68. ccBBox gridBBox = m_cloud1 ? m_cloud1->getOwnBB() : ccBBox();
  69. if (m_cloud2)
  70. {
  71. gridBBox += m_cloud2->getOwnBB();
  72. }
  73. if (gridBBox.isValid())
  74. {
  75. createBoundingBoxEditor(gridBBox, this);
  76. connect(m_ui->editGridToolButton, &QToolButton::clicked, this, &ccVolumeCalcTool::showGridBoxEditor);
  77. }
  78. else
  79. {
  80. m_ui->editGridToolButton->setEnabled(false);
  81. }
  82. m_ui->groundComboBox->addItem("Constant");
  83. m_ui->ceilComboBox->addItem("Constant");
  84. if (m_cloud1)
  85. {
  86. m_ui->groundComboBox->addItem(m_cloud1->getName());
  87. m_ui->ceilComboBox->addItem(m_cloud1->getName());
  88. }
  89. if (m_cloud2)
  90. {
  91. m_ui->groundComboBox->addItem(m_cloud2->getName());
  92. m_ui->ceilComboBox->addItem(m_cloud2->getName());
  93. }
  94. assert(m_ui->groundComboBox->count() >= 2);
  95. m_ui->groundComboBox->setCurrentIndex(m_ui->groundComboBox->count()-2);
  96. m_ui->ceilComboBox->setCurrentIndex(m_ui->ceilComboBox->count()-1);
  97. //add window
  98. create2DView(m_ui->mapFrame);
  99. if (m_glWindow)
  100. {
  101. ccGui::ParamStruct params = m_glWindow->getDisplayParameters();
  102. params.colorScaleShowHistogram = false;
  103. params.displayedNumPrecision = m_ui->precisionSpinBox->value();
  104. m_glWindow->setDisplayParameters(params, true);
  105. }
  106. loadSettings();
  107. updateGridInfo();
  108. gridIsUpToDate(false);
  109. }
  110. ccVolumeCalcTool::~ccVolumeCalcTool()
  111. {
  112. delete m_ui;
  113. }
  114. void ccVolumeCalcTool::setDisplayedNumberPrecision(int precision)
  115. {
  116. //update window
  117. if (m_glWindow)
  118. {
  119. ccGui::ParamStruct params = m_glWindow->getDisplayParameters();
  120. params.displayedNumPrecision = precision;
  121. m_glWindow->setDisplayParameters(params, true);
  122. m_glWindow->redraw(true, false);
  123. }
  124. //update report
  125. if (m_ui->clipboardPushButton->isEnabled())
  126. {
  127. outputReport(m_lastReport);
  128. }
  129. }
  130. void ccVolumeCalcTool::groundSourceChanged(int)
  131. {
  132. m_ui->fillGroundEmptyCellsComboBox->setEnabled(m_ui->groundComboBox->currentIndex() > 0);
  133. groundFillEmptyCellStrategyChanged(-1);
  134. }
  135. void ccVolumeCalcTool::ceilSourceChanged(int)
  136. {
  137. m_ui->fillCeilEmptyCellsComboBox->setEnabled(m_ui->ceilComboBox->currentIndex() > 0);
  138. ceilFillEmptyCellStrategyChanged(-1);
  139. }
  140. void ccVolumeCalcTool::swapRoles()
  141. {
  142. int sourceIndex = m_ui->ceilComboBox->currentIndex();
  143. int emptyCellStrat = m_ui->fillCeilEmptyCellsComboBox->currentIndex();
  144. double emptyCellValue = m_ui->ceilEmptyValueDoubleSpinBox->value();
  145. double maxEdgeLength = m_ui->ceilMaxEdgeLengthDoubleSpinBox->value();
  146. m_ui->ceilComboBox->setCurrentIndex(m_ui->groundComboBox->currentIndex());
  147. m_ui->fillCeilEmptyCellsComboBox->setCurrentIndex(m_ui->fillGroundEmptyCellsComboBox->currentIndex());
  148. m_ui->ceilEmptyValueDoubleSpinBox->setValue(m_ui->groundEmptyValueDoubleSpinBox->value());
  149. m_ui->ceilEmptyValueDoubleSpinBox->setValue(m_ui->groundMaxEdgeLengthDoubleSpinBox->value());
  150. m_ui->groundComboBox->setCurrentIndex(sourceIndex);
  151. m_ui->fillGroundEmptyCellsComboBox->setCurrentIndex(emptyCellStrat);
  152. m_ui->groundEmptyValueDoubleSpinBox->setValue(emptyCellValue);
  153. m_ui->groundMaxEdgeLengthDoubleSpinBox->setValue(maxEdgeLength);
  154. gridIsUpToDate(false);
  155. }
  156. bool ccVolumeCalcTool::showGridBoxEditor()
  157. {
  158. if (cc2Point5DimEditor::showGridBoxEditor())
  159. {
  160. updateGridInfo();
  161. return true;
  162. }
  163. return false;
  164. }
  165. void ccVolumeCalcTool::updateGridInfo()
  166. {
  167. m_ui->gridWidthLabel->setText(getGridSizeAsString());
  168. }
  169. double ccVolumeCalcTool::getGridStep() const
  170. {
  171. return m_ui->gridStepDoubleSpinBox->value();
  172. }
  173. unsigned char ccVolumeCalcTool::getProjectionDimension() const
  174. {
  175. int dim = m_ui->projDimComboBox->currentIndex();
  176. assert(dim >= 0 && dim < 3);
  177. return static_cast<unsigned char>(dim);
  178. }
  179. void ccVolumeCalcTool::sfProjectionTypeChanged(int index)
  180. {
  181. Q_UNUSED( index )
  182. gridIsUpToDate(false);
  183. }
  184. void ccVolumeCalcTool::projectionDirChanged(int dir)
  185. {
  186. Q_UNUSED( dir )
  187. updateGridInfo();
  188. gridIsUpToDate(false);
  189. }
  190. void ccVolumeCalcTool::groundFillEmptyCellStrategyChanged(int)
  191. {
  192. ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy = getFillEmptyCellsStrategy(m_ui->fillGroundEmptyCellsComboBox);
  193. m_ui->groundEmptyValueDoubleSpinBox->setEnabled( (m_ui->groundComboBox->currentIndex() == 0)
  194. || (fillEmptyCellsStrategy == ccRasterGrid::FILL_CUSTOM_HEIGHT) );
  195. m_ui->groundMaxEdgeLengthDoubleSpinBox->setEnabled(fillEmptyCellsStrategy == ccRasterGrid::INTERPOLATE_DELAUNAY);
  196. gridIsUpToDate(false);
  197. }
  198. void ccVolumeCalcTool::ceilFillEmptyCellStrategyChanged(int)
  199. {
  200. ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy = getFillEmptyCellsStrategy(m_ui->fillCeilEmptyCellsComboBox);
  201. m_ui->ceilEmptyValueDoubleSpinBox->setEnabled( (m_ui->ceilComboBox->currentIndex() == 0)
  202. || (fillEmptyCellsStrategy == ccRasterGrid::FILL_CUSTOM_HEIGHT) );
  203. m_ui->ceilMaxEdgeLengthDoubleSpinBox->setEnabled(fillEmptyCellsStrategy == ccRasterGrid::INTERPOLATE_DELAUNAY);
  204. gridIsUpToDate(false);
  205. }
  206. void ccVolumeCalcTool::gridOptionChanged()
  207. {
  208. gridIsUpToDate(false);
  209. }
  210. ccRasterGrid::ProjectionType ccVolumeCalcTool::getTypeOfProjection() const
  211. {
  212. switch (m_ui->heightProjectionComboBox->currentIndex())
  213. {
  214. case 0:
  215. return ccRasterGrid::PROJ_MINIMUM_VALUE;
  216. case 1:
  217. return ccRasterGrid::PROJ_AVERAGE_VALUE;
  218. case 2:
  219. return ccRasterGrid::PROJ_MAXIMUM_VALUE;
  220. default:
  221. //shouldn't be possible for this option!
  222. assert(false);
  223. }
  224. return ccRasterGrid::INVALID_PROJECTION_TYPE;
  225. }
  226. void ccVolumeCalcTool::loadSettings()
  227. {
  228. QSettings settings;
  229. settings.beginGroup(ccPS::VolumeCalculation());
  230. int projType = settings.value("ProjectionType", m_ui->heightProjectionComboBox->currentIndex()).toInt();
  231. int projDim = settings.value("ProjectionDim", m_ui->projDimComboBox->currentIndex()).toInt();
  232. int groundFillStrategy = settings.value("gFillStrategy", m_ui->fillGroundEmptyCellsComboBox->currentIndex()).toInt();
  233. int ceilFillStrategy = settings.value("cFillStrategy", m_ui->fillCeilEmptyCellsComboBox->currentIndex()).toInt();
  234. double step = settings.value("GridStep", m_ui->gridStepDoubleSpinBox->value()).toDouble();
  235. double groundEmptyHeight = settings.value("gEmptyCellsHeight", m_ui->groundEmptyValueDoubleSpinBox->value()).toDouble();
  236. double groundMaxEdgeLength = settings.value("gMaxEdgeLength", m_ui->groundMaxEdgeLengthDoubleSpinBox->value()).toDouble();
  237. double ceilEmptyHeight = settings.value("cEmptyCellsHeight", m_ui->ceilEmptyValueDoubleSpinBox->value()).toDouble();
  238. double ceilMaxEdgeLength = settings.value("cMaxEdgeLength", m_ui->ceilMaxEdgeLengthDoubleSpinBox->value()).toDouble();
  239. int precision = settings.value("NumPrecision", m_ui->precisionSpinBox->value()).toInt();
  240. settings.endGroup();
  241. m_ui->gridStepDoubleSpinBox->setValue(step);
  242. m_ui->heightProjectionComboBox->setCurrentIndex(projType);
  243. m_ui->fillGroundEmptyCellsComboBox->setCurrentIndex(groundFillStrategy);
  244. m_ui->fillCeilEmptyCellsComboBox->setCurrentIndex(ceilFillStrategy);
  245. m_ui->groundEmptyValueDoubleSpinBox->setValue(groundEmptyHeight);
  246. m_ui->groundMaxEdgeLengthDoubleSpinBox->setValue(groundMaxEdgeLength);
  247. m_ui->ceilEmptyValueDoubleSpinBox->setValue(ceilEmptyHeight);
  248. m_ui->ceilMaxEdgeLengthDoubleSpinBox->setValue(ceilMaxEdgeLength);
  249. m_ui->projDimComboBox->setCurrentIndex(projDim);
  250. m_ui->precisionSpinBox->setValue(precision);
  251. }
  252. void ccVolumeCalcTool::saveSettingsAndAccept()
  253. {
  254. saveSettings();
  255. accept();
  256. }
  257. void ccVolumeCalcTool::saveSettings()
  258. {
  259. QSettings settings;
  260. settings.beginGroup(ccPS::VolumeCalculation());
  261. settings.setValue("ProjectionType", m_ui->heightProjectionComboBox->currentIndex());
  262. settings.setValue("ProjectionDim", m_ui->projDimComboBox->currentIndex());
  263. settings.setValue("gFillStrategy", m_ui->fillGroundEmptyCellsComboBox->currentIndex());
  264. settings.setValue("cFillStrategy", m_ui->fillCeilEmptyCellsComboBox->currentIndex());
  265. settings.setValue("GridStep", m_ui->gridStepDoubleSpinBox->value());
  266. settings.setValue("gEmptyCellsHeight", m_ui->groundEmptyValueDoubleSpinBox->value());
  267. settings.setValue("gMaxEdgeLength", m_ui->groundMaxEdgeLengthDoubleSpinBox->value());
  268. settings.setValue("cEmptyCellsHeight", m_ui->ceilEmptyValueDoubleSpinBox->value());
  269. settings.setValue("cMaxEdgeLength", m_ui->ceilMaxEdgeLengthDoubleSpinBox->value());
  270. settings.setValue("NumPrecision", m_ui->precisionSpinBox->value());
  271. settings.endGroup();
  272. }
  273. void ccVolumeCalcTool::gridIsUpToDate(bool state)
  274. {
  275. if (state)
  276. {
  277. //standard button
  278. m_ui->updatePushButton->setStyleSheet(QString());
  279. }
  280. else
  281. {
  282. //red button
  283. m_ui->updatePushButton->setStyleSheet("color: white; background-color:red;");
  284. }
  285. m_ui->updatePushButton->setDisabled(state);
  286. m_ui->clipboardPushButton->setEnabled(state);
  287. m_ui->exportGridPushButton->setEnabled(state);
  288. if (!state)
  289. {
  290. m_ui->spareseWarningLabel->hide();
  291. m_ui->reportPlainTextEdit->setPlainText("Update the grid first");
  292. }
  293. }
  294. ccPointCloud* ccVolumeCalcTool::ConvertGridToCloud( ccRasterGrid& grid,
  295. const ccBBox& gridBox,
  296. unsigned char vertDim,
  297. bool exportToOriginalCS)
  298. {
  299. assert(gridBox.isValid());
  300. assert(vertDim < 3);
  301. ccPointCloud* rasterCloud = nullptr;
  302. try
  303. {
  304. //we only compute the default 'height' layer
  305. std::vector<ccRasterGrid::ExportableFields> exportedStatistics(1);
  306. exportedStatistics.front() = ccRasterGrid::PER_CELL_VALUE;
  307. rasterCloud = grid.convertToCloud( true,
  308. false,
  309. exportedStatistics,
  310. false,
  311. false,
  312. false,
  313. false,
  314. nullptr,
  315. vertDim,
  316. gridBox,
  317. 0,
  318. exportToOriginalCS,
  319. false);
  320. if (rasterCloud && rasterCloud->hasScalarFields())
  321. {
  322. rasterCloud->showSF(true);
  323. rasterCloud->setCurrentDisplayedScalarField(0);
  324. ccScalarField* sf = static_cast<ccScalarField*>(rasterCloud->getScalarField(0));
  325. assert(sf);
  326. sf->setName("Relative height");
  327. sf->setSymmetricalScale(sf->getMin() < 0 && sf->getMax() > 0);
  328. rasterCloud->showSFColorsScale(true);
  329. }
  330. }
  331. catch (const std::bad_alloc&)
  332. {
  333. ccLog::Warning("[ConvertGridToCloud] Not enough memory!");
  334. if (rasterCloud)
  335. {
  336. delete rasterCloud;
  337. rasterCloud = nullptr;
  338. }
  339. }
  340. return rasterCloud;
  341. }
  342. ccPointCloud* ccVolumeCalcTool::convertGridToCloud(bool exportToOriginalCS) const
  343. {
  344. ccPointCloud* rasterCloud = nullptr;
  345. try
  346. {
  347. //we only compute the default 'height' layer
  348. std::vector<ccRasterGrid::ExportableFields> exportedStatistics(1);
  349. exportedStatistics.front() = ccRasterGrid::PER_CELL_VALUE;
  350. rasterCloud = cc2Point5DimEditor::convertGridToCloud( true,
  351. false,
  352. exportedStatistics,
  353. false,
  354. false,
  355. false,
  356. false,
  357. nullptr,
  358. 0.0,
  359. exportToOriginalCS,
  360. false,
  361. nullptr );
  362. if (rasterCloud)
  363. {
  364. if (rasterCloud->hasScalarFields())
  365. {
  366. rasterCloud->showSF(true);
  367. rasterCloud->setCurrentDisplayedScalarField(0);
  368. ccScalarField* sf = static_cast<ccScalarField*>(rasterCloud->getScalarField(0));
  369. assert(sf);
  370. sf->setName("Relative height");
  371. sf->setSymmetricalScale(sf->getMin() < 0 && sf->getMax() > 0);
  372. rasterCloud->showSFColorsScale(true);
  373. }
  374. //keep Global Shift & Scale
  375. auto ground = getGroundCloud();
  376. auto ceil = getCeilCloud();
  377. if (ground.first && ground.first->isShifted())
  378. {
  379. rasterCloud->copyGlobalShiftAndScale(*ground.first);
  380. }
  381. else if (ceil.first && ceil.first->isShifted())
  382. {
  383. rasterCloud->copyGlobalShiftAndScale(*ceil.first);
  384. }
  385. }
  386. }
  387. catch (const std::bad_alloc&)
  388. {
  389. ccLog::Error("Not enough memory!");
  390. if (rasterCloud)
  391. {
  392. delete rasterCloud;
  393. rasterCloud = nullptr;
  394. }
  395. }
  396. return rasterCloud;
  397. }
  398. void ccVolumeCalcTool::updateGridAndDisplay()
  399. {
  400. bool success = updateGrid();
  401. if (success && m_glWindow)
  402. {
  403. //convert grid to point cloud
  404. if (m_rasterCloud)
  405. {
  406. m_glWindow->removeFromOwnDB(m_rasterCloud);
  407. delete m_rasterCloud;
  408. m_rasterCloud = nullptr;
  409. }
  410. m_rasterCloud = convertGridToCloud(false);
  411. if (m_rasterCloud)
  412. {
  413. m_glWindow->addToOwnDB(m_rasterCloud);
  414. ccBBox box = m_rasterCloud->getDisplayBB_recursive(false, m_glWindow);
  415. update2DDisplayZoom(box);
  416. }
  417. else
  418. {
  419. ccLog::Error("Not enough memory!");
  420. m_glWindow->redraw();
  421. }
  422. }
  423. gridIsUpToDate(success);
  424. }
  425. QString ccVolumeCalcTool::ReportInfo::toText(int precision) const
  426. {
  427. QLocale locale(QLocale::English);
  428. QStringList reportText;
  429. reportText << QString("Volume: %1").arg(locale.toString(volume, 'f', precision));
  430. reportText << QString("Surface: %1").arg(locale.toString(surface, 'f', precision));
  431. reportText << QString("----------------------");
  432. reportText << QString("Added volume: (+)%1").arg(locale.toString(addedVolume, 'f', precision));
  433. reportText << QString("Removed volume: (-)%1").arg(locale.toString(removedVolume, 'f', precision));
  434. reportText << QString("----------------------");
  435. reportText << QString("Matching cells: %1%").arg(matchingPrecent, 0, 'f', 1);
  436. reportText << QString("Non-matching cells:");
  437. reportText << QString(" ground = %1%").arg(groundNonMatchingPercent, 0, 'f', 1);
  438. reportText << QString(" ceil = %1%").arg(ceilNonMatchingPercent, 0, 'f', 1);
  439. reportText << QString("Average neighbors per cell: %1 / 8.0").arg(averageNeighborsPerCell, 0, 'f', 1);
  440. return reportText.join("\n");
  441. }
  442. void ccVolumeCalcTool::outputReport(const ReportInfo& info)
  443. {
  444. int precision = m_ui->precisionSpinBox->value();
  445. m_ui->reportPlainTextEdit->setPlainText(info.toText(precision));
  446. //below 7 neighbors per cell, at least one of the cloud is very sparse!
  447. m_ui->spareseWarningLabel->setVisible(info.averageNeighborsPerCell < 7.0f);
  448. m_lastReport = info;
  449. m_ui->clipboardPushButton->setEnabled(true);
  450. }
  451. bool SendError(const QString& message, QWidget* parentWidget)
  452. {
  453. if (parentWidget)
  454. {
  455. ccLog::Error(message);
  456. }
  457. else
  458. {
  459. ccLog::Warning("[Volume] " + message);
  460. }
  461. return false;
  462. }
  463. bool ccVolumeCalcTool::ComputeVolume( ccRasterGrid& grid,
  464. ccGenericPointCloud* ground,
  465. ccGenericPointCloud* ceil,
  466. const ccBBox& gridBox,
  467. unsigned char vertDim,
  468. double gridStep,
  469. unsigned gridWidth,
  470. unsigned gridHeight,
  471. ccRasterGrid::ProjectionType projectionType,
  472. ccRasterGrid::EmptyCellFillOption groundEmptyCellFillStrategy,
  473. double groundMaxEdgeLength,
  474. ccRasterGrid::EmptyCellFillOption ceilEmptyCellFillStrategy,
  475. double ceilMaxEdgeLength,
  476. ccVolumeCalcTool::ReportInfo& reportInfo,
  477. double groundHeight = std::numeric_limits<double>::quiet_NaN(),
  478. double ceilHeight = std::numeric_limits<double>::quiet_NaN(),
  479. QWidget* parentWidget/*=nullptr*/)
  480. {
  481. if ( gridStep <= 1.0e-8
  482. || gridWidth == 0
  483. || gridHeight == 0
  484. || vertDim > 2)
  485. {
  486. assert(false);
  487. ccLog::Warning("[Volume] Invalid input parameters");
  488. return false;
  489. }
  490. if (!ground && !ceil)
  491. {
  492. assert(false);
  493. ccLog::Warning("[Volume] No valid input cloud");
  494. return false;
  495. }
  496. if (!gridBox.isValid())
  497. {
  498. ccLog::Warning("[Volume] Invalid bounding-box");
  499. return false;
  500. }
  501. //grid size
  502. unsigned gridTotalSize = gridWidth * gridHeight;
  503. if (gridTotalSize == 1)
  504. {
  505. if (parentWidget && QMessageBox::question(parentWidget, "Unexpected grid size", "The generated grid will only have 1 cell! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
  506. return false;
  507. }
  508. else if (gridTotalSize > 10000000)
  509. {
  510. if (parentWidget && QMessageBox::question(parentWidget, "Big grid size", "The generated grid will have more than 10.000.000 cells! Do you want to proceed anyway?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
  511. return false;
  512. }
  513. //memory allocation
  514. CCVector3d minCorner = gridBox.minCorner();
  515. if (!grid.init(gridWidth, gridHeight, gridStep, minCorner))
  516. {
  517. //not enough memory
  518. return SendError("Not enough memory", parentWidget);
  519. }
  520. //progress dialog
  521. QScopedPointer<ccProgressDialog> pDlg(nullptr);
  522. if (parentWidget)
  523. {
  524. pDlg.reset(new ccProgressDialog(true, parentWidget));
  525. }
  526. ccRasterGrid groundRaster;
  527. if (ground)
  528. {
  529. if (!groundRaster.init(gridWidth, gridHeight, gridStep, minCorner))
  530. {
  531. //not enough memory
  532. return SendError("Not enough memory", parentWidget);
  533. }
  534. ccRasterGrid::InterpolationType interpolationType = ccRasterGrid::InterpolationTypeFromEmptyCellFillOption(groundEmptyCellFillStrategy);
  535. ccRasterGrid::DelaunayInterpolationParams dInterpParams;
  536. void* interpolationParams = nullptr;
  537. switch (interpolationType)
  538. {
  539. case ccRasterGrid::InterpolationType::DELAUNAY:
  540. dInterpParams.maxEdgeLength = groundMaxEdgeLength;
  541. interpolationParams = (void*)&dInterpParams;
  542. break;
  543. case ccRasterGrid::InterpolationType::KRIGING:
  544. // not supported yet
  545. assert(false);
  546. break;
  547. default:
  548. // do nothing
  549. break;
  550. }
  551. if (groundRaster.fillWith( ground,
  552. vertDim,
  553. projectionType,
  554. interpolationType,
  555. interpolationParams,
  556. ccRasterGrid::INVALID_PROJECTION_TYPE,
  557. pDlg.data()))
  558. {
  559. groundRaster.fillEmptyCells(groundEmptyCellFillStrategy, groundHeight);
  560. ccLog::Print(QString("[Volume] Ground raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(groundRaster.width).arg(groundRaster.height).arg(groundRaster.minHeight).arg(groundRaster.maxHeight));
  561. }
  562. else
  563. {
  564. return false;
  565. }
  566. }
  567. //ceil
  568. ccRasterGrid ceilRaster;
  569. if (ceil)
  570. {
  571. if (!ceilRaster.init(gridWidth, gridHeight, gridStep, minCorner))
  572. {
  573. //not enough memory
  574. return SendError("Not enough memory", parentWidget);
  575. }
  576. ccRasterGrid::InterpolationType interpolationType = ccRasterGrid::InterpolationTypeFromEmptyCellFillOption(ceilEmptyCellFillStrategy);
  577. ccRasterGrid::DelaunayInterpolationParams dInterpParams;
  578. void* interpolationParams = nullptr;
  579. switch (interpolationType)
  580. {
  581. case ccRasterGrid::InterpolationType::DELAUNAY:
  582. dInterpParams.maxEdgeLength = ceilMaxEdgeLength;
  583. interpolationParams = (void*)&dInterpParams;
  584. break;
  585. case ccRasterGrid::InterpolationType::KRIGING:
  586. // not supported yet
  587. assert(false);
  588. break;
  589. default:
  590. // do nothing
  591. break;
  592. }
  593. if (ceilRaster.fillWith(ceil,
  594. vertDim,
  595. projectionType,
  596. interpolationType,
  597. interpolationParams,
  598. ccRasterGrid::INVALID_PROJECTION_TYPE,
  599. pDlg.data()))
  600. {
  601. ceilRaster.fillEmptyCells(ceilEmptyCellFillStrategy, ceilHeight);
  602. ccLog::Print(QString("[Volume] Ceil raster grid: size: %1 x %2 / heights: [%3 ; %4]").arg(ceilRaster.width).arg(ceilRaster.height).arg(ceilRaster.minHeight).arg(ceilRaster.maxHeight));
  603. }
  604. else
  605. {
  606. return false;
  607. }
  608. }
  609. //update grid and compute volume
  610. {
  611. if (pDlg)
  612. {
  613. pDlg->setMethodTitle(QObject::tr("Volume computation"));
  614. pDlg->setInfo(QObject::tr("Cells: %1 x %2").arg(grid.width).arg(grid.height));
  615. pDlg->start();
  616. pDlg->show();
  617. QCoreApplication::processEvents();
  618. }
  619. CCCoreLib::NormalizedProgress nProgress(pDlg.data(), grid.width * grid.height);
  620. size_t ceilNonMatchingCount = 0;
  621. size_t groundNonMatchingCount = 0;
  622. size_t cellCount = 0;
  623. //at least one of the grid is based on a cloud
  624. grid.nonEmptyCellCount = 0;
  625. for (unsigned i = 0; i < grid.height; ++i)
  626. {
  627. for (unsigned j = 0; j < grid.width; ++j)
  628. {
  629. ccRasterCell& cell = grid.rows[i][j];
  630. bool validGround = true;
  631. cell.minHeight = groundHeight;
  632. if (ground)
  633. {
  634. cell.minHeight = groundRaster.rows[i][j].h;
  635. validGround = std::isfinite(cell.minHeight);
  636. }
  637. bool validCeil = true;
  638. cell.maxHeight = ceilHeight;
  639. if (ceil)
  640. {
  641. cell.maxHeight = ceilRaster.rows[i][j].h;
  642. validCeil = std::isfinite(cell.maxHeight);
  643. }
  644. if (validGround && validCeil)
  645. {
  646. cell.h = cell.maxHeight - cell.minHeight;
  647. cell.nbPoints = 1;
  648. reportInfo.volume += cell.h;
  649. if (cell.h < 0)
  650. {
  651. reportInfo.removedVolume -= cell.h;
  652. }
  653. else if (cell.h > 0)
  654. {
  655. reportInfo.addedVolume += cell.h;
  656. }
  657. reportInfo.surface += 1.0;
  658. ++grid.nonEmptyCellCount; // matching count
  659. ++cellCount;
  660. }
  661. else
  662. {
  663. if (validGround)
  664. {
  665. ++cellCount;
  666. ++groundNonMatchingCount;
  667. }
  668. else if (validCeil)
  669. {
  670. ++cellCount;
  671. ++ceilNonMatchingCount;
  672. }
  673. cell.h = std::numeric_limits<double>::quiet_NaN();
  674. cell.nbPoints = 0;
  675. }
  676. if (pDlg && !nProgress.oneStep())
  677. {
  678. ccLog::Warning("[Volume] Process cancelled by the user");
  679. return false;
  680. }
  681. }
  682. }
  683. grid.validCellCount = grid.nonEmptyCellCount;
  684. //count the average number of valid neighbors
  685. {
  686. size_t validNeighborsCount = 0;
  687. size_t count = 0;
  688. for (unsigned i = 1; i < grid.height - 1; ++i)
  689. {
  690. for (unsigned j = 1; j < grid.width - 1; ++j)
  691. {
  692. ccRasterCell& cell = grid.rows[i][j];
  693. if (std::isfinite(cell.h))
  694. {
  695. for (unsigned k = i - 1; k <= i + 1; ++k)
  696. {
  697. for (unsigned l = j - 1; l <= j + 1; ++l)
  698. {
  699. if (k != i || l != j)
  700. {
  701. ccRasterCell& otherCell = grid.rows[k][l];
  702. if (std::isfinite(otherCell.h))
  703. {
  704. ++validNeighborsCount;
  705. }
  706. }
  707. }
  708. }
  709. ++count;
  710. }
  711. }
  712. }
  713. if (count)
  714. {
  715. reportInfo.averageNeighborsPerCell = static_cast<double>(validNeighborsCount) / count;
  716. }
  717. }
  718. reportInfo.matchingPrecent = static_cast<float>(grid.validCellCount * 100) / cellCount;
  719. reportInfo.groundNonMatchingPercent = static_cast<float>(groundNonMatchingCount * 100) / cellCount;
  720. reportInfo.ceilNonMatchingPercent = static_cast<float>(ceilNonMatchingCount * 100) / cellCount;
  721. float cellArea = static_cast<float>(grid.gridStep * grid.gridStep);
  722. reportInfo.volume *= cellArea;
  723. reportInfo.addedVolume *= cellArea;
  724. reportInfo.removedVolume *= cellArea;
  725. reportInfo.surface *= cellArea;
  726. }
  727. grid.setValid(true);
  728. return true;
  729. }
  730. std::pair<ccGenericPointCloud*, double> ccVolumeCalcTool::getGroundCloud() const
  731. {
  732. ccGenericPointCloud* groundCloud = nullptr;
  733. double groundHeight = std::numeric_limits<double>::quiet_NaN();
  734. switch (m_ui->groundComboBox->currentIndex())
  735. {
  736. case 0:
  737. groundHeight = m_ui->groundEmptyValueDoubleSpinBox->value();
  738. break;
  739. case 1:
  740. groundCloud = m_cloud1 ? m_cloud1 : m_cloud2;
  741. break;
  742. case 2:
  743. groundCloud = m_cloud2;
  744. break;
  745. default:
  746. assert(false);
  747. break;
  748. }
  749. return { groundCloud, groundHeight };
  750. }
  751. std::pair<ccGenericPointCloud*, double> ccVolumeCalcTool::getCeilCloud() const
  752. {
  753. ccGenericPointCloud* ceilCloud = nullptr;
  754. double ceilHeight = std::numeric_limits<double>::quiet_NaN();
  755. switch (m_ui->ceilComboBox->currentIndex())
  756. {
  757. case 0:
  758. ceilHeight = m_ui->ceilEmptyValueDoubleSpinBox->value();
  759. break;
  760. case 1:
  761. ceilCloud = m_cloud1 ? m_cloud1 : m_cloud2;
  762. break;
  763. case 2:
  764. ceilCloud = m_cloud2;
  765. break;
  766. default:
  767. assert(false);
  768. break;
  769. }
  770. return { ceilCloud, ceilHeight };
  771. }
  772. bool ccVolumeCalcTool::updateGrid()
  773. {
  774. if (!m_cloud2)
  775. {
  776. assert(false);
  777. return false;
  778. }
  779. //cloud bounding-box --> grid size
  780. ccBBox box = getCustomBBox();
  781. if (!box.isValid())
  782. {
  783. return false;
  784. }
  785. unsigned gridWidth = 0;
  786. unsigned gridHeight = 0;
  787. if (!getGridSize(gridWidth, gridHeight))
  788. {
  789. return false;
  790. }
  791. //grid step
  792. double gridStep = getGridStep();
  793. assert(gridStep != 0);
  794. //ground
  795. auto ground = getGroundCloud();
  796. if (nullptr == ground.first && std::isnan(ground.second))
  797. {
  798. assert(false);
  799. return false;
  800. }
  801. //ceil
  802. auto ceil = getCeilCloud();
  803. if (nullptr == ceil.first && std::isnan(ceil.second))
  804. {
  805. assert(false);
  806. return false;
  807. }
  808. ccVolumeCalcTool::ReportInfo reportInfo;
  809. if (ComputeVolume( m_grid,
  810. ground.first,
  811. ceil.first,
  812. box,
  813. getProjectionDimension(),
  814. gridStep,
  815. gridWidth,
  816. gridHeight,
  817. getTypeOfProjection(),
  818. getFillEmptyCellsStrategy(m_ui->fillGroundEmptyCellsComboBox),
  819. m_ui->groundMaxEdgeLengthDoubleSpinBox->value(),
  820. getFillEmptyCellsStrategy(m_ui->fillCeilEmptyCellsComboBox),
  821. m_ui->ceilMaxEdgeLengthDoubleSpinBox->value(),
  822. reportInfo,
  823. ground.second,
  824. ceil.second,
  825. this))
  826. {
  827. outputReport(reportInfo);
  828. return true;
  829. }
  830. else
  831. {
  832. return false;
  833. }
  834. }
  835. void ccVolumeCalcTool::exportToClipboard() const
  836. {
  837. QClipboard* clipboard = QApplication::clipboard();
  838. if (clipboard)
  839. {
  840. clipboard->setText(m_ui->reportPlainTextEdit->toPlainText());
  841. }
  842. }
  843. void ccVolumeCalcTool::exportGridAsCloud() const
  844. {
  845. if (!m_grid.isValid())
  846. {
  847. assert(false);
  848. }
  849. ccPointCloud* rasterCloud = convertGridToCloud(true);
  850. if (!rasterCloud)
  851. {
  852. //error message should have already been issued
  853. return;
  854. }
  855. rasterCloud->setName("Height difference " + rasterCloud->getName());
  856. ccGenericPointCloud* originCloud = (m_cloud1 ? m_cloud1 : m_cloud2);
  857. assert(originCloud);
  858. if (originCloud)
  859. {
  860. if (originCloud->getParent())
  861. {
  862. originCloud->getParent()->addChild(rasterCloud);
  863. }
  864. rasterCloud->setDisplay(originCloud->getDisplay());
  865. }
  866. MainWindow* mainWindow = MainWindow::TheInstance();
  867. if (mainWindow)
  868. {
  869. mainWindow->addToDB(rasterCloud);
  870. ccLog::Print(QString("[Volume] Cloud '%1' successfully exported").arg(rasterCloud->getName()));
  871. }
  872. else
  873. {
  874. assert(false);
  875. delete rasterCloud;
  876. }
  877. }