ccContourLinesGenerator.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  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: The CloudCompare project #
  15. //# #
  16. //##########################################################################
  17. #include "ccContourLinesGenerator.h"
  18. //qCC_db
  19. #include <ccPointCloud.h>
  20. #include <ccPolyline.h>
  21. #include <ccProgressDialog.h>
  22. #include <ccRasterGrid.h>
  23. #include <ccScalarField.h>
  24. //System
  25. #include <cassert>
  26. static QString GetPolylineName(double value, unsigned mainIndex, unsigned partNumber)
  27. {
  28. QString contourLineName = QObject::tr("Contour line [value = %1]").arg(value);
  29. if (partNumber != 0)
  30. {
  31. contourLineName += QObject::tr(" (poly %1-%2)").arg(mainIndex).arg(partNumber);
  32. }
  33. else
  34. {
  35. contourLineName += QObject::tr(" (poly %1)").arg(mainIndex);
  36. }
  37. return contourLineName;
  38. }
  39. #ifndef CC_GDAL_SUPPORT
  40. #include "ccIsolines.h" //old alternative code to generate contour lines (doesn't work very well :( )
  41. //Qt
  42. #include <QCoreApplication>
  43. #else
  44. //GDAL
  45. #include <cpl_string.h>
  46. #include <gdal.h>
  47. #include <gdal_alg.h>
  48. struct ContourGenerationParameters
  49. {
  50. QMultiMap<double, ccPolyline*> contourLines;
  51. QMap<double, unsigned> contourLinesMainCount;
  52. const ccRasterGrid* grid = nullptr;
  53. bool projectContourOnAltitudes = false;
  54. };
  55. static CPLErr ContourWriter( double dfLevel,
  56. int nPoints,
  57. double* padfX,
  58. double* padfY,
  59. void* userData)
  60. {
  61. if (nPoints < 2)
  62. {
  63. //nothing to do
  64. assert(false);
  65. return CE_None;
  66. }
  67. ContourGenerationParameters* params = reinterpret_cast<ContourGenerationParameters*>(userData);
  68. if (!params || !params->grid)
  69. {
  70. assert(false);
  71. return CE_Failure;
  72. }
  73. ccPointCloud* vertices = nullptr;
  74. ccPolyline* poly = nullptr;
  75. std::vector<ccPolyline*> previousPolylines;
  76. unsigned contourIndex = 1;
  77. if (params->contourLinesMainCount.contains(dfLevel))
  78. {
  79. contourIndex = params->contourLinesMainCount[dfLevel] + 1;
  80. }
  81. params->contourLinesMainCount[dfLevel] = contourIndex;
  82. unsigned partCount = 0;
  83. for (int i = 0; i < nPoints; ++i)
  84. {
  85. CCVector3 P(padfX[i], padfY[i], dfLevel);
  86. //detect potential loops
  87. if (i + 1 == nPoints && poly)
  88. {
  89. const CCVector3* firstPoint = poly->getPoint(0);
  90. if (firstPoint->x == P.x && firstPoint->y == P.y)
  91. {
  92. poly->setClosed(true);
  93. break;
  94. }
  95. }
  96. if (params->projectContourOnAltitudes)
  97. {
  98. int xi = std::min(std::max(static_cast<int>(padfX[i]), 0), static_cast<int>(params->grid->width) - 1);
  99. int yi = std::min(std::max(static_cast<int>(padfY[i]), 0), static_cast<int>(params->grid->height) - 1);
  100. double h = params->grid->rows[yi][xi].h;
  101. if (std::isfinite(h))
  102. {
  103. P.z = static_cast<PointCoordinateType>(h);
  104. }
  105. else
  106. {
  107. //we end the current polyline and start a new one
  108. if (poly)
  109. {
  110. if (poly->size() < 2)
  111. {
  112. //we discard isolated points
  113. delete poly;
  114. poly = nullptr;
  115. }
  116. else
  117. {
  118. poly->shrinkToFit();
  119. vertices->shrinkToFit();
  120. ++partCount;
  121. poly->setName(GetPolylineName(dfLevel, contourIndex, partCount == 1 && i + 2 >= nPoints ? 0 : partCount)); // if not enough points to add another polyline, we don't need to add the sub-part index
  122. if (params->contourLines.insert(dfLevel, poly) == params->contourLines.end())
  123. {
  124. //not enough memory?
  125. delete poly;
  126. return CE_Failure;
  127. }
  128. }
  129. poly = nullptr;
  130. vertices = nullptr;
  131. }
  132. continue;
  133. }
  134. }
  135. if (!poly)
  136. {
  137. if (nPoints < i + 2)
  138. {
  139. // a single point left? Nothing we can do...
  140. //ccLog::Warning("An isolated point was discarded");
  141. break;
  142. }
  143. //we need to instantiate a new polyline
  144. vertices = new ccPointCloud("vertices");
  145. vertices->setEnabled(false);
  146. poly = new ccPolyline(vertices);
  147. poly->addChild(vertices);
  148. poly->setClosed(false);
  149. //add the 'const altitude' meta-data as well
  150. poly->setMetaData(ccPolyline::MetaKeyConstAltitude(), QVariant(dfLevel));
  151. if (!vertices->reserve(nPoints - i) || !poly->reserve(nPoints - i))
  152. {
  153. //not enough memory
  154. delete poly;
  155. poly = nullptr;
  156. return CE_Failure;
  157. }
  158. }
  159. assert(vertices);
  160. poly->addPointIndex(vertices->size());
  161. vertices->addPoint(P);
  162. }
  163. if (poly)
  164. {
  165. poly->shrinkToFit();
  166. vertices->shrinkToFit();
  167. poly->setName(GetPolylineName(dfLevel, contourIndex, partCount == 0 ? 0 : partCount + 1));
  168. if (params->contourLines.insert(dfLevel, poly) == params->contourLines.end())
  169. {
  170. //not enough memory?
  171. delete poly;
  172. return CE_Failure;
  173. }
  174. }
  175. return CE_None;
  176. }
  177. #endif //CC_GDAL_SUPPORT
  178. bool ccContourLinesGenerator::GenerateContourLines( ccRasterGrid* rasterGrid,
  179. const CCVector2d& gridMinCornerXY,
  180. const Parameters& params,
  181. std::vector<ccPolyline*>& contourLines)
  182. {
  183. if (!rasterGrid || !rasterGrid->isValid())
  184. {
  185. ccLog::Error("Need a valid raster/cloud to compute contours!");
  186. assert(false);
  187. return false;
  188. }
  189. if (params.startAltitude > params.maxAltitude)
  190. {
  191. ccLog::Error("Start value is above the layer maximum value!");
  192. assert(false);
  193. return false;
  194. }
  195. if (params.step < 0)
  196. {
  197. ccLog::Error("Invalid step value");
  198. assert(false);
  199. return false;
  200. }
  201. if (params.minVertexCount < 3)
  202. {
  203. ccLog::Error("Invalid input parameter: can't have less than 3 vertices per contour line");
  204. assert(false);
  205. return false;
  206. }
  207. bool sparseLayer = (params.altitudes && params.altitudes->currentSize() != rasterGrid->height * rasterGrid->width);
  208. if (sparseLayer && !std::isfinite(params.emptyCellsValue))
  209. {
  210. ccLog::Error("Invalid empty cell value (sparse layer)");
  211. assert(false);
  212. return false;
  213. }
  214. unsigned levelCount = 1;
  215. if (CCCoreLib::GreaterThanEpsilon(params.step))
  216. {
  217. levelCount += static_cast<unsigned>((params.maxAltitude - params.startAltitude) / params.step); //static_cast is equivalent to floor if value >= 0
  218. }
  219. try
  220. {
  221. #ifdef CC_GDAL_SUPPORT //use GDAL (more robust) - otherwise we will use an old code found on the Internet (with a strange behavior)
  222. //invoke the GDAL 'Contour Generator'
  223. ContourGenerationParameters gdalParams;
  224. gdalParams.grid = rasterGrid;
  225. gdalParams.projectContourOnAltitudes = params.projectContourOnAltitudes;
  226. GDALContourGeneratorH hCG = GDAL_CG_Create( rasterGrid->width,
  227. rasterGrid->height,
  228. std::isnan(params.emptyCellsValue) ? FALSE : TRUE,
  229. params.emptyCellsValue,
  230. params.step,
  231. params.startAltitude,
  232. ContourWriter,
  233. &gdalParams);
  234. if (!hCG)
  235. {
  236. ccLog::Error("[GDAL] Failed to create contour generator");
  237. return false;
  238. }
  239. //feed the scan lines
  240. {
  241. double* scanline = static_cast<double*>(CPLMalloc(sizeof(double) * rasterGrid->width));
  242. if (!scanline)
  243. {
  244. ccLog::Error("[GDAL] Not enough memory");
  245. return false;
  246. }
  247. unsigned layerIndex = 0;
  248. for (unsigned j = 0; j < rasterGrid->height; ++j)
  249. {
  250. const ccRasterGrid::Row& cellRow = rasterGrid->rows[j];
  251. for (unsigned i = 0; i < rasterGrid->width; ++i)
  252. {
  253. if (cellRow[i].nbPoints || !sparseLayer)
  254. {
  255. if (params.altitudes)
  256. {
  257. ScalarType value = params.altitudes->getValue(layerIndex++);
  258. scanline[i] = ccScalarField::ValidValue(value) ? value : params.emptyCellsValue;
  259. }
  260. else
  261. {
  262. scanline[i] = std::isfinite(cellRow[i].h) ? cellRow[i].h : params.emptyCellsValue;
  263. }
  264. }
  265. else
  266. {
  267. scanline[i] = params.emptyCellsValue;
  268. }
  269. }
  270. CPLErr error = GDAL_CG_FeedLine(hCG, scanline);
  271. if (error != CE_None)
  272. {
  273. ccLog::Error("[GDAL] An error occurred during contour lines generation");
  274. break;
  275. }
  276. }
  277. if (scanline)
  278. {
  279. CPLFree(scanline);
  280. }
  281. scanline = nullptr;
  282. //reproject contour lines from raster C.S. to the cloud C.S.
  283. auto levels = gdalParams.contourLines.uniqueKeys();
  284. for (double level : levels)
  285. {
  286. const auto polylinesAtLevel = gdalParams.contourLines.values(level);
  287. for (int i = polylinesAtLevel.size() - 1; i >= 0; --i)
  288. {
  289. ccPolyline* poly = polylinesAtLevel[i];
  290. if (static_cast<int>(poly->size()) < params.minVertexCount)
  291. {
  292. delete poly;
  293. poly = nullptr;
  294. continue;
  295. }
  296. for (unsigned i = 0; i < poly->size(); ++i)
  297. {
  298. CCVector3* P2D = const_cast<CCVector3*>(poly->getAssociatedCloud()->getPoint(i));
  299. CCVector3 P(static_cast<PointCoordinateType>((P2D->x - 0.5) * rasterGrid->gridStep + gridMinCornerXY.x),
  300. static_cast<PointCoordinateType>((P2D->y - 0.5) * rasterGrid->gridStep + gridMinCornerXY.y),
  301. P2D->z);
  302. *P2D = P;
  303. }
  304. //add contour
  305. contourLines.push_back(poly);
  306. }
  307. }
  308. gdalParams.contourLines.clear(); //just in case
  309. }
  310. GDAL_CG_Destroy(hCG);
  311. #else
  312. unsigned xDim = rasterGrid->width;
  313. unsigned yDim = rasterGrid->height;
  314. int margin = 0;
  315. if (!params.ignoreBorders)
  316. {
  317. margin = 1;
  318. xDim += 2;
  319. yDim += 2;
  320. }
  321. std::vector<double> grid(static_cast<size_t>(xDim) * yDim, 0);
  322. //fill grid
  323. {
  324. unsigned layerIndex = 0;
  325. for (unsigned j = 0; j < rasterGrid->height; ++j)
  326. {
  327. const ccRasterGrid::Row& cellRow = rasterGrid->rows[j];
  328. double* row = &(grid[(j + margin)*xDim + margin]);
  329. for (unsigned i = 0; i < rasterGrid->width; ++i)
  330. {
  331. if (cellRow[i].nbPoints || !sparseLayer)
  332. {
  333. if (params.altitudes)
  334. {
  335. ScalarType value = params.altitudes->getValue(layerIndex++);
  336. row[i] = ccScalarField::ValidValue(value) ? value : params.emptyCellsValue;
  337. }
  338. else
  339. {
  340. row[i] = std::isfinite(cellRow[i].h) ? cellRow[i].h : params.emptyCellsValue;
  341. }
  342. }
  343. else
  344. {
  345. row[i] = params.emptyCellsValue;
  346. }
  347. }
  348. }
  349. }
  350. //generate contour lines
  351. {
  352. Isolines<double> iso(static_cast<int>(xDim), static_cast<int>(yDim));
  353. if (!params.ignoreBorders)
  354. {
  355. iso.createOnePixelBorder(grid.data(), params.startAltitude - 1.0);
  356. }
  357. ccProgressDialog pDlg(true, params.parentWidget);
  358. pDlg.setMethodTitle(QObject::tr("Contour plot"));
  359. pDlg.setInfo(QObject::tr("Levels: %1\nCells: %2 x %3").arg(levelCount).arg(rasterGrid->width).arg(rasterGrid->height));
  360. pDlg.start();
  361. pDlg.show();
  362. QCoreApplication::processEvents();
  363. CCCoreLib::NormalizedProgress nProgress(&pDlg, levelCount);
  364. for (double v = params.startAltitude; v <= params.maxAltitude; v += params.step)
  365. {
  366. //extract contour lines for the current level
  367. iso.setThreshold(v);
  368. int lineCount = iso.find(grid.data());
  369. ccLog::PrintDebug(QString("[Rasterize][Isolines] value=%1 : %2 lines").arg(v).arg(lineCount));
  370. //convert them to poylines
  371. for (int i = 0; i < lineCount; ++i)
  372. {
  373. int vertCount = iso.getContourLength(i);
  374. unsigned subPartCount = 0;
  375. if (vertCount >= params.minVertexCount)
  376. {
  377. int startVi = 0; //we may have to split the polyline in multiple chunks
  378. while (startVi < vertCount)
  379. {
  380. ccPointCloud* vertices = new ccPointCloud("vertices");
  381. ccPolyline* poly = new ccPolyline(vertices);
  382. poly->addChild(vertices);
  383. bool isClosed = (startVi == 0 ? iso.isContourClosed(i) : false);
  384. if (poly->reserve(vertCount - startVi) && vertices->reserve(vertCount - startVi))
  385. {
  386. unsigned localIndex = 0;
  387. for (int vi = startVi; vi < vertCount; ++vi)
  388. {
  389. ++startVi;
  390. double x = iso.getContourX(i, vi) - margin;
  391. double y = iso.getContourY(i, vi) - margin;
  392. CCVector3 P;
  393. //DGM: we will only do the dimension mapping at export time
  394. //(otherwise the contour lines appear in the wrong orientation compared to the grid/raster which
  395. // is in the XY plane by default!)
  396. /*P.u[X] = */P.x = static_cast<PointCoordinateType>((x + 0.5) * rasterGrid->gridStep + gridMinCornerXY.x);
  397. /*P.u[Y] = */P.y = static_cast<PointCoordinateType>((y + 0.5) * rasterGrid->gridStep + gridMinCornerXY.y);
  398. if (params.projectContourOnAltitudes)
  399. {
  400. int xi = std::min(std::max(static_cast<int>(x), 0), static_cast<int>(rasterGrid->width) - 1);
  401. int yi = std::min(std::max(static_cast<int>(y), 0), static_cast<int>(rasterGrid->height) - 1);
  402. double h = rasterGrid->rows[yi][xi].h;
  403. if (std::isfinite(h))
  404. {
  405. /*P.u[Z] = */P.z = static_cast<PointCoordinateType>(h);
  406. }
  407. else
  408. {
  409. //DGM: we stop the current polyline
  410. isClosed = false;
  411. break;
  412. }
  413. }
  414. else
  415. {
  416. /*P.u[Z] = */P.z = static_cast<PointCoordinateType>(v);
  417. }
  418. vertices->addPoint(P);
  419. assert(localIndex < vertices->size());
  420. poly->addPointIndex(localIndex++);
  421. }
  422. assert(poly);
  423. if (poly->size() > 1)
  424. {
  425. poly->setClosed(isClosed); //if we have less vertices, it means we have 'chopped' the original contour
  426. vertices->setEnabled(false);
  427. //add the 'const altitude' meta-data as well
  428. poly->setMetaData(ccPolyline::MetaKeyConstAltitude(), QVariant(v));
  429. //add contour
  430. ++subPartCount;
  431. poly->setName(GetPolylineName(v, static_cast<unsigned>(lineCount + 1), isClosed ? 0 : subPartCount));
  432. try
  433. {
  434. contourLines.push_back(poly);
  435. }
  436. catch (const std::bad_alloc&)
  437. {
  438. ccLog::Warning("[ccContourLinesGenerator] Not enough memory");
  439. return false;
  440. }
  441. }
  442. else
  443. {
  444. delete poly;
  445. poly = nullptr;
  446. }
  447. }
  448. else
  449. {
  450. delete poly;
  451. poly = nullptr;
  452. ccLog::Warning("Not enough memory!");
  453. return false;
  454. }
  455. }
  456. }
  457. }
  458. if (!nProgress.oneStep())
  459. {
  460. //process cancelled by user
  461. break;
  462. }
  463. }
  464. }
  465. #endif
  466. }
  467. catch (const std::bad_alloc&)
  468. {
  469. ccLog::Warning("[ccContourLinesGenerator] Not enough memory");
  470. return false;
  471. }
  472. ccLog::Print(QString("[ccContourLinesGenerator] %1 iso-lines generated (%2 levels)").arg(contourLines.size()).arg(levelCount));
  473. return true;
  474. }