ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvRasterizeTool.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvRasterizeTool.h"
9 
10 // Local
11 #include "MainWindow.h"
12 #include "ecvCommon.h"
13 #include "ecvPersistentSettings.h"
14 #ifndef CV_GDAL_SUPPORT
15 #include "ecvIsolines.h" //old alternative code to generate contour lines (doesn't work very well :( )
16 #endif
17 
18 // CV_DB_LIB
19 #include <ecvColorScalesManager.h>
20 #include <ecvDisplayTools.h>
21 #include <ecvFileUtils.h>
22 #include <ecvGenericPointCloud.h>
23 #include <ecvMesh.h>
24 #include <ecvPointCloud.h>
25 #include <ecvPolyline.h>
26 #include <ecvProgressDialog.h>
27 #include <ecvScalarField.h>
28 
29 // qCC_io
30 // #include <ImageFileFilter.h>
31 
32 // Qt
33 #include <QFileDialog>
34 #include <QMap>
35 #include <QMessageBox>
36 #include <QPushButton>
37 #include <QSettings>
38 
39 // System
40 #include <assert.h>
41 
42 static const char HILLSHADE_FIELD_NAME[] = "Hillshade";
43 
45  QWidget* parent /*=0*/)
46  : QDialog(parent, Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint),
48  Ui::RasterizeToolDialog(),
49  m_cloud(cloud) {
50  setupUi(this);
51 
52 #ifdef CV_GDAL_SUPPORT
53  ignoreContourBordersCheckBox->setVisible(false);
54 #else
55  generateRasterPushButton->setDisabled(true);
56  generateRasterPushButton->setChecked(false);
57 #endif
58 
59  // force update
60  resampleOptionToggled(resampleCloudCheckBox->isChecked());
62 
63  connect(buttonBox, &QDialogButtonBox::accepted, this,
65  connect(buttonBox, &QDialogButtonBox::rejected, this,
67 
68  connect(gridStepDoubleSpinBox,
69  static_cast<void (QDoubleSpinBox::*)(double)>(
70  &QDoubleSpinBox::valueChanged),
72  connect(gridStepDoubleSpinBox,
73  static_cast<void (QDoubleSpinBox::*)(double)>(
74  &QDoubleSpinBox::valueChanged),
76  connect(emptyValueDoubleSpinBox,
77  static_cast<void (QDoubleSpinBox::*)(double)>(
78  &QDoubleSpinBox::valueChanged),
80  connect(dimensionComboBox,
81  static_cast<void (QComboBox::*)(int)>(
82  &QComboBox::currentIndexChanged),
84  connect(heightProjectionComboBox,
85  static_cast<void (QComboBox::*)(int)>(
86  &QComboBox::currentIndexChanged),
88  connect(scalarFieldProjection,
89  static_cast<void (QComboBox::*)(int)>(
90  &QComboBox::currentIndexChanged),
92  connect(fillEmptyCellsComboBox,
93  static_cast<void (QComboBox::*)(int)>(
94  &QComboBox::currentIndexChanged),
96 
97  connect(resampleCloudCheckBox, &QAbstractButton::toggled, this,
99  connect(updateGridPushButton, &QAbstractButton::clicked, this,
101  connect(generateCloudPushButton, &QAbstractButton::clicked, this,
102  [this]() { generateCloud(true); });
103  connect(generateImagePushButton, &QAbstractButton::clicked, this,
105  connect(generateRasterPushButton, &QAbstractButton::clicked, this,
107  connect(generateASCIIPushButton, &QAbstractButton::clicked, this,
109  connect(generateMeshPushButton, &QAbstractButton::clicked, this,
111  connect(generateContoursPushButton, &QAbstractButton::clicked, this,
113  connect(exportContoursPushButton, &QAbstractButton::clicked, this,
115  connect(clearContoursPushButton, &QAbstractButton::clicked, this,
117 
118  connect(generateHillshadePushButton, &QAbstractButton::clicked, this,
120 
121  connect(activeLayerComboBox,
122  static_cast<void (QComboBox::*)(int)>(
123  &QComboBox::currentIndexChanged),
124  this, [this](int index) { activeLayerChanged(index); });
125 
126  // custom bbox editor
127  ccBBox gridBBox = m_cloud ? m_cloud->getOwnBB() : ccBBox();
128  if (gridBBox.isValid()) {
129  createBoundingBoxEditor(gridBBox, this);
130  connect(editGridToolButton, &QAbstractButton::clicked, this,
132  } else {
133  editGridToolButton->setEnabled(false);
134  }
135 
136  if (m_cloud) {
137  cloudNameLabel->setText(
138  QStringLiteral("<b>%1</b> (%2 points)")
139  .arg(m_cloud->getName(),
140  QLocale::system().toString(m_cloud->size())));
141  if (m_cloud->hasScalarFields()) {
142  interpolateSFCheckBox->setEnabled(true);
143  scalarFieldProjection->setEnabled(true);
144  } else {
145  interpolateSFCheckBox->setChecked(false);
146  interpolateSFCheckBox->setEnabled(false);
147  scalarFieldProjection->setEnabled(false);
148  }
149 
150  // populate layer box
151  activeLayerComboBox->addItem(
153  QVariant(LAYER_HEIGHT));
154  if (m_cloud->hasColors()) {
155  activeLayerComboBox->addItem("RGB", QVariant(LAYER_RGB));
156  }
157  if (cloud->isA(CV_TYPES::POINT_CLOUD) && cloud->hasScalarFields()) {
158  ccPointCloud* pc = static_cast<ccPointCloud*>(cloud);
159  for (unsigned i = 0; i < pc->getNumberOfScalarFields(); ++i) {
160  activeLayerComboBox->addItem(pc->getScalarField(i)->getName(),
161  QVariant(LAYER_SF));
162  }
163  }
164 
165  activeLayerComboBox->setEnabled(activeLayerComboBox->count() > 1);
166 
167  // add window
168  create2DView(mapFrame);
169  }
170 
171  loadSettings();
172 
173  updateGridInfo();
174 
175  gridIsUpToDate(false);
176 
177  resize(minimumSize());
178 }
179 
181 
183  while (!m_contourLines.empty()) {
184  ccPolyline* poly = m_contourLines.back();
187  delete poly;
188  m_contourLines.pop_back();
189  }
190 
191  exportContoursPushButton->setEnabled(false);
192  clearContoursPushButton->setEnabled(false);
193 
195 }
196 
199  updateGridInfo();
200  return true;
201  }
202 
203  return false;
204 }
205 
207  gridWidthLabel->setText(getGridSizeAsString());
208 }
209 
211  return gridStepDoubleSpinBox->value();
212 }
213 
215  switch (field) {
217  return generateCountSFcheckBox->isChecked();
219  return generateMinHeightSFcheckBox->isChecked();
221  return generateMaxHeightSFcheckBox->isChecked();
223  return generateAvgHeightSFcheckBox->isChecked();
225  return generateStdDevHeightSFcheckBox->isChecked();
227  return generateHeightRangeSFcheckBox->isChecked();
228  default:
229  assert(false);
230  };
231 
232  return false;
233 }
234 
236  return resampleCloudCheckBox->isEnabled() &&
237  resampleCloudCheckBox->isChecked();
238 }
239 
241  int dim = dimensionComboBox->currentIndex();
242  assert(dim >= 0 && dim < 3);
243 
244  return static_cast<unsigned char>(dim);
245 }
246 
248  warningResampleWithAverageLabel->setVisible(
249  resampleCloudCheckBox->isChecked() &&
252 }
253 
255  // we can't use the 'resample origin cloud' option with 'average height'
256  // projection resampleCloudCheckBox->setEnabled(index !=
257  // PROJ_AVERAGE_VALUE); DGM: now we can! We simply display a warning message
258  warningResampleWithAverageLabel->setVisible(
259  resampleCloudCheckBox->isChecked() &&
261  gridIsUpToDate(false);
262 }
263 
265  gridIsUpToDate(false);
266 }
267 
269  updateGridInfo();
270  gridIsUpToDate(false);
271 }
272 
274  bool autoRedraw /*=true*/) {
275  if (activeLayerComboBox->itemData(layerIndex).toInt() == LAYER_SF &&
276  activeLayerComboBox->itemText(layerIndex) != HILLSHADE_FIELD_NAME) {
277  interpolateSFCheckBox->setChecked(
278  true); // force the choice of a SF projection strategy
279  interpolateSFCheckBox->setEnabled(false);
280  generateImagePushButton->setEnabled(false);
281  generateASCIIPushButton->setEnabled(false);
282  projectContoursOnAltCheckBox->setEnabled(true);
283  } else {
284  // interpolateSFCheckBox->setChecked(false); //DGM: we can't force that,
285  // just let the user decide
286  interpolateSFCheckBox->setEnabled(
287  m_cloud && m_cloud->hasScalarFields()); // we need SF fields!
288  generateImagePushButton->setEnabled(true);
289  generateASCIIPushButton->setEnabled(true);
290  projectContoursOnAltCheckBox->setEnabled(false);
291  }
292 
293  if (m_rasterCloud) {
294  // active layer = RGB colors
295  if (activeLayerComboBox->currentData().toInt() == LAYER_RGB) {
296  if (!m_rasterCloud->hasColors()) {
297  gridIsUpToDate(false);
298  }
299  gridLayerRangeLabel->setText("[0 ; 255]");
300 
301  m_rasterCloud->showColors(true);
302  m_rasterCloud->showSF(false);
303  } else {
304  // does the selected 'layer' exist?
306  qPrintable(activeLayerComboBox->itemText(layerIndex)));
308  m_rasterCloud->showSF(true);
309  m_rasterCloud->showColors(false);
310 
311  if (sfIndex >= 0) {
312  ccScalarField* activeLayer =
314  if (activeLayer) {
315  const ccScalarField::Range& layerValues =
316  activeLayer->displayRange();
317  gridLayerRangeLabel->setText(
318  QString("%1 [%2 ; %3]")
319  .arg(layerValues.range())
320  .arg(layerValues.min())
321  .arg(layerValues.max()));
322  contourStartDoubleSpinBox->setValue(layerValues.min());
323  contourStepDoubleSpinBox->setValue(layerValues.range() /
324  10.0);
325  } else {
326  assert(false);
327  gridLayerRangeLabel->setText("no active layer?!");
328  }
329  } else {
330  gridLayerRangeLabel->setText("Layer not computed");
331  gridIsUpToDate(false);
332  }
333  }
334 
335  if (ecvDisplayTools::GetMainWindow() && autoRedraw) {
337  }
338  }
339 }
340 
342  ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy =
343  getFillEmptyCellsStrategy(fillEmptyCellsComboBox);
344 
345  bool active = fillEmptyCellsStrategy == ccRasterGrid::FILL_CUSTOM_HEIGHT ||
346  fillEmptyCellsStrategy == ccRasterGrid::INTERPOLATE_DELAUNAY;
347 
348  emptyValueDoubleSpinBox->setEnabled(active);
349  emptyValueDoubleSpinBox->setVisible(active);
350  gridIsUpToDate(false);
351 }
352 
354 
356  return emptyValueDoubleSpinBox->value();
357 }
358 
360  switch (heightProjectionComboBox->currentIndex()) {
361  case 0:
363  case 1:
365  case 2:
367  default:
368  // shouldn't be possible for this option!
369  assert(false);
370  }
371 
373 }
374 
376  if ( !interpolateSFCheckBox
377  ->isChecked()) // DGM: the check-box might be disabled to
378  // actually 'force' the user to choose a
379  // projection type
380  {
381  return ccRasterGrid::INVALID_PROJECTION_TYPE; // means that we don't
382  // want to keep SF values
383  }
384 
385  switch (scalarFieldProjection->currentIndex()) {
386  case 0:
388  case 1:
390  case 2:
392  default:
393  // shouldn't be possible for this option!
394  assert(false);
395  }
396 
398 }
399 
401  QSettings settings;
402  settings.beginGroup(ecvPS::HeightGridGeneration());
403  int projType = settings.value("ProjectionType",
404  heightProjectionComboBox->currentIndex())
405  .toInt();
406  int projDim =
407  settings.value("ProjectionDim", dimensionComboBox->currentIndex())
408  .toInt();
409  bool sfProj =
410  settings.value("SfProjEnabled", interpolateSFCheckBox->isChecked())
411  .toBool();
412  int sfProjStrategy = settings.value("SfProjStrategy",
413  scalarFieldProjection->currentIndex())
414  .toInt();
415  int fillStrategy = settings.value("FillStrategy",
416  fillEmptyCellsComboBox->currentIndex())
417  .toInt();
418  double step = settings.value("GridStep", gridStepDoubleSpinBox->value())
419  .toDouble();
420  double emptyHeight =
421  settings.value("EmptyCellsHeight", emptyValueDoubleSpinBox->value())
422  .toDouble();
423  bool genCountSF = settings.value("GenerateCountSF",
424  generateCountSFcheckBox->isChecked())
425  .toBool();
426  bool resampleCloud = settings.value("ResampleOrigCloud",
427  resampleCloudCheckBox->isChecked())
428  .toBool();
429  int minVertexCount =
430  settings.value("MinVertexCount", minVertexCountSpinBox->value())
431  .toInt();
432  bool ignoreBorders =
433  settings.value("IgnoreBorders",
434  ignoreContourBordersCheckBox->isChecked())
435  .toBool();
436  bool generateCountSF = settings.value("generateCountSF",
437  generateCountSFcheckBox->isChecked())
438  .toBool();
439  bool generateMinHeightSF =
440  settings.value("generateMinHeightSF",
441  generateMinHeightSFcheckBox->isChecked())
442  .toBool();
443  bool generateMaxHeightSF =
444  settings.value("generateMaxHeightSF",
445  generateMinHeightSFcheckBox->isChecked())
446  .toBool();
447  bool generateAbgHeightSF =
448  settings.value("generateAvgHeightSF",
449  generateAvgHeightSFcheckBox->isChecked())
450  .toBool();
451  bool generateStdDevHeightSF =
452  settings.value("generateStdDevHeightSF",
453  generateStdDevHeightSFcheckBox->isChecked())
454  .toBool();
455  bool generateHeightRangeSF =
456  settings.value("generateHeightRangeSF",
457  generateHeightRangeSFcheckBox->isChecked())
458  .toBool();
459  bool projectContoursOnAlt =
460  settings.value("projectContoursOnAlt",
461  projectContoursOnAltCheckBox->isChecked())
462  .toBool();
463 
464  settings.endGroup();
465 
466  gridStepDoubleSpinBox->setValue(step);
467  heightProjectionComboBox->setCurrentIndex(projType);
468  fillEmptyCellsComboBox->setCurrentIndex(fillStrategy);
469  emptyValueDoubleSpinBox->setValue(emptyHeight);
470  dimensionComboBox->setCurrentIndex(projDim);
471  interpolateSFCheckBox->setChecked(sfProj);
472  scalarFieldProjection->setCurrentIndex(sfProjStrategy);
473  generateCountSFcheckBox->setChecked(genCountSF);
474  resampleCloudCheckBox->setChecked(resampleCloud);
475  minVertexCountSpinBox->setValue(minVertexCount);
476  ignoreContourBordersCheckBox->setChecked(ignoreBorders);
477  generateCountSFcheckBox->setChecked(generateCountSF);
478  generateMinHeightSFcheckBox->setChecked(generateMinHeightSF);
479  generateMinHeightSFcheckBox->setChecked(generateMaxHeightSF);
480  generateAvgHeightSFcheckBox->setChecked(generateAbgHeightSF);
481  generateStdDevHeightSFcheckBox->setChecked(generateStdDevHeightSF);
482  generateHeightRangeSFcheckBox->setChecked(generateHeightRangeSF);
483  projectContoursOnAltCheckBox->setChecked(projectContoursOnAlt);
484 }
485 
487  if (!m_contourLines.empty()) {
488  // ask the user to confirm before it's tool late!
489  if (QMessageBox::question(this, "Unsaved contour lines",
490  "Contour lines have not been exported! Do "
491  "you really want to close the tool?",
492  QMessageBox::Yes,
493  QMessageBox::No) == QMessageBox::No)
494  return false;
495  }
496  return true;
497 }
498 
500  if (!canClose()) return;
501 
502  saveSettings();
503  accept();
504 }
505 
507  if (!canClose()) return;
508 
509  reject();
510 }
511 
513  QSettings settings;
514  settings.beginGroup(ecvPS::HeightGridGeneration());
515  settings.setValue("ProjectionType",
516  heightProjectionComboBox->currentIndex());
517  settings.setValue("ProjectionDim", dimensionComboBox->currentIndex());
518  settings.setValue("SfProjEnabled", interpolateSFCheckBox->isChecked());
519  settings.setValue("SfProjStrategy", scalarFieldProjection->currentIndex());
520  settings.setValue("FillStrategy", fillEmptyCellsComboBox->currentIndex());
521  settings.setValue("GridStep", gridStepDoubleSpinBox->value());
522  settings.setValue("EmptyCellsHeight", emptyValueDoubleSpinBox->value());
523  settings.setValue("GenerateCountSF", generateCountSFcheckBox->isChecked());
524  settings.setValue("ResampleOrigCloud", resampleCloudCheckBox->isChecked());
525  settings.setValue("MinVertexCount", minVertexCountSpinBox->value());
526  settings.setValue("IgnoreBorders",
527  ignoreContourBordersCheckBox->isChecked());
528  settings.setValue("generateCountSF", generateCountSFcheckBox->isChecked());
529  settings.setValue("generateMinHeightSF",
530  generateMinHeightSFcheckBox->isChecked());
531  settings.setValue("generateMaxHeightSF",
532  generateMinHeightSFcheckBox->isChecked());
533  settings.setValue("generateAvgHeightSF",
534  generateAvgHeightSFcheckBox->isChecked());
535  settings.setValue("generateStdDevHeightSF",
536  generateStdDevHeightSFcheckBox->isChecked());
537  settings.setValue("generateHeightRangeSF",
538  generateHeightRangeSFcheckBox->isChecked());
539  settings.setValue("projectContoursOnAlt",
540  projectContoursOnAltCheckBox->isChecked());
541  settings.endGroup();
542 }
543 
545  if (state) {
546  // standard button
547  updateGridPushButton->setStyleSheet(QString());
548  } else {
549  // red button
550  updateGridPushButton->setStyleSheet(
551  "color: white; background-color:red;");
552  }
553  updateGridPushButton->setDisabled(state);
554 
555  tabWidget->setEnabled(state);
556 }
557 
559  const std::vector<ccRasterGrid::ExportableFields>& exportedFields,
560  bool interpolateSF,
561  bool interpolateColors,
562  bool copyHillshadeSF,
563  QString activeSFName,
564  bool exportToOriginalCS) const {
565  if (!m_cloud || !m_grid.isValid()) return 0;
566 
567  // default values
568  double emptyCellsHeight = 0;
569  double minHeight = m_grid.minHeight;
570  double maxHeight = m_grid.maxHeight;
571  // get real values
572  ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy =
573  getFillEmptyCellsStrategyExt(emptyCellsHeight, minHeight,
574  maxHeight);
575 
576  // call parent method
578  exportedFields, interpolateSF, interpolateColors,
579  /*resampleInputCloudXY=*/resampleOriginalCloud(),
580  /*resampleInputCloudZ=*/getTypeOfProjection() !=
582  /*inputCloud=*/m_cloud,
583  /*fillEmptyCells=*/fillEmptyCellsStrategy !=
585  emptyCellsHeight, exportToOriginalCS);
586 
587  // success?
588  if (cloudGrid) {
589  // add the hillshade SF
590  if (copyHillshadeSF) {
591  int hillshadeSFIdx = m_rasterCloud->getScalarFieldIndexByName(
593  if (hillshadeSFIdx >= 0) {
594  cloudViewer::ScalarField* hillshadeField =
595  m_rasterCloud->getScalarField(hillshadeSFIdx);
596  if (hillshadeField->currentSize() == cloudGrid->size()) {
597  try {
598  ccScalarField* hillshadeClone = new ccScalarField(
599  *static_cast<ccScalarField*>(hillshadeField));
600  cloudGrid->addScalarField(hillshadeClone);
601  } catch (const std::bad_alloc&) {
603  "[Rasterize] Not enough memory to export the "
604  "hillshade field");
605  }
606  }
607  }
608  }
609 
610  // currently displayed SF
611  int activeSFIndex =
612  cloudGrid->getScalarFieldIndexByName(qPrintable(activeSFName));
613  cloudGrid->showSF(activeSFIndex >= 0);
614  if (activeSFIndex < 0 && cloudGrid->getNumberOfScalarFields() != 0) {
615  // if no SF is displayed, we should at least set a valid one (for
616  // later)
617  activeSFIndex =
618  static_cast<int>(cloudGrid->getNumberOfScalarFields()) - 1;
619  }
620  cloudGrid->setCurrentDisplayedScalarField(activeSFIndex);
621 
622  cloudGrid->showColors(interpolateColors);
623 
624  // don't forget the original shift
625  cloudGrid->setGlobalShift(m_cloud->getGlobalShift());
626  cloudGrid->setGlobalScale(m_cloud->getGlobalScale());
627  }
628 
629  return cloudGrid;
630 }
631 
633  // special case: remove the (temporary) hillshade field entry
634  int hillshadeIndex = activeLayerComboBox->findText(HILLSHADE_FIELD_NAME);
635  if (hillshadeIndex >= 0) {
636  if (activeLayerComboBox->currentIndex() == hillshadeIndex &&
637  activeLayerComboBox->count() > 1) {
638  activeLayerComboBox->setCurrentIndex(0);
639  }
640  activeLayerComboBox->removeItem(hillshadeIndex);
641  }
642 
643  // remove the previous cloud
644  if (m_rasterCloud) {
648  }
649 
650  delete m_rasterCloud;
651  m_rasterCloud = 0;
652  }
653 
654  bool activeLayerIsSF =
655  (activeLayerComboBox->currentData().toInt() == LAYER_SF);
656  bool activeLayerIsRGB =
657  (activeLayerComboBox->currentData().toInt() == LAYER_RGB);
658  bool interpolateSF =
659  activeLayerIsSF || (getTypeOfSFInterpolation() !=
661  bool success = updateGrid(interpolateSF);
662 
663  if (success && ecvDisplayTools::GetMainWindow()) {
664  // convert grid to point cloud
665  std::vector<ccRasterGrid::ExportableFields> exportedFields;
666  try {
667  // we always compute the default 'height' layer
668  exportedFields.push_back(ccRasterGrid::PER_CELL_VALUE);
669  // but we may also have to compute the 'original SF(s)' layer(s)
670  QString activeLayerName = activeLayerComboBox->currentText();
672  exportedFields,
673  /*interpolateSF=*/interpolateSF,
674  /*interpolateColors=*/activeLayerIsRGB,
675  /*copyHillshadeSF=*/false, activeLayerName, false);
676  } catch (const std::bad_alloc&) {
677  // see below
678  }
679 
680  if (m_rasterCloud) {
683  update2DDisplayZoom(box);
684 
685  // update
686  activeLayerChanged(activeLayerComboBox->currentIndex(), false);
687  } else {
688  CVLog::Error("Not enough memory!");
690  }
691  }
692 
693  gridIsUpToDate(success);
694 }
695 
696 bool ccRasterizeTool::updateGrid(bool interpolateSF /*=false*/) {
697  if (!m_cloud) {
698  assert(false);
699  return false;
700  }
701 
702  // main parameters
705  interpolateSF ? getTypeOfSFInterpolation()
707  bool fillEmptyCells = (getFillEmptyCellsStrategy(fillEmptyCellsComboBox) ==
709 
710  // cloud bounding-box --> grid size
711  ccBBox box = getCustomBBox();
712  if (!box.isValid()) {
713  return false;
714  }
715 
716  // clear volume info
717  {
718  volumeLabel->setText("0");
719  filledCellsPercentageLabel->setText("0 %");
720  }
721 
722  unsigned gridWidth = 0, gridHeight = 0;
723  if (!getGridSize(gridWidth, gridHeight)) {
724  return false;
725  }
726 
727  // grid size
728  unsigned gridTotalSize = gridWidth * gridHeight;
729  if (gridTotalSize == 1) {
730  if (QMessageBox::question(this, "Unexpected grid size",
731  "The generated grid will only have 1 cell! "
732  "Do you want to proceed anyway?",
733  QMessageBox::Yes,
734  QMessageBox::No) == QMessageBox::No)
735  return false;
736  } else if (gridTotalSize > 10000000) {
737  if (QMessageBox::question(
738  this, "Big grid size",
739  "The generated grid will have more than 10.000.000 cells! "
740  "Do you want to proceed anyway?",
741  QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
742  return false;
743  }
744 
746 
747  // grid step
748  double gridStep = getGridStep();
749  assert(gridStep != 0);
750 
751  // memory allocation
752  CCVector3d minCorner = CCVector3d::fromArray(box.minCorner().u);
753  if (!m_grid.init(gridWidth, gridHeight, gridStep, minCorner)) {
754  // not enough memory
755  CVLog::Error("Not enough memory");
756  return false;
757  }
758 
759  // vertical dimension
760  const unsigned char Z = getProjectionDimension();
761  assert(Z <= 2);
762 
763  ecvProgressDialog pDlg(true, this);
764  if (!m_grid.fillWith(m_cloud, Z, projectionType, fillEmptyCells,
765  interpolateSFs, &pDlg)) {
766  return false;
767  }
768 
769  // update volume estimate
770  {
771  double hSum = 0;
772  unsigned filledCellCount = 0;
773  for (unsigned j = 0; j < m_grid.height; ++j) {
774  const ccRasterGrid::Row& row = m_grid.rows[j];
775  for (unsigned i = 0; i < m_grid.width; ++i) {
776  if (std::isfinite(row[i].h)) {
777  hSum += row[i].h;
778  ++filledCellCount;
779  }
780  }
781  }
782 
783  if (filledCellCount) {
784  double cellArea = m_grid.gridStep * m_grid.gridStep;
785  volumeLabel->setText(QString::number(hSum * cellArea));
786  filledCellsPercentageLabel->setText(
787  QString::number(static_cast<double>(100 * filledCellCount) /
789  'f', 2) +
790  " %");
791  }
792  }
793 
794  CVLog::Print(QString("[Rasterize] Current raster grid:\n\tSize: %1 x "
795  "%2\n\tHeight values: [%3 ; %4]")
796  .arg(m_grid.width)
797  .arg(m_grid.height)
798  .arg(m_grid.minHeight)
799  .arg(m_grid.maxHeight));
800 
801  return true;
802 }
803 
804 ccPointCloud* ccRasterizeTool::generateCloud(bool autoExport /*=true*/) const {
805  if (!m_cloud || !m_grid.isValid()) {
806  return 0;
807  }
808 
809  // look for fields to be exported
810  std::vector<ccRasterGrid::ExportableFields> exportedFields;
811  try {
812  exportedFields.push_back(ccRasterGrid::PER_CELL_VALUE);
814  exportedFields.push_back(ccRasterGrid::PER_CELL_COUNT);
816  exportedFields.push_back(ccRasterGrid::PER_CELL_MIN_VALUE);
818  exportedFields.push_back(ccRasterGrid::PER_CELL_MAX_VALUE);
820  exportedFields.push_back(ccRasterGrid::PER_CELL_AVG_VALUE);
822  exportedFields.push_back(ccRasterGrid::PER_CELL_VALUE_STD_DEV);
824  exportedFields.push_back(ccRasterGrid::PER_CELL_VALUE_RANGE);
825  } catch (const std::bad_alloc&) {
826  CVLog::Error("Not enough memory!");
827  return 0;
828  }
829  QString activeLayerName = activeLayerComboBox->currentText();
830  bool activeLayerIsSF =
831  (activeLayerComboBox->currentData().toInt() == LAYER_SF);
832  // bool activeLayerIsRGB = (activeLayerComboBox->currentData().toInt() ==
833  // LAYER_RGB);
834  ccPointCloud* rasterCloud =
835  convertGridToCloud(exportedFields,
836  /*interpolateSF=*/
839  activeLayerIsSF,
840  /*interpolateColors=*/true,
841  /*copyHillshadeSF=*/true, activeLayerName, true);
842 
843  if (rasterCloud && autoExport) {
844  if (m_cloud->getParent()) {
845  m_cloud->getParent()->addChild(rasterCloud);
846  }
847  // rasterCloud->setDisplay(m_cloud->getDisplay());
848 
849  if (m_cloud->isEnabled()) {
850  m_cloud->setEnabled(false);
852  "[Rasterize] Previously selected entity (source cloud) has "
853  "been hidden!");
854  }
855 
856  MainWindow* mainWindow = MainWindow::TheInstance();
857  if (mainWindow) {
858  mainWindow->addToDB(rasterCloud);
859  CVLog::Print(QString("[Rasterize] Cloud '%1' successfully exported")
860  .arg(rasterCloud->getName()));
861  } else {
862  assert(false);
863  delete rasterCloud;
864  }
865  }
866 
867  return rasterCloud;
868 }
869 
871  ccPointCloud* rasterCloud = generateCloud(false);
872  if (rasterCloud) {
873  std::string errorStr;
878  IGNORE_MAX_EDGE_LENGTH,
879  getProjectionDimension(), errorStr);
880  ccMesh* rasterMesh = 0;
881  if (baseMesh) {
882  rasterMesh = new ccMesh(baseMesh, rasterCloud);
883  delete baseMesh;
884  baseMesh = 0;
885  }
886  if (rasterMesh) {
887  if (m_cloud->getParent())
888  m_cloud->getParent()->addChild(rasterMesh);
889  rasterCloud->setEnabled(false);
890  rasterCloud->setVisible(true);
891  rasterCloud->setName("vertices");
892  rasterMesh->addChild(rasterCloud);
893  // rasterMesh->setDisplay_recursive(m_cloud->getDisplay());
894  rasterMesh->setName(m_cloud->getName() + ".mesh");
895  rasterMesh->showSF(rasterCloud->sfShown());
896  rasterMesh->showColors(rasterCloud->colorsShown());
897 
898  MainWindow* mainWindow = MainWindow::TheInstance();
899  if (mainWindow) MainWindow::TheInstance()->addToDB(rasterMesh);
900  CVLog::Print(QString("[Rasterize] Mesh '%1' successfully exported")
901  .arg(rasterMesh->getName()));
902  } else {
903  CVLog::Error(QString("Failed to create mesh ('%1')")
904  .arg(QString::fromStdString(errorStr)));
905  }
906  }
907 }
908 
909 #ifdef CV_GDAL_SUPPORT
910 // GDAL
911 #include <cpl_string.h>
912 #include <gdal.h>
913 #include <gdal_alg.h>
914 #include <gdal_priv.h>
915 #include <ogr_api.h>
916 // local
917 #include "ui_rasterExportOptionsDlg.h"
918 
919 // system
920 #include <assert.h>
921 
922 class RasterExportOptionsDlg : public QDialog,
923  public Ui::RasterExportOptionsDialog {
924 public:
925  explicit RasterExportOptionsDlg(QWidget* parent = 0)
926  : QDialog(parent, Qt::Tool), Ui::RasterExportOptionsDialog() {
927  setupUi(this);
928  }
929 };
930 
931 #endif
932 
934 #ifdef CV_GDAL_SUPPORT
935 
936  if (!m_cloud || !m_grid.isValid()) {
937  return;
938  }
939 
940  bool hasScalarFields = !m_grid.scalarFields.empty();
941  int visibleSfIndex = -1;
942  if (activeLayerComboBox->currentData().toInt() == LAYER_SF &&
944  // the indexes in the 'm_grid.scalarFields' are the same as in the cloud
945  visibleSfIndex =
946  static_cast<ccPointCloud*>(m_cloud)->getScalarFieldIndexByName(
947  qPrintable(activeLayerComboBox->currentText()));
948  }
949 
950  // which (and how many) bands shall we create?
951  ExportBands exportBands;
952  exportBands.height = true; // height by default
953  exportBands.rgb = false; // not a good idea to mix RGB and height values!
954 
955  RasterExportOptionsDlg reoDlg;
956  reoDlg.dimensionsLabel->setText(
957  QString("%1 x %2").arg(m_grid.width).arg(m_grid.height));
958  reoDlg.exportRGBCheckBox->setEnabled(m_grid.hasColors);
959  reoDlg.exportRGBCheckBox->setChecked(exportBands.rgb);
960  reoDlg.exportHeightsCheckBox->setChecked(exportBands.height);
961  reoDlg.exportDensityCheckBox->setChecked(exportBands.density);
962  reoDlg.exportActiveLayerCheckBox->setChecked(exportBands.visibleSF);
963  reoDlg.exportActiveLayerCheckBox->setEnabled(visibleSfIndex >= 0);
964  reoDlg.exportAllSFCheckBox->setEnabled(hasScalarFields);
965  reoDlg.exportAllSFCheckBox->setChecked(exportBands.allSFs);
966 
967  while (true) {
968  if (!reoDlg.exec()) {
969  // cancelled by user
970  return;
971  }
972 
973  // check the selection
974  if (reoDlg.exportRGBCheckBox->isEnabled() &&
975  reoDlg.exportRGBCheckBox->isChecked() &&
976  (reoDlg.exportHeightsCheckBox->isChecked() ||
977  reoDlg.exportDensityCheckBox->isChecked() ||
978  reoDlg.exportActiveLayerCheckBox->isChecked() ||
979  reoDlg.exportAllSFCheckBox->isChecked())) {
980  if (QMessageBox::warning(
981  0, "Mixed raster",
982  "Mixing colors and other layers will result in\na "
983  "strange raster file with 64 bits color bands\n(some "
984  "applications won't handle them properly)",
985  QMessageBox::Ignore,
986  QMessageBox::Retry) == QMessageBox::Ignore) {
987  // the user ignored the warning
988  break;
989  }
990  } else {
991  // nothing to worry about :D
992  break;
993  }
994  }
995 
996  // we ask the output filename AFTER displaying the export parameters ;)
997  QString outputFilename;
998  {
999  QSettings settings;
1000  settings.beginGroup(ecvPS::HeightGridGeneration());
1001  QString imageSavePath =
1002  settings.value("savePathImage", ecvFileUtils::defaultDocPath())
1003  .toString();
1004  outputFilename = QFileDialog::getSaveFileName(
1005  const_cast<ccRasterizeTool*>(this), "Save height grid raster",
1006  imageSavePath + QString("/raster.tif"), "geotiff (*.tif)");
1007 
1008  if (outputFilename.isNull()) {
1009  return;
1010  }
1011 
1012  // save current export path to persistent settings
1013  settings.setValue("savePathImage",
1014  QFileInfo(outputFilename).absolutePath());
1015  }
1016 
1017  exportBands.height = reoDlg.exportHeightsCheckBox->isChecked();
1018  exportBands.rgb = reoDlg.exportRGBCheckBox->isChecked();
1019  exportBands.density = reoDlg.exportDensityCheckBox->isChecked();
1020  exportBands.allSFs = reoDlg.exportAllSFCheckBox->isChecked();
1021  exportBands.visibleSF = reoDlg.exportActiveLayerCheckBox->isChecked();
1022 
1023  ExportGeoTiff(outputFilename, exportBands,
1024  getFillEmptyCellsStrategy(fillEmptyCellsComboBox), m_grid,
1026  getCustomHeightForEmptyCells(), m_cloud, visibleSfIndex);
1027 
1028 #else
1029 
1030  assert(false);
1031  CVLog::Error(
1032  "[Rasterize] GDAL not supported by this version! Can't generate a "
1033  "raster...");
1034 
1035 #endif
1036 }
1037 
1039  QString outputFilename,
1040  const ExportBands& exportBands,
1041  ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy,
1042  const ccRasterGrid& grid,
1043  const ccBBox& gridBBox,
1044  unsigned char Z,
1045  double customHeightForEmptyCells /*=std::numeric_limits<double>::quiet_NaN()*/
1046  ,
1047  ccGenericPointCloud* originCloud /*=0*/,
1048  int visibleSfIndex /*=-1*/) {
1049 #ifdef CV_GDAL_SUPPORT
1050 
1051  if (exportBands.visibleSF && visibleSfIndex < 0) {
1052  assert(false);
1053  return false;
1054  }
1055 
1056  // vertical dimension
1057  assert(Z <= 2);
1058  const unsigned char X = Z == 2 ? 0 : Z + 1;
1059  const unsigned char Y = X == 2 ? 0 : X + 1;
1060 
1061  // global shift
1062  assert(gridBBox.isValid());
1063  double shiftX = gridBBox.minCorner().u[X];
1064  double shiftY = gridBBox.maxCorner().u[Y];
1065  double shiftZ = 0.0;
1066 
1067  double stepX = grid.gridStep;
1068  double stepY = grid.gridStep;
1069  if (originCloud) {
1070  const CCVector3d& shift = originCloud->getGlobalShift();
1071  shiftX -= shift.u[X];
1072  shiftY -= shift.u[Y];
1073  shiftZ -= shift.u[Z];
1074 
1075  double scale = originCloud->getGlobalScale();
1076  assert(scale != 0);
1077  stepX /= scale;
1078  stepY /= scale;
1079  }
1080 
1081  int totalBands = 0;
1082  bool onlyRGBA = true;
1083 
1084  if (exportBands.height) {
1085  ++totalBands;
1086  onlyRGBA = false;
1087  }
1088 
1089  bool rgbaMode = false;
1090  if (exportBands.rgb) {
1091  totalBands += 3; // one per component
1092  if (fillEmptyCellsStrategy == ccRasterGrid::LEAVE_EMPTY &&
1093  grid.validCellCount < grid.height * grid.width) {
1094  rgbaMode = true;
1095  ++totalBands; // alpha
1096  }
1097  } else {
1098  onlyRGBA = false;
1099  }
1100 
1101  if (exportBands.density) {
1102  ++totalBands;
1103  onlyRGBA = false;
1104  }
1105 
1106  if (exportBands.allSFs) {
1107  for (size_t i = 0; i < grid.scalarFields.size(); ++i) {
1108  if (!grid.scalarFields[i].empty()) {
1109  ++totalBands;
1110  onlyRGBA = false;
1111  }
1112  }
1113  } else if (exportBands.visibleSF && visibleSfIndex >= 0) {
1114  ++totalBands;
1115  onlyRGBA = false;
1116  }
1117 
1118  if (totalBands == 0) {
1119  CVLog::Error(
1120  "Can't output a raster with no band! (check export "
1121  "parameters)");
1122  return false;
1123  }
1124 
1125  GDALAllRegister();
1126  CVLog::PrintDebug("(GDAL drivers: %i)",
1127  GetGDALDriverManager()->GetDriverCount());
1128 
1129  const char pszFormat[] = "GTiff";
1130  GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat);
1131  if (!poDriver) {
1132  CVLog::Error("[GDAL] Driver %s is not supported", pszFormat);
1133  return false;
1134  }
1135 
1136  char** papszMetadata = poDriver->GetMetadata();
1137  if (!CSLFetchBoolean(papszMetadata, GDAL_DCAP_CREATE, FALSE)) {
1138  CVLog::Error("[GDAL] Driver %s doesn't support Create() method",
1139  pszFormat);
1140  return false;
1141  }
1142 
1143  char** papszOptions = nullptr;
1144  GDALDataset* poDstDS = poDriver->Create(
1145  qPrintable(outputFilename), static_cast<int>(grid.width),
1146  static_cast<int>(grid.height), totalBands,
1147  onlyRGBA ? GDT_Byte : GDT_Float64, papszOptions);
1148 
1149  if (!poDstDS) {
1150  CVLog::Error(
1151  "[GDAL] Failed to create output raster (not enough memory?)");
1152  return false;
1153  }
1154 
1155  poDstDS->SetMetadataItem("AREA_OR_POINT", "AREA");
1156 
1157  double adfGeoTransform[6] = {
1158  shiftX, // top left x
1159  stepX, // w-e pixel resolution (can be negative)
1160  0, // 0
1161  shiftY, // top left y
1162  0, // 0
1163  -stepY // n-s pixel resolution (can be negative)
1164  };
1165 
1166  poDstDS->SetGeoTransform(adfGeoTransform);
1167 
1168  // OGRSpatialReference oSRS;
1169  // oSRS.SetUTM( 11, TRUE );
1170  // oSRS.SetWellKnownGeogCS( "NAD27" );
1171  // char *pszSRS_WKT = nullptr;
1172  // oSRS.exportToWkt( &pszSRS_WKT );
1173  // poDstDS->SetProjection( pszSRS_WKT );
1174  // CPLFree( pszSRS_WKT );
1175 
1176  int currentBand = 0;
1177 
1178  // exort RGB band?
1179  if (exportBands.rgb) {
1180  GDALRasterBand* rgbBands[3] = {poDstDS->GetRasterBand(++currentBand),
1181  poDstDS->GetRasterBand(++currentBand),
1182  poDstDS->GetRasterBand(++currentBand)};
1183  rgbBands[0]->SetColorInterpretation(GCI_RedBand);
1184  rgbBands[1]->SetColorInterpretation(GCI_GreenBand);
1185  rgbBands[2]->SetColorInterpretation(GCI_BlueBand);
1186 
1187  unsigned char* cLine =
1188  (unsigned char*)CPLMalloc(sizeof(unsigned char) * grid.width);
1189  if (!cLine) {
1190  CVLog::Error("[GDAL] Not enough memory");
1191  GDALClose(poDstDS);
1192  return false;
1193  }
1194 
1195  bool error = false;
1196 
1197  // export the R, G and B components
1198  for (unsigned k = 0; k < 3; ++k) {
1199  rgbBands[k]->SetStatistics(
1200  0, 255, 128,
1201  0); // warning: arbitrary average and std. dev. values
1202 
1203  for (unsigned j = 0; j < grid.height; ++j) {
1204  const ccRasterGrid::Row& row =
1205  grid.rows[grid.height - 1 -
1206  j]; // the first row is the northest one
1207  // (i.e. Ymax)
1208  for (unsigned i = 0; i < grid.width; ++i) {
1209  cLine[i] = (std::isfinite(row[i].h)
1210  ? static_cast<unsigned char>(std::max(
1211  0.0,
1212  std::min(255.0,
1213  row[i].color.u[k])))
1214  : 0);
1215  }
1216 
1217  if (rgbBands[k]->RasterIO(GF_Write, 0, static_cast<int>(j),
1218  static_cast<int>(grid.width), 1,
1219  cLine, static_cast<int>(grid.width),
1220  1, GDT_Byte, 0, 0) != CE_None) {
1221  error = true;
1222  k = 3; // early stop
1223  break;
1224  }
1225  }
1226  }
1227 
1228  // export the alpha band (if necessary)
1229  if (!error && rgbaMode) {
1230  GDALRasterBand* aBand = poDstDS->GetRasterBand(++currentBand);
1231  aBand->SetColorInterpretation(GCI_AlphaBand);
1232  aBand->SetStatistics(
1233  0, 255, 255,
1234  0); // warning: arbitrary average and std. dev. values
1235 
1236  for (unsigned j = 0; j < grid.height; ++j) {
1237  const ccRasterGrid::Row& row = grid.rows[grid.height - 1 - j];
1238  for (unsigned i = 0; i < grid.width; ++i) {
1239  cLine[i] = (std::isfinite(row[i].h) ? 255 : 0);
1240  }
1241 
1242  if (aBand->RasterIO(GF_Write, 0, static_cast<int>(j),
1243  static_cast<int>(grid.width), 1, cLine,
1244  static_cast<int>(grid.width), 1, GDT_Byte,
1245  0, 0) != CE_None) {
1246  error = true;
1247  break;
1248  }
1249  }
1250  }
1251 
1252  CPLFree(cLine);
1253 
1254  if (error) {
1255  CVLog::Error(
1256  "[GDAL] An error occurred while writing the color bands!");
1257  GDALClose(poDstDS);
1258  return false;
1259  }
1260  }
1261 
1262  double* scanline = (double*)CPLMalloc(sizeof(double) * grid.width);
1263  if (!scanline) {
1264  CVLog::Error("[GDAL] Not enough memory");
1265  GDALClose(poDstDS);
1266  return false;
1267  }
1268 
1269  // exort height band?
1270  if (exportBands.height) {
1271  GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand);
1272  assert(poBand);
1273  poBand->SetColorInterpretation(GCI_Undefined);
1274 
1275  double emptyCellHeight = 0;
1276  switch (fillEmptyCellsStrategy) {
1278  emptyCellHeight = grid.minHeight - 1.0;
1279  poBand->SetNoDataValue(
1280  emptyCellHeight); // should be transparent!
1281  break;
1283  emptyCellHeight = grid.minHeight;
1284  break;
1286  emptyCellHeight = grid.maxHeight;
1287  break;
1290  emptyCellHeight = customHeightForEmptyCells;
1291  break;
1293  emptyCellHeight = grid.meanHeight;
1294  break;
1295  default:
1296  assert(false);
1297  }
1298 
1299  emptyCellHeight += shiftZ;
1300 
1301  for (unsigned j = 0; j < grid.height; ++j) {
1302  const ccRasterGrid::Row& row = grid.rows[grid.height - 1 - j];
1303  for (unsigned i = 0; i < grid.width; ++i) {
1304  scanline[i] = std::isfinite(row[i].h) ? row[i].h + shiftZ
1305  : emptyCellHeight;
1306  }
1307 
1308  if (poBand->RasterIO(GF_Write, 0, static_cast<int>(j),
1309  static_cast<int>(grid.width), 1, scanline,
1310  static_cast<int>(grid.width), 1, GDT_Float64,
1311  0, 0) != CE_None) {
1312  CVLog::Error(
1313  "[GDAL] An error occurred while writing the height "
1314  "band!");
1315  if (scanline) CPLFree(scanline);
1316  GDALClose(poDstDS);
1317  return false;
1318  }
1319  }
1320  }
1321 
1322  // export density band
1323  if (exportBands.density) {
1324  GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand);
1325  assert(poBand);
1326  poBand->SetColorInterpretation(GCI_Undefined);
1327  for (unsigned j = 0; j < grid.height; ++j) {
1328  const ccRasterGrid::Row& row = grid.rows[grid.height - 1 - j];
1329  for (unsigned i = 0; i < grid.width; ++i) {
1330  scanline[i] = row[i].nbPoints;
1331  }
1332 
1333  if (poBand->RasterIO(GF_Write, 0, static_cast<int>(j),
1334  static_cast<int>(grid.width), 1, scanline,
1335  static_cast<int>(grid.width), 1, GDT_Float64,
1336  0, 0) != CE_None) {
1337  CVLog::Error(
1338  "[GDAL] An error occurred while writing the height "
1339  "band!");
1340  if (scanline) CPLFree(scanline);
1341  GDALClose(poDstDS);
1342  return false;
1343  }
1344  }
1345  }
1346 
1347  // export SF bands
1348  if (exportBands.allSFs || (exportBands.visibleSF && visibleSfIndex >= 0)) {
1349  for (size_t k = 0; k < grid.scalarFields.size(); ++k) {
1350  assert(!grid.scalarFields[k].empty());
1351  if (exportBands.allSFs || (exportBands.visibleSF &&
1352  visibleSfIndex == static_cast<int>(k))) {
1353  const double* sfGrid = grid.scalarFields[k].data();
1354  GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand);
1355 
1356  double sfNanValue = std::numeric_limits<
1357  ccRasterGrid::SF::value_type>::quiet_NaN();
1358  poBand->SetNoDataValue(sfNanValue); // should be transparent!
1359  assert(poBand);
1360  poBand->SetColorInterpretation(GCI_Undefined);
1361 
1362  for (unsigned j = 0; j < grid.height; ++j) {
1363  const ccRasterGrid::Row& row =
1364  grid.rows[grid.height - 1 - j];
1365  const double* sfRow =
1366  sfGrid + (grid.height - 1 - j) * grid.width;
1367  for (unsigned i = 0; i < grid.width; ++i) {
1368  scanline[i] = row[i].nbPoints ? sfRow[i] : sfNanValue;
1369  }
1370 
1371  if (poBand->RasterIO(GF_Write, 0, static_cast<int>(j),
1372  static_cast<int>(grid.width), 1,
1373  scanline, static_cast<int>(grid.width),
1374  1, GDT_Float64, 0, 0) != CE_None) {
1375  // the corresponding SF should exist on the input cloud
1376  CVLog::Error(
1377  QString("[GDAL] An error occurred while "
1378  "writing a scalar field band!"));
1379  k = grid.scalarFields.size(); // quick stop
1380  break;
1381  }
1382  }
1383  }
1384  }
1385  }
1386 
1387  if (scanline) CPLFree(scanline);
1388  scanline = 0;
1389 
1390  /* Once we're done, close properly the dataset */
1391  GDALClose(poDstDS);
1392 
1393  CVLog::Print(QString("[Rasterize] Raster '%1' successfully saved")
1394  .arg(outputFilename));
1395  return true;
1396 
1397 #else
1398  assert(false);
1399  CVLog::Error(
1400  "[Rasterize] GDAL not supported by this version! Can't generate a "
1401  "raster...");
1402  return false;
1403 #endif
1404 }
1405 
1406 // See
1407 // http://edndoc.esri.com/arcobjects/9.2/net/shared/geoprocessing/spatial_analyst_tools/how_hillshade_works.htm
1409  if (!m_grid.isValid() || !m_rasterCloud) {
1410  CVLog::Error("Need a valid raster/cloud to compute contours!");
1411  return;
1412  }
1413  if (m_grid.height < 3 || m_grid.width < 3) {
1414  CVLog::Error("Grid is too small");
1415  return;
1416  }
1417 
1418  // get/create layer
1419  ccScalarField* hillshadeLayer = 0;
1421  if (sfIdx >= 0) {
1422  hillshadeLayer = static_cast<ccScalarField*>(
1423  m_rasterCloud->getScalarField(sfIdx));
1424  } else {
1425  hillshadeLayer = new ccScalarField(HILLSHADE_FIELD_NAME);
1426  if (!hillshadeLayer->reserveSafe(m_rasterCloud->size())) {
1427  CVLog::Error("Not enough memory!");
1428  hillshadeLayer->release();
1429  hillshadeLayer = nullptr;
1430  return;
1431  }
1432 
1433  sfIdx = m_rasterCloud->addScalarField(hillshadeLayer);
1434  activeLayerComboBox->addItem(HILLSHADE_FIELD_NAME, QVariant(LAYER_SF));
1435  activeLayerComboBox->setEnabled(true);
1436  }
1437  assert(hillshadeLayer &&
1438  hillshadeLayer->currentSize() == m_rasterCloud->size());
1439  hillshadeLayer->fill(NAN_VALUE);
1440 
1441  bool sparseSF =
1442  (hillshadeLayer->currentSize() != m_grid.height * m_grid.width);
1443 
1444  // now we can compute the hillshade
1445  int zenith_deg = sunZenithSpinBox->value();
1446  double zenith_rad = cloudViewer::DegreesToRadians(zenith_deg);
1447 
1448  double cos_zenith_rad = cos(zenith_rad);
1449  double sin_zenith_rad = sin(zenith_rad);
1450 
1451  int azimuth_deg = sunAzimuthSpinBox->value();
1452  int azimuth_math = 360 - azimuth_deg + 90;
1453  double azimuth_rad = cloudViewer::DegreesToRadians(azimuth_math);
1454 
1455  // for all cells
1456  unsigned validCellIndex = 0;
1457  for (unsigned j = (sparseSF ? 0 : 1); j < m_grid.height - 1; ++j) {
1458  const ccRasterGrid::Row& row = m_grid.rows[j];
1459 
1460  for (unsigned i = sparseSF ? 0 : 1; i < m_grid.width; ++i) {
1461  // valid height value
1462  if (std::isfinite(row[i].h)) {
1463  if (i != 0 && i + 1 != m_grid.width && j != 0) {
1464  double dz_dx = 0.0;
1465  int dz_dx_count = 0;
1466  double dz_dy = 0.0;
1467  int dz_dy_count = 0;
1468 
1469  for (int di = -1; di <= 1; ++di) {
1470  for (int dj = -1; dj <= 1; ++dj) {
1471  const ccRasterCell& n =
1472  m_grid.rows[j - dj]
1473  [i +
1474  di]; //-dj (instead of +
1475  // dj) because we scan
1476  // the grid in the
1477  // reverse orientation!
1478  //(from bottom to top)
1479  if (n.h == n.h) {
1480  if (di != 0) {
1481  int dx_weight = (dj == 0 ? 2 : 1);
1482  dz_dx += (di < 0 ? -1.0 : 1.0) * dx_weight *
1483  n.h;
1484  dz_dx_count += dx_weight;
1485  }
1486 
1487  if (dj != 0) {
1488  int dy_weight = (di == 0 ? 2 : 1);
1489  dz_dy += (dj < 0 ? -1.0 : 1.0) * dy_weight *
1490  n.h;
1491  dz_dy_count += dy_weight;
1492  }
1493  }
1494  }
1495  }
1496 
1497  // for now we only handle the cell that have 8 valid
1498  // neighbors!
1499  if (dz_dx_count == 8 && dz_dy_count == 8) {
1500  dz_dx /= (8.0 * m_grid.gridStep);
1501  dz_dy /= (8.0 * m_grid.gridStep);
1502 
1503  double slope_rad = atan(/*z_factor **/ sqrt(
1504  dz_dx * dz_dx + dz_dy * dz_dy));
1505 
1506  double aspect_rad = 0;
1507  static const double s_zero = 1.0e-8;
1508  if (fabs(dz_dx) > s_zero) {
1509  aspect_rad = atan2(dz_dy, -dz_dx);
1510  if (aspect_rad < 0) {
1511  aspect_rad += 2.0 * M_PI;
1512  }
1513  } else // dz_dx == 0
1514  {
1515  if (dz_dy > s_zero) {
1516  aspect_rad = 0.5 * M_PI;
1517  } else if (dz_dy < s_zero) {
1518  aspect_rad = 1.5 * M_PI;
1519  }
1520  }
1521 
1522  ScalarType hillshade = static_cast<ScalarType>(std::max(
1523  0.0,
1524  cos_zenith_rad * cos(slope_rad) +
1525  sin_zenith_rad * sin(slope_rad) *
1526  cos(azimuth_rad - aspect_rad)));
1527  hillshadeLayer->setValue(sparseSF
1528  ? validCellIndex
1529  : i + j * m_grid.width,
1530  hillshade);
1531  }
1532  }
1533  ++validCellIndex;
1534  }
1535  }
1536  }
1537 
1538  hillshadeLayer->computeMinAndMax();
1539  hillshadeLayer->setColorScale(
1542  m_rasterCloud->showSF(true);
1543  activeLayerComboBox->setCurrentIndex(
1544  activeLayerComboBox->findText(HILLSHADE_FIELD_NAME));
1545 
1548  }
1549 }
1550 
1551 #ifdef CV_GDAL_SUPPORT
1552 
1553 struct ContourGenerationParameters {
1554  std::vector<ccPolyline*> contourLines;
1555  const ccRasterGrid* grid = 0;
1556  bool projectContourOnAltitudes = false;
1557 };
1558 
1559 static CPLErr ContourWriter(double dfLevel,
1560  int nPoints,
1561  double* padfX,
1562  double* padfY,
1563  void* userData) {
1564  if (nPoints < 2) {
1565  // nothing to do
1566  assert(false);
1567  return CE_None;
1568  }
1569 
1570  ContourGenerationParameters* params =
1571  (ContourGenerationParameters*)userData;
1572  if (!params || !params->grid) {
1573  assert(false);
1574  return CE_Failure;
1575  }
1576 
1577  ccPointCloud* vertices = nullptr;
1578  ccPolyline* poly = nullptr;
1579 
1580  unsigned subIndex = 0;
1581  for (int i = 0; i < nPoints; ++i) {
1582  CCVector3 P(padfX[i], padfY[i], dfLevel);
1583 
1584  if (params->projectContourOnAltitudes) {
1585  int xi = std::min(std::max(static_cast<int>(padfX[i]), 0),
1586  static_cast<int>(params->grid->width) - 1);
1587  int yi = std::min(std::max(static_cast<int>(padfY[i]), 0),
1588  static_cast<int>(params->grid->height) - 1);
1589  double h = params->grid->rows[yi][xi].h;
1590  if (std::isfinite(h)) {
1591  P.z = static_cast<PointCoordinateType>(h);
1592  } else {
1593  // DGM: we stop the current polyline
1594  if (poly) {
1595  if (poly->size() < 2) {
1596  delete poly;
1597  params->contourLines.pop_back();
1598  }
1599  poly = nullptr;
1600  vertices = nullptr;
1601  }
1602  continue;
1603  }
1604  }
1605 
1606  if (!poly) {
1607  // we need to instantiate a new polyline
1608  vertices = new ccPointCloud("vertices");
1609  vertices->setEnabled(false);
1610  poly = new ccPolyline(vertices);
1611  poly->addChild(vertices);
1612  poly->setMetaData("SubIndex", ++subIndex);
1613  poly->setClosed(false);
1614 
1615  // add the 'const altitude' meta-data as well
1617  QVariant(dfLevel));
1618 
1619  if (!vertices->reserve(nPoints - i) ||
1620  !poly->reserve(nPoints - i)) {
1621  // not enough memory
1622  delete poly;
1623  poly = 0;
1624  return CE_Failure;
1625  }
1626 
1627  try {
1628  params->contourLines.push_back(poly);
1629  } catch (const std::bad_alloc&) {
1630  return CE_Failure;
1631  }
1632  }
1633 
1634  assert(vertices);
1635  poly->addPointIndex(vertices->size());
1636  vertices->addPoint(P);
1637  }
1638 
1639  return CE_None;
1640 }
1641 
1642 #endif // CV_GDAL_SUPPORT
1643 
1645  double height,
1646  unsigned subIndex) {
1647  assert(poly);
1648  if (poly->size() > 1) {
1649  poly->setName(QString("Contour line value = %1 (#%2)")
1650  .arg(height)
1651  .arg(subIndex));
1654  poly->setWidth(contourWidthSpinBox->value() < 2
1655  ? 0
1656  : contourWidthSpinBox
1657  ->value()); // size 1 is equivalent to
1658  // the default size
1660  // poly->setClosed(isClosed);
1661  if (colorizeContoursCheckBox->isChecked()) {
1662  ccScalarField* activeLayer =
1664  if (activeLayer) {
1665  const ecvColor::Rgb* col = activeLayer->getColor(height);
1666  if (col) {
1667  poly->setColor(*col);
1668  }
1669  }
1670  }
1671  poly->showColors(true);
1672  // vertices->setEnabled(false);
1673 
1675 
1676  m_contourLines.push_back(poly);
1677  }
1678 }
1679 
1681  if (!m_grid.isValid() || !m_rasterCloud) {
1682  CVLog::Error("Need a valid raster/cloud to compute contours!");
1683  return;
1684  }
1685 
1686  // read options
1687  bool projectContourOnAltitudes = false;
1688  {
1689  switch (activeLayerComboBox->currentData().toInt()) {
1690  case LAYER_HEIGHT:
1691  // nothing to do
1692  break;
1693  case LAYER_RGB:
1694  CVLog::Error("Can't generate contours from RGB colors");
1695  return;
1696  default:
1697  projectContourOnAltitudes =
1698  projectContoursOnAltCheckBox->isChecked();
1699  break;
1700  }
1701  }
1702 
1703  // current layer
1704  ccScalarField* activeLayer =
1706  if (!activeLayer) {
1707  CVLog::Error("No valid/active layer!");
1708  return;
1709  }
1710  const double emptyCellsValue = activeLayer->getMin() - 1.0;
1711 
1712  // first contour level
1713  double startValue = contourStartDoubleSpinBox->value();
1714  if (startValue > activeLayer->getMax()) {
1715  CVLog::Error("Start value is above the layer maximum value!");
1716  return;
1717  }
1718 
1719  // gap between levels
1720  double step = contourStepDoubleSpinBox->value();
1721  assert(step > 0);
1722  unsigned levelCount =
1723  1 + static_cast<unsigned>(
1724  floor((activeLayer->getMax() - startValue) / step));
1725 
1726  // minimum number of vertices per contour line
1727  int minVertexCount = minVertexCountSpinBox->value();
1728  assert(minVertexCount >= 3);
1729 
1731 
1732  bool memoryError = false;
1733 
1734 #ifdef CV_GDAL_SUPPORT // use GDAL (more robust) - otherwise we will use an old
1735  // code found on the Internet (with a strange behavior)
1736 
1737  // invoke the GDAL 'Contour Generator'
1738  ContourGenerationParameters params;
1739  params.grid = &m_grid;
1740  params.projectContourOnAltitudes = projectContourOnAltitudes;
1741  GDALContourGeneratorH hCG =
1742  GDAL_CG_Create(m_grid.width, m_grid.height, 1, emptyCellsValue,
1743  step, startValue, ContourWriter, &params);
1744  if (!hCG) {
1745  CVLog::Error("[GDAL] Failed to create contour generator");
1746  return;
1747  }
1748 
1749  // feed the scan lines
1750  {
1751  double* scanline = (double*)CPLMalloc(sizeof(double) * m_grid.width);
1752  if (!scanline) {
1753  CVLog::Error("[GDAL] Not enough memory");
1754  return;
1755  }
1756 
1757  bool sparseLayer =
1758  (activeLayer->currentSize() != m_grid.height * m_grid.width);
1759  unsigned layerIndex = 0;
1760 
1761  for (unsigned j = 0; j < m_grid.height; ++j) {
1762  const ccRasterGrid::Row& cellRow = m_grid.rows[j];
1763  for (unsigned i = 0; i < m_grid.width; ++i) {
1764  if (cellRow[i].nbPoints || !sparseLayer) {
1765  ScalarType value = activeLayer->getValue(layerIndex++);
1766  scanline[i] = ccScalarField::ValidValue(value)
1767  ? value
1768  : emptyCellsValue;
1769 
1770  } else {
1771  scanline[i] = emptyCellsValue;
1772  }
1773  }
1774 
1775  CPLErr error = GDAL_CG_FeedLine(hCG, scanline);
1776  if (error != CE_None) {
1777  CVLog::Error(
1778  "[GDAL] An error occurred during countour lines "
1779  "generation");
1780  break;
1781  }
1782  }
1783 
1784  if (scanline) {
1785  CPLFree(scanline);
1786  }
1787  scanline = nullptr;
1788 
1789  // have we generated any contour line?
1790  if (!params.contourLines.empty()) {
1791  // vertical dimension
1792  const unsigned char Z = getProjectionDimension();
1793  assert(Z <= 2);
1794  const unsigned char X = Z == 2 ? 0 : Z + 1;
1795  const unsigned char Y = X == 2 ? 0 : X + 1;
1796 
1797  ccBBox gridBBox = getCustomBBox();
1798  assert(gridBBox.isValid());
1799 
1800  // reproject contour lines from raster C.S. to the cloud C.S.
1801  for (ccPolyline*& poly : params.contourLines) {
1802  if (static_cast<int>(poly->size()) < minVertexCount) {
1803  delete poly;
1804  poly = nullptr;
1805  continue;
1806  }
1807 
1808  double height = std::numeric_limits<double>::quiet_NaN();
1809  for (unsigned i = 0; i < poly->size(); ++i) {
1810  CCVector3* P2D = const_cast<CCVector3*>(
1811  poly->getAssociatedCloud()->getPoint(i));
1812  if (i == 0) {
1813  height = P2D->z;
1814  }
1815 
1816  CCVector3 P;
1817  // DGM: we will only do the dimension mapping at export time
1818  //(otherwise the contour lines appear in the wrong
1819  // orientation compared to the grid/raster which
1820  // is in the XY plane by default!)
1821  /*P.u[X] = */ P.x = static_cast<PointCoordinateType>(
1822  (P2D->x - 0.5) * m_grid.gridStep +
1823  gridBBox.minCorner().u[X]);
1824  /*P.u[Y] = */ P.y = static_cast<PointCoordinateType>(
1825  (P2D->y - 0.5) * m_grid.gridStep +
1826  gridBBox.minCorner().u[Y]);
1827  /*P.u[Z] = */ P.z = P2D->z;
1828 
1829  *P2D = P;
1830  }
1831 
1832  addNewContour(poly, height,
1833  poly->getMetaData("SubIndex").toUInt());
1834  }
1835 
1836  params.contourLines.resize(0); // just in case
1837  }
1838  }
1839 
1840 #else
1841 
1842  bool ignoreBorders = ignoreContourBordersCheckBox->isChecked();
1843  unsigned xDim = m_grid.width;
1844  unsigned yDim = m_grid.height;
1845 
1846  int margin = 0;
1847  if (!ignoreBorders) {
1848  margin = 1;
1849  xDim += 2;
1850  yDim += 2;
1851  }
1852  std::vector<double> grid;
1853  try {
1854  grid.resize(xDim * yDim, 0);
1855  } catch (const std::bad_alloc&) {
1856  CVLog::Error("Not enough memory!");
1858  return;
1859  }
1860 
1861  // fill grid
1862  {
1863  bool sparseLayer =
1864  (activeLayer->currentSize() != m_grid.height * m_grid.width);
1865 
1866  unsigned layerIndex = 0;
1867  for (unsigned j = 0; j < m_grid.height; ++j) {
1868  const ccRasterGrid::Row& cellRow = m_grid.rows[j];
1869  double* row = &(grid[(j + margin) * xDim + margin]);
1870  for (unsigned i = 0; i < m_grid.width; ++i) {
1871  if (cellRow[i].nbPoints || !sparseLayer) {
1872  ScalarType value = activeLayer->getValue(layerIndex++);
1873  row[i] = ccScalarField::ValidValue(value) ? value
1874  : emptyCellsValue;
1875  } else {
1876  row[i] = emptyCellsValue;
1877  }
1878  }
1879  }
1880  }
1881 
1882  try {
1883  Isolines<double> iso(static_cast<int>(xDim), static_cast<int>(yDim));
1884  if (!ignoreBorders) {
1885  iso.createOnePixelBorder(grid.data(), activeLayer->getMin() - 1.0);
1886  }
1887  // bounding box
1888  ccBBox box = getCustomBBox();
1889  assert(box.isValid());
1890 
1891  // vertical dimension
1892  const unsigned char Z = getProjectionDimension();
1893  assert(Z <= 2);
1894  const unsigned char X = (Z == 2 ? 0 : Z + 1);
1895  const unsigned char Y = (X == 2 ? 0 : X + 1);
1896 
1897  ecvProgressDialog pDlg(true, this);
1898  pDlg.setMethodTitle(tr("Contour plot"));
1899  pDlg.setInfo(tr("Levels: %1\nCells: %2 x %3")
1900  .arg(levelCount)
1901  .arg(m_grid.width)
1902  .arg(m_grid.height));
1903  pDlg.start();
1904  pDlg.show();
1905  QApplication::processEvents();
1906  cloudViewer::NormalizedProgress nProgress(&pDlg, levelCount);
1907 
1908  int lineWidth = contourWidthSpinBox->value();
1909  bool colorize = colorizeContoursCheckBox->isChecked();
1910 
1911  double v = startValue;
1912  while (v <= activeLayer->getMax() && !memoryError) {
1913  // extract contour lines for the current level
1914  iso.setThreshold(v);
1915  int lineCount = iso.find(grid.data());
1916 
1918  QString("[Rasterize][Isolines] value=%1 : %2 lines")
1919  .arg(v)
1920  .arg(lineCount));
1921 
1922  // convert them to poylines
1923  int realCount = 0;
1924  for (int i = 0; i < lineCount; ++i) {
1925  int vertCount = iso.getContourLength(i);
1926  if (vertCount >= minVertexCount) {
1927  int startVi = 0; // we may have to split the polyline in
1928  // multiple chunks
1929  while (startVi < vertCount) {
1930  ccPointCloud* vertices = new ccPointCloud("vertices");
1931  ccPolyline* poly = new ccPolyline(vertices);
1932  poly->addChild(vertices);
1933  bool isClosed =
1934  (startVi == 0 ? iso.isContourClosed(i) : false);
1935  if (poly->reserve(vertCount - startVi) &&
1936  vertices->reserve(vertCount - startVi)) {
1937  unsigned localIndex = 0;
1938  for (int vi = startVi; vi < vertCount; ++vi) {
1939  ++startVi;
1940 
1941  double x = iso.getContourX(i, vi) - margin;
1942  double y = iso.getContourY(i, vi) - margin;
1943 
1944  CCVector3 P;
1945  // DGM: we will only do the dimension mapping at
1946  // export time (otherwise the contour lines
1947  // appear in the wrong orientation compared to
1948  // the grid/raster which
1949  // is in the XY plane by default!)
1950  /*P.u[X] = */ P.x =
1951  static_cast<PointCoordinateType>(
1952  (x + 0.5) * m_grid.gridStep +
1953  box.minCorner().u[X]);
1954  /*P.u[Y] = */ P.y =
1955  static_cast<PointCoordinateType>(
1956  (y + 0.5) * m_grid.gridStep +
1957  box.minCorner().u[Y]);
1958  if (projectContourOnAltitudes) {
1959  int xi = std::min(
1960  std::max(static_cast<int>(x), 0),
1961  static_cast<int>(m_grid.width) - 1);
1962  int yi = std::min(
1963  std::max(static_cast<int>(y), 0),
1964  static_cast<int>(m_grid.height) -
1965  1);
1966  double h = m_grid.rows[yi][xi].h;
1967  if (std::isfinite(h)) {
1968  /*P.u[Z] = */ P.z = static_cast<
1969  PointCoordinateType>(h);
1970  } else {
1971  // DGM: we stop the current polyline
1972  isClosed = false;
1973  break;
1974  }
1975  } else {
1976  /*P.u[Z] = */ P.z =
1977  static_cast<PointCoordinateType>(v);
1978  }
1979 
1980  vertices->addPoint(P);
1981  assert(localIndex < vertices->size());
1982  poly->addPointIndex(localIndex++);
1983  }
1984 
1985  assert(poly);
1986  if (poly->size() > 1) {
1987  poly->setClosed(
1988  isClosed); // if we have less vertices,
1989  // it means we have
1990  // 'chopped' the original
1991  // contour
1992  vertices->setEnabled(false);
1993 
1994  // add the 'const altitude' meta-data as well
1995  poly->setMetaData(
1997  QVariant(v));
1998 
1999  addNewContour(poly, v, ++realCount);
2000  } else {
2001  delete poly;
2002  poly = 0;
2003  }
2004  } else {
2005  delete poly;
2006  poly = 0;
2007  CVLog::Error("Not enough memory!");
2008  memoryError = true; // early stop
2009  break;
2010  }
2011  }
2012  }
2013  }
2014  v += step;
2015 
2016  if (!nProgress.oneStep()) {
2017  // process cancelled by user
2018  break;
2019  }
2020  }
2021  } catch (const std::bad_alloc&) {
2022  CVLog::Error("Not enough memory!");
2023  }
2024 #endif
2025 
2026  CVLog::Print(QString("[Rasterize] %1 iso-lines generated (%2 levels)")
2027  .arg(m_contourLines.size())
2028  .arg(levelCount));
2029 
2030  if (!m_contourLines.empty()) {
2031  if (memoryError) {
2033  } else {
2034  exportContoursPushButton->setEnabled(true);
2035  clearContoursPushButton->setEnabled(true);
2036  }
2037  }
2038 
2041  }
2042 }
2043 
2045  MainWindow* mainWindow = MainWindow::TheInstance();
2046  if (!mainWindow || !m_cloud || m_contourLines.empty()) {
2047  assert(false);
2048  return;
2049  }
2050 
2051  bool colorize = colorizeContoursCheckBox->isChecked();
2052 
2053  // vertical dimension
2054  const unsigned char Z = getProjectionDimension();
2055  assert(Z <= 2);
2056  const unsigned char X = (Z == 2 ? 0 : Z + 1);
2057  const unsigned char Y = (X == 2 ? 0 : X + 1);
2058 
2059  ccHObject* group =
2060  new ccHObject(QString("Contour plot(%1) [step=%2]")
2061  .arg(m_cloud->getName())
2062  .arg(contourStepDoubleSpinBox->value()));
2063  for (size_t i = 0; i < m_contourLines.size(); ++i) {
2064  ccPolyline* poly = m_contourLines[i];
2065 
2066  // now is the time to map the polyline coordinates to the right
2067  // dimensions!
2068  ccPointCloud* vertices =
2069  dynamic_cast<ccPointCloud*>(poly->getAssociatedCloud());
2070  assert(vertices);
2071  if (vertices && Z != 2) {
2072  for (unsigned j = 0; j < vertices->size(); ++j) {
2073  CCVector3* P = const_cast<CCVector3*>(vertices->getPoint(j));
2074  CCVector3 Q = *P;
2075  P->u[X] = Q.x;
2076  P->u[Y] = Q.y;
2077  P->u[Z] = Q.z;
2078  }
2079  vertices->invalidateBoundingBox();
2080  poly->invalidateBoundingBox();
2081  }
2082 
2083  if (!colorize) poly->showColors(false);
2084  group->addChild(poly);
2087  }
2088  m_contourLines.resize(0);
2089  exportContoursPushButton->setEnabled(false);
2090 
2091  // group->setDisplay_recursive(m_cloud->getDisplay());
2092  mainWindow->addToDB(group);
2093 
2094  CVLog::Print(QString("Contour lines have been successfully exported to DB "
2095  "(group name: %1)")
2096  .arg(group->getName()));
2097 }
2098 
2100  double& emptyCellsHeight, double& minHeight, double& maxHeight) const {
2101  ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy =
2102  getFillEmptyCellsStrategy(fillEmptyCellsComboBox);
2103 
2104  emptyCellsHeight = 0.0;
2105  minHeight = m_grid.minHeight;
2106  maxHeight = m_grid.maxHeight;
2107 
2108  switch (fillEmptyCellsStrategy) {
2110  // nothing to do
2111  break;
2113  emptyCellsHeight = m_grid.minHeight;
2114  break;
2116  emptyCellsHeight = m_grid.maxHeight;
2117  break;
2120  double customEmptyCellsHeight = getCustomHeightForEmptyCells();
2121  // update min and max height by the way (only if there are invalid
2122  // cells ;)
2124  if (customEmptyCellsHeight <= m_grid.minHeight)
2125  minHeight = customEmptyCellsHeight;
2126  else if (customEmptyCellsHeight >= m_grid.maxHeight)
2127  maxHeight = customEmptyCellsHeight;
2128  emptyCellsHeight = customEmptyCellsHeight;
2129  }
2130  } break;
2132  //'average height' is a kind of 'custom height' so we can fall back
2133  // to this mode!
2134  fillEmptyCellsStrategy = ccRasterGrid::FILL_CUSTOM_HEIGHT;
2135  emptyCellsHeight = m_grid.meanHeight;
2136  break;
2137  default:
2138  assert(false);
2139  }
2140 
2141  return fillEmptyCellsStrategy;
2142 }
2143 
2145  if (!m_grid.isValid()) return;
2146 
2147  // default values
2148  double emptyCellsHeight = 0;
2149  double minHeight = m_grid.minHeight;
2150  double maxHeight = m_grid.maxHeight;
2151  // get real values
2152  ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy =
2153  getFillEmptyCellsStrategyExt(emptyCellsHeight, minHeight,
2154  maxHeight);
2155 
2156  QImage bitmap8(m_grid.width, m_grid.height, QImage::Format_Indexed8);
2157  if (!bitmap8.isNull()) {
2158  bool addTransparentColor =
2159  (fillEmptyCellsStrategy == ccRasterGrid::LEAVE_EMPTY);
2160 
2161  // build a custom palette
2162  QVector<QRgb> palette(256);
2165  const ccColorScale::Shared& colorScale =
2167  ->getColorScale();
2168  unsigned steps = (addTransparentColor ? 255 : 256);
2169  for (unsigned i = 0; i < steps; i++) {
2170  const ecvColor::Rgb* col = colorScale->getColorByRelativePos(
2171  i / static_cast<double>(steps - 1), steps,
2173  palette[i] = qRgba(col->r, col->g, col->b, 255);
2174  }
2175  } else {
2176  for (unsigned i = 0; i < 256; i++) {
2177  palette[i] = qRgba(i, i, i, 255);
2178  }
2179  }
2180 
2181  double maxColorComp =
2182  255.99; //.99 --> to avoid round-off issues later!
2183  if (addTransparentColor) {
2184  palette[255] = qRgba(255, 0, 255,
2185  0); // magenta/transparent color for empty
2186  // cells (in place of pure white)
2187  maxColorComp = 254.99;
2188  }
2189 
2190  bitmap8.setColorTable(palette);
2191  // bitmap8.fill(255);
2192 
2193  unsigned emptyCellColorIndex = 0;
2194  switch (fillEmptyCellsStrategy) {
2196  emptyCellColorIndex = 255; // should be transparent!
2197  break;
2199  emptyCellColorIndex = 0;
2200  break;
2202  emptyCellColorIndex = 255;
2203  break;
2205  double normalizedHeight = (emptyCellsHeight - minHeight) /
2206  (maxHeight - minHeight);
2207  // min and max should have already been updated with custom
2208  // empty cell height!
2209  assert(normalizedHeight >= 0.0 && normalizedHeight <= 1.0);
2210  emptyCellColorIndex = static_cast<unsigned>(
2211  floor(normalizedHeight * maxColorComp));
2212  } break;
2214  default:
2215  assert(false);
2216  }
2217 
2218  double range = maxHeight - minHeight;
2219  if (cloudViewer::LessThanEpsilon(range)) {
2220  range = 1.0;
2221  }
2222 
2223  // Filling the image with grid values
2224  for (unsigned j = 0; j < m_grid.height; ++j) {
2225  const ccRasterGrid::Row& row = m_grid.rows[j];
2226  for (unsigned i = 0; i < m_grid.width; ++i) {
2227  if (std::isfinite(row[i].h)) {
2228  double normalizedHeight = (row[i].h - minHeight) / range;
2229  assert(normalizedHeight >= 0.0 && normalizedHeight <= 1.0);
2230  unsigned char val = static_cast<unsigned char>(
2231  floor(normalizedHeight * maxColorComp));
2232  bitmap8.setPixel(i, m_grid.height - 1 - j, val);
2233  } else // NaN
2234  {
2235  bitmap8.setPixel(i, m_grid.height - 1 - j,
2236  emptyCellColorIndex);
2237  }
2238  }
2239  }
2240 
2241 #if 0
2242  //open file saving dialog
2243  {
2244  QSettings settings;
2245  settings.beginGroup(ecvPS::HeightGridGeneration());
2246  QString imageSavePath = settings.value("savePathImage", ecvFileUtils::defaultDocPath()).toString();
2247 
2248  QString outputFilename = ImageFileFilter::GetSaveFilename( "Save raster image",
2249  "raster_image",
2250  imageSavePath,
2251  const_cast<ccRasterizeTool*>(this));
2252 
2253  if (!outputFilename.isNull())
2254  {
2255  //save current export path to persistent settings
2256  settings.setValue("savePathImage", QFileInfo(outputFilename).absolutePath());
2257  settings.endGroup();
2258 
2259  if (bitmap8.save(outputFilename))
2260  {
2261  CVLog::Print(QString("[Rasterize] Image '%1' successfully saved").arg(outputFilename));
2262  }
2263  else
2264  {
2265  CVLog::Error("Failed to save image file!");
2266  }
2267  }
2268  }
2269 #endif
2270  } else {
2271  CVLog::Error("Failed to create output image! (not enough memory?)");
2272  }
2273 }
2274 
2276  if (!m_grid.isValid()) return;
2277 
2278  QSettings settings;
2279  settings.beginGroup(ecvPS::HeightGridGeneration());
2280  QString asciiGridSavePath =
2281  settings.value("savePathASCIIGrid", ecvFileUtils::defaultDocPath())
2282  .toString();
2283 
2284  // open file saving dialog
2285  QString filter("ASCII file (*.txt)");
2286  QString outputFilename = QFileDialog::getSaveFileName(
2287  0, "Save grid as ASCII file",
2288  asciiGridSavePath + QString("/raster_matrix.txt"), filter);
2289  if (outputFilename.isNull()) return;
2290 
2291  FILE* pFile = fopen(qPrintable(outputFilename), "wt");
2292  if (!pFile) {
2294  QString("[ccHeightGridGeneration] Failed to write '%1' file!")
2295  .arg(outputFilename));
2296  }
2297 
2298  // default values
2299  double emptyCellsHeight = 0;
2300  double minHeight = m_grid.minHeight;
2301  double maxHeight = m_grid.maxHeight;
2302  // get real values
2303  getFillEmptyCellsStrategyExt(emptyCellsHeight, minHeight, maxHeight);
2304  for (unsigned j = 0; j < m_grid.height; ++j) {
2305  const ccRasterGrid::Row& row = m_grid.rows[m_grid.height - 1 - j];
2306  for (unsigned i = 0; i < m_grid.width; ++i) {
2307  fprintf(pFile, "%.8f ",
2308  std::isfinite(row[i].h) ? row[i].h : emptyCellsHeight);
2309  }
2310 
2311  fprintf(pFile, "\n");
2312  }
2313 
2314  fclose(pFile);
2315  pFile = 0;
2316 
2317  // save current export path to persistent settings
2318  settings.setValue("savePathASCIIGrid",
2319  QFileInfo(outputFilename).absolutePath());
2320 
2321  CVLog::Print(QString("[Rasterize] Raster matrix '%1' successfully saved")
2322  .arg(outputFilename));
2323 }
constexpr double M_PI
Pi.
Definition: CVConst.h:19
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int size
int height
math::float4 color
void * X
Definition: SmallVector.cpp:45
cmdLineReadable * params[]
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
static bool PrintDebug(const char *format,...)
Same as Print, but works only in Debug mode.
Definition: CVLog.cpp:153
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
static QString GetSaveFilename(const QString &dialogTitle, const QString &baseName, const QString &imageSavePath, QWidget *parentWidget=nullptr)
Helper: select an output image filename.
int getContourLength(int contour) const
Returns the length of a given contour.
Definition: ecvIsolines.h:105
void setThreshold(T t)
Sets isoline value to trace.
Definition: ecvIsolines.h:91
double getContourY(int contour, int v) const
Definition: ecvIsolines.h:801
void createOnePixelBorder(T *in, T borderval) const
Creates a single pixel, 0-valued border around the grid.
Definition: ecvIsolines.h:135
int find(const T *in)
Find isolines.
Definition: ecvIsolines.h:94
bool isContourClosed(int contour) const
Returns whether a given contour is closed or not.
Definition: ecvIsolines.h:110
double getContourX(int contour, int v) const
Definition: ecvIsolines.h:796
static MainWindow * TheInstance()
Returns the unique instance of this object.
void addToDB(const QStringList &filenames, QString fileFilter=QString(), bool displayDialog=true)
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
2.5D data editor (generic interface)
virtual ccBBox getCustomBBox() const
Returns custom bbox.
ccRasterGrid::EmptyCellFillOption getFillEmptyCellsStrategy(QComboBox *comboBox) const
Returns the empty cell strategy (for a given combo-box)
void createBoundingBoxEditor(const ccBBox &gridBBox, QWidget *parent)
Creates the bounding-box editor.
ccPointCloud * convertGridToCloud(const std::vector< ccRasterGrid::ExportableFields > &exportedFields, bool interpolateSF, bool interpolateColors, bool resampleInputCloudXY, bool resampleInputCloudZ, ccGenericPointCloud *inputCloud, bool fillEmptyCells, double emptyCellsHeight, bool exportToOriginalCS) const
Shortcut to ccRasterGrid::convertToCloud.
ccRasterGrid m_grid
Raster grid.
virtual void update2DDisplayZoom(ccBBox &box)
Updates the 2D display zoom.
ccPointCloud * m_rasterCloud
'Raster' cloud
virtual bool showGridBoxEditor()
Show grid box editor and update.
virtual QString getGridSizeAsString() const
Returns the grid size as a string.
void create2DView(QFrame *parentFrame)
Creates the 2D view.
virtual bool getGridSize(unsigned &width, unsigned &height) const
Returns the grid size.
Bounding box structure.
Definition: ecvBBox.h:25
QSharedPointer< ccColorScale > Shared
Shared pointer type.
Definition: ecvColorScale.h:74
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
virtual bool colorsShown() const
Returns whether colors are shown or not.
virtual bool hasColors() const
Returns whether colors are enabled or not.
virtual void setVisible(bool state)
Sets entity visibility.
virtual bool sfShown() const
Returns whether active scalar field is visible.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual bool hasScalarFields() const
Returns whether one or more scalar fields are instantiated.
A 3D cloud interface with associated features (color, normals, octree, etc.)
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
virtual ccBBox getDisplayBB_recursive(bool relative)
Returns the bounding-box of this entity and it's children WHEN DISPLAYED.
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getParent() const
Returns parent object.
Definition: ecvHObject.h:245
Triangular mesh.
Definition: ecvMesh.h:35
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
void setMetaData(const QString &key, const QVariant &data)
Sets a meta-data element.
bool isA(CV_CLASS_ENUM type) const
Definition: ecvObject.h:131
QVariant getMetaData(const QString &key) const
Returns a given associated meta data.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
virtual bool isEnabled() const
Returns whether the object is enabled or not.
Definition: ecvObject.h:97
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
void invalidateBoundingBox() override
Invalidates bounding box.
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
bool hasColors() const override
Returns whether colors are enabled or not.
ccScalarField * getCurrentDisplayedScalarField() const
Returns the currently displayed scalar (or 0 if none)
Colored polyline.
Definition: ecvPolyline.h:24
virtual void setGlobalShift(const CCVector3d &shift) override
Sets shift applied to original coordinates (information storage only)
static QString MetaKeyConstAltitude()
Meta data key: contour plot constant altitude (for contour plots, etc.)
Definition: ecvPolyline.h:224
virtual void setGlobalScale(double scale) override
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
void setWidth(PointCoordinateType width)
Sets the width of the line.
Rasterize tool (dialog)
void addNewContour(ccPolyline *poly, double height, unsigned subIndex)
Adds a new contour line.
ccPointCloud * generateCloud(bool autoExport=true) const
Exports the grid as a cloud.
virtual ccRasterGrid::ProjectionType getTypeOfProjection() const override
Returns type of projection.
void generateMesh() const
Exports the grid as a mesh.
void updateGridAndDisplay()
Update the grid and the 2D display.
void projectionDirChanged(int)
Called when the projection direction changes.
virtual unsigned char getProjectionDimension() const override
Returns projection dimension.
virtual void generateASCIIMatrix() const
Exports the grid as an ASCII matrix.
virtual void generateImage() const
Exports the grid as an image.
void generateContours()
Generates a contour plot.
void resampleOptionToggled(bool)
Called when the 'resampled' option changes.
double getCustomHeightForEmptyCells() const
Returns user defined height for empty cells.
void updateGridInfo()
Updates the gid info.
bool exportAsSF(ccRasterGrid::ExportableFields field) const
ccRasterizeTool(ccGenericPointCloud *cloud, QWidget *parent=0)
Default constructor.
void projectionTypeChanged(int)
Called when the projection type changes.
bool resampleOriginalCloud() const
void loadSettings()
Load persistent settings.
static bool ExportGeoTiff(QString outputFilename, const ExportBands &exportBands, ccRasterGrid::EmptyCellFillOption fillEmptyCellsStrategy, const ccRasterGrid &grid, const ccBBox &gridBBox, unsigned char Z, double customHeightForEmptyCells=std::numeric_limits< double >::quiet_NaN(), ccGenericPointCloud *originCloud=0, int visibleSfIndex=-1)
Exports a raster grid as a geotiff file.
void fillEmptyCellStrategyChanged(int)
Called when the empty cell filling strategy changes.
std::vector< ccPolyline * > m_contourLines
Contour lines.
void saveSettings()
Save persistent settings and 'accept' dialog.
void testAndAccept()
Accepts the dialog (if some conditions are met) and save settings.
ccGenericPointCloud * m_cloud
Associated cloud.
bool updateGrid(bool interpolateSF=false)
Updates the grid.
void exportContourLines()
Exports the (already generated) contour lines.
ccRasterGrid::EmptyCellFillOption getFillEmptyCellsStrategyExt(double &emptyCellsHeight, double &minHeight, double &maxHeight) const
Returns strategy for empty cell filling (extended version)
void generateHillshade()
Generates hillshade.
~ccRasterizeTool()
Destructor.
virtual bool showGridBoxEditor() override
virtual void gridIsUpToDate(bool state) override
Declares whether the grid is up-to-date or not.
void generateRaster() const
Exports the grid as a raster.
void sfProjectionTypeChanged(int)
Called when the SF projection type changes.
void removeContourLines()
Removes all displayed contour lines.
bool canClose()
Tests if the dialog can be safely closed.
void gridOptionChanged()
Called when the an option of the grid generation has changed.
ccRasterGrid::ProjectionType getTypeOfSFInterpolation() const
Returns type of SF interpolation.
void testAndReject()
Rejects the dialog (if some conditions are met)
ccPointCloud * convertGridToCloud(const std::vector< ccRasterGrid::ExportableFields > &exportedFields, bool interpolateSF, bool interpolateColors, bool copyHillshadeSF, QString activeSFName, bool exportToOriginalCS) const
Converts the grid to a cloud with scalar field(s)
virtual double getGridStep() const override
Returns projection grid step.
void activeLayerChanged(int, bool autoRedraw=true)
Called when the active layer changes.
Scalar field range structure.
ScalarType min() const
ScalarType max() const
ScalarType range() const
A scalar field associated to display-related parameters.
const ccColorScale::Shared & getColorScale() const
Returns associated color scale.
void setColorScale(ccColorScale::Shared scale)
Sets associated color scale.
const Range & displayRange() const
Access to the range of displayed values.
const ecvColor::Rgb * getColor(ScalarType value) const
void computeMinAndMax() override
Determines the min and max values.
virtual void setGlobalScale(double scale)
virtual void setGlobalShift(double x, double y, double z)
Sets shift applied to original coordinates (information storage only)
virtual const CCVector3d & getGlobalShift() const
Returns the shift applied to original coordinates.
virtual double getGlobalScale() const
Returns the scale applied to original coordinates.
const Vector3Tpl< T > & maxCorner() const
Returns max corner (const)
Definition: BoundingBox.h:156
const Vector3Tpl< T > & minCorner() const
Returns min corner (const)
Definition: BoundingBox.h:154
bool isValid() const
Returns whether bounding box is valid or not.
Definition: BoundingBox.h:203
virtual unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
A generic mesh with index-based vertex access.
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned getNumberOfScalarFields() const
Returns the number of associated (and active) scalar fields.
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
static GenericIndexedMesh * computeTriangulation(GenericIndexedCloudPersist *cloud, TRIANGULATION_TYPES type, PointCoordinateType maxEdgeLength, unsigned char dim, std::string &outputErrorStr)
Applys a geometrical transformation to a single point.
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
virtual GenericIndexedCloudPersist * getAssociatedCloud()
Returns the associated (source) cloud.
unsigned size() const override
Returns the number of points.
void invalidateBoundingBox()
Invalidates the bounding-box.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
A simple scalar field (to be associated to a point cloud)
Definition: ScalarField.h:25
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
Definition: ScalarField.h:77
ScalarType getMin() const
Returns the minimum value.
Definition: ScalarField.h:72
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
bool reserveSafe(std::size_t count)
Reserves memory (no exception thrown)
Definition: ScalarField.cpp:71
static bool ValidValue(ScalarType value)
Returns whether a scalar value is valid or not.
Definition: ScalarField.h:61
unsigned currentSize() const
Definition: ScalarField.h:100
ScalarType getMax() const
Returns the maximum value.
Definition: ScalarField.h:74
RGB color structure.
Definition: ecvColorTypes.h:49
static void RemoveFromOwnDB(ccHObject *obj)
Removes an entity from window own DB.
static void AddToOwnDB(ccHObject *obj, bool noDependency=true)
Adds an entity to window own DB.
static QMainWindow * GetMainWindow()
static void RedrawDisplay(bool only2D=false, bool forceRedraw=true)
static const QString HeightGridGeneration()
Graphical progress indicator (thread-safe)
virtual void start() override
virtual void setInfo(const char *infoStr) override
Notifies some information about the ongoing process.
virtual void setMethodTitle(const char *methodTitle) override
Notifies the algorithm title.
__host__ __device__ float2 fabs(float2 v)
Definition: cutil_math.h:1254
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
static const char HILLSHADE_FIELD_NAME[]
static void error(char *msg)
Definition: lsd.c:159
#define FALSE
Definition: lsd.c:115
@ POINT_CLOUD
Definition: CVTypes.h:104
bool interpolateColors(const ccHObject::Container &selectedEntities, QWidget *parent)
Interpolate colors from on entity and transfer them to another one.
bool interpolateSFs(const ccHObject::Container &selectedEntities, ecvMainAppInterface *app)
Interpolate scalar fields from on entity and transfer them to another one.
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98
bool LessThanEpsilon(float x)
Test a floating point number against our epsilon (a very small number).
Definition: CVMath.h:23
std::string toString(T x)
Definition: Common.h:80
constexpr Rgb darkGrey(MAX/2, MAX/2, MAX/2)
constexpr Rgb lightGrey(static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8), static_cast< ColorCompType >(MAX *0.8))
QString defaultDocPath()
Shortcut for getting the documents location path.
Definition: ecvFileUtils.h:30
cloudViewer::NormalizedProgress * nProgress
Raster grid cell.
Definition: ecvRasterGrid.h:22
double h
Height value.
Definition: ecvRasterGrid.h:35
Raster grid type.
Definition: ecvRasterGrid.h:53
ExportableFields
Exportable fields.
Definition: ecvRasterGrid.h:79
@ PER_CELL_VALUE_STD_DEV
Definition: ecvRasterGrid.h:85
unsigned validCellCount
Number of VALID cells.
ProjectionType
Types of projection.
double gridStep
Grid step ('pixel' size)
std::vector< ccRasterCell > Row
Row.
unsigned height
Number of rows.
bool hasColors
Whether the (average) colors are available or not.
bool init(unsigned w, unsigned h, double gridStep, const CCVector3d &minCorner)
Initializes / resets the grid.
double meanHeight
Average height (computed on the NON-EMPTY or INTERPOLATED cells)
bool fillWith(ccGenericPointCloud *cloud, unsigned char projectionDimension, ProjectionType projectionType, bool interpolateEmptyCells, ProjectionType sfInterpolation=INVALID_PROJECTION_TYPE, ecvProgressDialog *progressDialog=nullptr)
Fills the grid with a point cloud.
std::vector< SF > scalarFields
Associated scalar fields.
static QString GetDefaultFieldName(ExportableFields field)
Returns the default name of a given field.
std::vector< Row > rows
All cells.
double maxHeight
Max height (computed on the NON-EMPTY or INTERPOLATED cells)
bool isValid() const
Returns whether the grid is 'valid' or not.
unsigned width
Number of columns.
EmptyCellFillOption
Option for handling empty cells.
double minHeight
Min height (computed on the NON-EMPTY or INTERPOLATED cells)
Bands to be exported.