ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
LASFilter.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 "LASFilter.h"
9 
10 // Local
11 #include "LASOpenDlg.h"
12 
13 // CV_CORE_LIB
14 #include <CVLog.h>
15 #include <CVMath.h>
16 
17 // qCC_db
18 #include <ecvHObjectCaster.h>
19 #include <ecvPointCloud.h>
20 #include <ecvProgressDialog.h>
21 #include <ecvScalarField.h>
22 
23 #include "ecvColorScalesManager.h"
24 
25 // cloudViewer
26 #include <CVPlatform.h>
27 
28 // Qt
29 #include <QFileInfo>
30 #include <QFuture>
31 #include <QInputDialog>
32 #include <QSharedPointer>
33 #include <QtConcurrent>
34 
35 // pdal
36 #include <memory>
37 #include <pdal/Dimension.hpp>
38 #include <pdal/Filter.hpp>
39 #include <pdal/Options.hpp>
40 #include <pdal/PointTable.hpp>
41 #include <pdal/PointView.hpp>
42 #include <pdal/filters/StreamCallbackFilter.hpp>
43 #include <pdal/io/BufferReader.hpp>
44 #include <pdal/io/LasHeader.hpp>
45 #include <pdal/io/LasReader.hpp>
46 #include <pdal/io/LasVLR.hpp>
47 #include <pdal/io/LasWriter.hpp>
48 
49 Q_DECLARE_METATYPE(pdal::SpatialReference)
50 
51 using namespace pdal::Dimension;
52 using namespace pdal;
53 
54 // Qt gui
55 #include <ui_saveLASFileDlg.h>
56 
57 // System
58 #include <string.h>
59 
60 #include <bitset>
61 
62 static const char s_LAS_SRS_Key[] =
63  "LAS.spatialReference.nosave"; // DGM: added the '.nosave' suffix
64  // because this custom type can't be
65  // streamed properly
66 
68 struct ExtraLasField : LasField {
71  Id id,
72  double defaultVal = 0.0,
73  double min = 0.0,
74  double max = -1.0)
75  : LasField(LAS_EXTRA, defaultVal, min, max),
76  fieldName(SanitizeString(name)),
77  pdalId(id),
78  scale(1.0),
79  offset(0.0) {
80  if (fieldName != name) {
81  CVLog::Warning(QString("Extra field '%1' renamed '%2' to comply to "
82  "LAS specifications")
83  .arg(name)
84  .arg(fieldName));
85  }
86  }
87 
88  typedef QSharedPointer<ExtraLasField> Shared;
89 
90  inline QString getName() const override { return fieldName; }
91 
92  QString fieldName;
93  Id pdalId;
94  double scale;
95  double offset;
96 };
97 
99 class LASSaveDlg : public QDialog, public Ui::SaveLASFileDialog {
100 public:
101  explicit LASSaveDlg(QWidget* parent = nullptr)
102  : QDialog(parent), Ui::SaveLASFileDialog() {
103  setupUi(this);
104  clearEVLRs();
105  }
106 
107  void clearEVLRs() {
108  evlrListWidget->clear();
109  extraFieldGroupBox->setEnabled(false);
110  extraFieldGroupBox->setChecked(false);
111  }
112 
113  void addEVLR(const QString& description) {
114  QListWidgetItem* item = new QListWidgetItem(description);
115  evlrListWidget->addItem(item);
116  // auto select the entry
117  item->setSelected(true);
118  // auto enable the extraFieldGroupBox
119  extraFieldGroupBox->setEnabled(true);
120  extraFieldGroupBox->setChecked(false);
121  }
122 
123  bool doSaveEVLR(size_t index) const {
124  if (!extraFieldGroupBox->isChecked()) return false;
125 
126  QListWidgetItem* item = evlrListWidget->item(static_cast<int>(index));
127  return item && item->isSelected();
128  }
129 };
130 
132  : FileIOFilter({"_PDAL LAS Filter",
133  DEFAULT_PRIORITY, // deprecated: lower priority
134  QStringList{"las", "laz"}, "las",
135  QStringList{"LAS cloud with PDAL (*.las *.laz)"},
136  QStringList{"LAS cloud with PDAL (*.las *.laz)"},
137  Import | Export}) {}
138 
140  bool& multiple,
141  bool& exclusive) const {
142  if (type == static_cast<CV_CLASS_ENUM>(CV_TYPES::POINT_CLOUD)) {
143  multiple = false;
144  exclusive = true;
145  return true;
146  }
147  return false;
148 }
149 
151 QSharedPointer<LASSaveDlg> s_saveDlg(nullptr);
152 pdal::Dimension::Id typeToId(LAS_FIELDS sfType, uint8_t pointFormat) {
153  switch (sfType) {
154  case LAS_FIELDS::LAS_X:
155  return pdal::Dimension::Id::X;
156  case LAS_FIELDS::LAS_Y:
157  return pdal::Dimension::Id::Y;
158  case LAS_FIELDS::LAS_Z:
159  return pdal::Dimension::Id::Z;
178  case LAS_FIELDS::LAS_RED:
179  return pdal::Dimension::Id::Red;
181  return pdal::Dimension::Id::Green;
183  return pdal::Dimension::Id::Blue;
187  return pdal::Dimension::Id::Unknown;
188  // Sub fields
194  return pointFormat < 6 ? pdal::Dimension::Id::Classification
195  : pdal::Dimension::Id::ClassFlags;
197  return pointFormat < 6
198  ? pdal::Dimension::Id::Unknown
199  : pdal::Dimension::Id::ClassFlags; // only for point
200  // formats >= 6
201  // Invalid flag
203  default:
204  return pdal::Dimension::Id::Unknown;
205  };
206 }
207 
209  const QString& filename,
210  const SaveParameters& parameters) {
211  if (!entity || filename.isEmpty()) return CC_FERR_BAD_ARGUMENT;
212 
213  ccGenericPointCloud* theCloud =
215  if (!theCloud) {
216  CVLog::Warning("[LAS] This filter can only save one cloud at a time");
218  }
219 
220  unsigned numberOfPoints = theCloud->size();
221  if (numberOfPoints == 0) {
222  CVLog::Warning("[LAS] Cloud is empty!");
223  // return CC_FERR_NO_SAVE;
224  }
225 
226  // colors
227  bool hasColors = theCloud->hasColors();
228 
229  // standard las fields (as scalar fields)
230  std::vector<LasField> fieldsToSave;
231  // extra las fields (as scalar fields)
232  std::vector<ExtraLasField::Shared> extraFields;
233 
234  uint8_t minPointFormat = 0;
235 
236  if (theCloud->isA(CV_TYPES::POINT_CLOUD)) {
237  ccPointCloud* pc = static_cast<ccPointCloud*>(theCloud);
238 
239  LasField::GetLASFields(pc, fieldsToSave, minPointFormat);
240 
241  for (unsigned i = 0; i < pc->getNumberOfScalarFields(); ++i) {
242  ccScalarField* sf =
243  static_cast<ccScalarField*>(pc->getScalarField(i));
244  // find an equivalent in official LAS fields
245  QString sfName = QString(sf->getName()).toUpper();
246 
247  auto name_matches = [&sfName](const LasField& field) {
248  return sfName == field.getName().toUpper();
249  };
250  auto pos = std::find_if(fieldsToSave.begin(), fieldsToSave.end(),
251  name_matches);
252  if (pos == fieldsToSave.end()) {
253  ExtraLasField::Shared extraField(
254  new ExtraLasField(QString(sf->getName()), Id::Unknown));
255  extraFields.emplace_back(extraField);
256  extraFields.back()->sf = sf;
257  }
258  }
259  }
260 
261  bool hasClassification = false;
262  bool hasClassifFlags = false;
263  for (const LasField& field : fieldsToSave) {
264  switch (field.type) {
265  case LAS_CLASSIFICATION:
266  case LAS_CLASSIF_VALUE:
267  hasClassification = true;
268  break;
269 
273  if (minPointFormat >= 6)
274  hasClassifFlags = true;
275  else
276  hasClassification = true;
277  break;
278 
279  case LAS_CLASSIF_OVERLAP:
280  if (minPointFormat >= 6)
281  hasClassifFlags = true;
282  else
283  assert(false);
284  break;
285 
286  default:
287  // nothing to do
288  break;
289  }
290  }
291 
292  // progress dialog
293  QScopedPointer<ecvProgressDialog> pDlg(nullptr);
294  if (parameters.parentWidget) {
295  pDlg.reset(new ecvProgressDialog(
296  true, parameters.parentWidget)); // cancel available
297  pDlg->setMethodTitle(QObject::tr("Save LAS file"));
298  pDlg->setInfo(QObject::tr("Points: %L1").arg(numberOfPoints));
299  pDlg->start();
300  }
301  cloudViewer::NormalizedProgress nProgress(pDlg.data(), numberOfPoints);
302 
303  CCVector3d bbMin, bbMax;
304  if (!theCloud->getOwnGlobalBB(bbMin, bbMax)) {
305  if (theCloud->size() != 0) {
306  // it can only be acceptable if the cloud is empty
307  //(yes, some people expect to save empty clouds!)
308  return CC_FERR_NO_SAVE;
309  } else {
310  bbMax = bbMin = CCVector3d(0.0, 0.0, 0.0);
311  }
312  }
313 
314  // let the user choose between the original scale and the 'optimal' one (for
315  // accuracy, not for compression ;)
316  CCVector3d originalLasScale(0, 0, 0);
317  bool hasScaleMetaData = false;
318  {
319  originalLasScale.x = theCloud->getMetaData(LAS_SCALE_X_META_DATA)
320  .toDouble(&hasScaleMetaData);
321  if (hasScaleMetaData) {
322  originalLasScale.y = theCloud->getMetaData(LAS_SCALE_Y_META_DATA)
323  .toDouble(&hasScaleMetaData);
324  if (hasScaleMetaData) {
325  originalLasScale.z =
327  .toDouble(&hasScaleMetaData);
328  }
329  }
330  }
331 
332  CCVector3d lasOffset(0, 0, 0);
333  bool hasOffsetMetaData = false;
334  {
335  lasOffset.x = theCloud->getMetaData(LAS_OFFSET_X_META_DATA)
336  .toDouble(&hasOffsetMetaData);
337  if (hasOffsetMetaData) {
338  lasOffset.y = theCloud->getMetaData(LAS_OFFSET_Y_META_DATA)
339  .toDouble(&hasOffsetMetaData);
340  if (hasOffsetMetaData) {
341  lasOffset.z = theCloud->getMetaData(LAS_OFFSET_Z_META_DATA)
342  .toDouble(&hasOffsetMetaData);
343  }
344  }
345  }
346 
347  if (!hasOffsetMetaData) {
348  // Try to use the global shift if no LAS offset is defined
349  if (theCloud->isShifted()) {
350  lasOffset =
351  -theCloud->getGlobalShift(); //'global shift' is the
352  // opposite of LAS offset ;)
353  hasOffsetMetaData = true;
354  } else {
355  // If we don't have any offset, let's use the min bounding-box
356  // corner
358  // we have no choice, we'll use the min bounding box
359  lasOffset.x = bbMin.x;
360  lasOffset.y = bbMin.y;
361  lasOffset.z = 0;
362  }
363  }
364  } else {
365  // We should still check that the offset 'works'
366  if (ecvGlobalShiftManager::NeedShift(bbMax - lasOffset)) {
368  "[LAS] The former LAS_OFFSET doesn't seem to be optimal. "
369  "Using the minimum bounding-box corner instead.");
370  CCVector3d globaShift =
371  theCloud->getGlobalShift(); //'global shift' is the
372  // opposite of LAS offset ;)
373 
374  if (ecvGlobalShiftManager::NeedShift(bbMax + globaShift)) {
376  "[LAS] Using the minimum bounding-box corner instead.");
377  lasOffset.x = bbMin.x;
378  lasOffset.y = bbMin.y;
379  lasOffset.z = 0;
380  } else {
382  "[LAS] Using the previous Global Shift instead.");
383  lasOffset = -globaShift;
384  }
385  }
386  }
387 
388  // maximum cloud 'extents' relatively to the 'offset' point
389  CCVector3d diagPos = bbMax - lasOffset;
390  CCVector3d diagNeg = lasOffset - bbMin;
391  CCVector3d diag(std::max(diagPos.x, diagNeg.x),
392  std::max(diagPos.y, diagNeg.y),
393  std::max(diagPos.z, diagNeg.z));
394  // optimal scale (for accuracy) --> 1e-9 because the maximum integer is
395  // roughly +/-2e+9
396  CCVector3d optimalScale(1.0e-9 * std::max<double>(diag.x, 1.0),
397  1.0e-9 * std::max<double>(diag.y, 1.0),
398  1.0e-9 * std::max<double>(diag.z, 1.0));
399 
400  bool canUseOriginalScale = false;
401  if (hasScaleMetaData) {
402  // we may not be able to use the previous LAS scale
403  canUseOriginalScale = (originalLasScale.x >= optimalScale.x &&
404  originalLasScale.y >= optimalScale.y &&
405  originalLasScale.z >= optimalScale.z);
406  }
407 
408  // uniformize the value to make it less disturbing to some lastools users ;)
409  {
410  double maxScale = std::max(optimalScale.x,
411  std::max(optimalScale.y, optimalScale.z));
412  double n = ceil(log10(maxScale)); // ceil because n should be negative
413  maxScale = pow(10.0, n);
414  optimalScale.x = optimalScale.y = optimalScale.z = maxScale;
415  }
416 
417  CCVector3d lasScale =
418  (canUseOriginalScale ? originalLasScale : optimalScale);
419 
420  if (parameters.alwaysDisplaySaveDialog) {
421  if (!s_saveDlg) s_saveDlg.reset(new LASSaveDlg(nullptr));
422 
423  s_saveDlg->bestAccuracyLabel->setText(QString("(%1, %2, %3)")
424  .arg(optimalScale.x)
425  .arg(optimalScale.y)
426  .arg(optimalScale.z));
427 
428  if (hasScaleMetaData) {
429  s_saveDlg->origAccuracyLabel->setText(
430  QString("(%1, %2, %3)")
431  .arg(originalLasScale.x)
432  .arg(originalLasScale.y)
433  .arg(originalLasScale.z));
434 
435  if (!canUseOriginalScale) {
436  s_saveDlg->labelOriginal->setText(QObject::tr(
437  "Original scale is too small for this cloud "
438  " ")); // add two whitespaces to avoid issues
439  // with italic characters justification
440  s_saveDlg->labelOriginal->setStyleSheet("color: red;");
441  }
442  } else {
443  s_saveDlg->origAccuracyLabel->setText("none");
444  }
445 
446  if (!hasScaleMetaData || !canUseOriginalScale) {
447  if (s_saveDlg->origRadioButton->isChecked())
448  s_saveDlg->bestRadioButton->setChecked(true);
449  s_saveDlg->origRadioButton->setEnabled(false);
450  }
451 
452  s_saveDlg->clearEVLRs();
453 
454  for (const ExtraLasField::Shared& extraField : extraFields) {
455  s_saveDlg->addEVLR(extraField->getName());
456  }
457 
458  s_saveDlg->exec();
459 
460  if (s_saveDlg->bestRadioButton->isChecked()) {
461  lasScale = optimalScale;
462  } else if (s_saveDlg->origRadioButton->isChecked()) {
463  lasScale = originalLasScale;
464  } else if (s_saveDlg->customRadioButton->isChecked()) {
465  double s = s_saveDlg->customScaleDoubleSpinBox->value();
466  lasScale = CCVector3d(s, s, s);
467  }
468  } else if (!hasScaleMetaData) {
469  lasScale = optimalScale;
470  }
471 
472  std::vector<ExtraLasField::Shared> extraFieldsToSave;
473  try {
474  for (unsigned i = 0; i < extraFields.size(); ++i) {
475  if (!s_saveDlg || s_saveDlg->doSaveEVLR(i)) {
476  // All extra scalar fields are written as double.
477  // A more specific solution would be welcome.
478  extraFieldsToSave.push_back(extraFields[i]);
479  }
480  }
481  } catch (const std::bad_alloc&) {
483  }
484 
485  // manage the point format
486  minPointFormat = LasField::UpdateMinPointFormat(minPointFormat, hasColors,
487  false, true);
488 
489  if (theCloud->hasMetaData(
490  LAS_POINT_FORMAT_META_DATA)) // DGM: is it really necessary?
491  {
492  bool ok = false;
493  unsigned previousPointFormat =
494  theCloud->getMetaData(LAS_POINT_FORMAT_META_DATA).toUInt(&ok);
495  if (ok && previousPointFormat < 256) {
496  minPointFormat = std::max(static_cast<uint8_t>(previousPointFormat),
497  minPointFormat);
498  } else {
499  CVLog::Warning("Invalid point_format metadata");
500  }
501  }
502 
503  CC_FILE_ERROR callbackError = CC_FERR_NO_ERROR;
504  unsigned ptsWritten = 0;
505 
506  auto convertOne = [&](PointRef& point) {
507  if (ptsWritten == numberOfPoints) return false;
508 
509  if (pDlg && pDlg->isCancelRequested()) {
510  callbackError = CC_FERR_CANCELED_BY_USER;
511  return false;
512  }
513 
514  const CCVector3* P = theCloud->getPoint(ptsWritten);
515  {
516  CCVector3d Pglobal = theCloud->toGlobal3d<PointCoordinateType>(*P);
517  point.setField(Id::X, Pglobal.x);
518  point.setField(Id::Y, Pglobal.y);
519  point.setField(Id::Z, Pglobal.z);
520  }
521 
522  if (hasColors) {
523  // DGM: LAS colors are stored on 16 bits!
524  const ecvColor::Rgb& rgb = theCloud->getPointColor(ptsWritten);
525  point.setField(Id::Red, static_cast<uint16_t>(rgb.r) << 8);
526  point.setField(Id::Green, static_cast<uint16_t>(rgb.g) << 8);
527  point.setField(Id::Blue, static_cast<uint16_t>(rgb.b) << 8);
528  }
529 
530  // standard las fields
531  uint8_t classFlags = 0;
532  uint8_t classification = 0;
533  for (const LasField& lasField : fieldsToSave) {
534  assert(lasField.sf);
535  Id pdalId = typeToId(lasField.type, minPointFormat);
536  switch (lasField.type) {
537  case LAS_X:
538  case LAS_Y:
539  case LAS_Z:
540  case LAS_RED:
541  case LAS_GREEN:
542  case LAS_BLUE:
543  assert(false);
544  break;
545  case LAS_TIME:
546  point.setField(pdalId,
547  lasField.getSafeValue(ptsWritten) +
548  lasField.sf->getGlobalShift());
549  break;
550  case LAS_CLASSIFICATION:
551  classification = static_cast<uint8_t>(
552  static_cast<int>(
553  lasField.getSafeValue(ptsWritten)) &
554  255);
555  break;
556  case LAS_CLASSIF_VALUE:
557  classification = static_cast<uint8_t>(
558  static_cast<int>(
559  lasField.getSafeValue(ptsWritten)) &
560  (minPointFormat < 6 ? 31 : 255));
561  break;
563  if (lasField.getSafeValue(ptsWritten) != 0) {
564  if (minPointFormat < 6)
565  classification |=
566  32; // bit #5 of the 'Classification' field
567  else
568  classFlags |= 1; // bit #0 of the 'Classification
569  // Flags' field
570  }
571  break;
573  if (lasField.getSafeValue(ptsWritten) != 0) {
574  if (minPointFormat < 6)
575  classification |=
576  64; // bit #6 of the 'Classification' field
577  else
578  classFlags |= 2; // bit #1 of the 'Classification
579  // Flags' field
580  }
581  break;
583  if (lasField.getSafeValue(ptsWritten) != 0) {
584  if (minPointFormat < 6)
585  classification |= 128; // bit #7 of the
586  // 'Classification' field
587  else
588  classFlags |= 4; // bit #2 of the 'Classification
589  // Flags' field
590  }
591  break;
592  case LAS_CLASSIF_OVERLAP:
593  if (lasField.getSafeValue(ptsWritten) != 0) {
594  if (minPointFormat >= 6)
595  classFlags |= 8; // bit #3 of the 'Classification
596  // Flags' field
597  else
598  assert(false);
599  }
600  break;
601  case LAS_INVALID:
602  break;
603  default:
604  point.setField(pdalId, lasField.getSafeValue(ptsWritten));
605  break;
606  }
607  }
608  if (hasClassification)
609  point.setField(Id::Classification, classification);
610  if (hasClassifFlags) point.setField(Id::ClassFlags, classFlags);
611 
612  // extra las fields
613  for (const ExtraLasField::Shared& extraField : extraFieldsToSave) {
614  point.setField(extraField->pdalId,
615  extraField->getSafeValue(ptsWritten) +
616  extraField->sf->getGlobalShift());
617  }
618 
619  nProgress.oneStep();
620 
621  ++ptsWritten;
622  return true;
623  };
624 
625  try {
626  LasWriter writer;
627  Options writerOptions;
628 
629  if (theCloud->hasMetaData(s_LAS_SRS_Key)) {
630  // restore the SRS if possible
631  QString wkt = theCloud->getMetaData(s_LAS_SRS_Key).value<QString>();
632  writerOptions.add("a_srs", wkt.toStdString());
633  }
634  writerOptions.add("dataformat_id", minPointFormat);
635 
636  writerOptions.add("offset_x", lasOffset.x);
637  writerOptions.add("offset_y", lasOffset.y);
638  writerOptions.add("offset_z", lasOffset.z);
639 
640  writerOptions.add("scale_x", lasScale.x);
641  writerOptions.add("scale_y", lasScale.y);
642  writerOptions.add("scale_z", lasScale.z);
643 
644  writerOptions.add("filename", filename.toStdString());
645  writerOptions.add("extra_dims", "all");
646 
648  bool ok = false;
649  unsigned global_encoding =
651  .toUInt(&ok);
652  if (ok) {
653  writerOptions.add("global_encoding", global_encoding);
654  }
655  }
656 
657  if (theCloud->hasMetaData(LAS_PROJECT_UUID_META_DATA)) {
658  QString uuid = theCloud->getMetaData(LAS_PROJECT_UUID_META_DATA)
659  .toString();
660  writerOptions.add("project_id", uuid.toStdString());
661  }
662 
663  if (theCloud->hasMetaData(LAS_VERSION_MINOR_META_DATA)) {
664  bool ok = false;
665  int minor_version =
667  .toInt(&ok);
668  if (ok &&
669  minor_version != 0) // PDAL can read but not write LAS 1.0
670  writerOptions.add("minor_version", minor_version);
671  else if (!ok)
672  CVLog::Warning(QString("Invalid minor_version metadata"));
673  }
674 
675  StreamCallbackFilter f;
676  f.setCallback(convertOne);
677  writer.setInput(f);
678  writer.setOptions(writerOptions);
679 
680  // field count
681  point_count_t tableSize = 3; // XYZ
682  if (hasColors) tableSize += 3; // RGB
683  tableSize += fieldsToSave.size();
684  tableSize += extraFieldsToSave.size();
685 
686  FixedPointTable table(tableSize);
687 
688  table.layout()->registerDim(Id::X);
689  table.layout()->registerDim(Id::Y);
690  table.layout()->registerDim(Id::Z);
691 
692  if (hasColors) {
693  table.layout()->registerDim(Id::Red);
694  table.layout()->registerDim(Id::Green);
695  table.layout()->registerDim(Id::Blue);
696  }
697 
698  for (const LasField& lasField : fieldsToSave) {
699  std::string dimName = lasField.getName().toStdString();
700  Id pdalId = id(dimName);
701  table.layout()->registerDim(pdalId);
702  }
703 
704  for (const ExtraLasField::Shared extraField : extraFields) {
705  std::string dimName = extraField->getName().toStdString();
706  // All extra scalar fields are written as double.
707  // A more specific solution would be welcome.
708  Type t = Type::Double;
709  extraField->pdalId =
710  table.layout()->registerOrAssignDim(dimName, t);
711  }
712 
713  writer.prepare(table);
714  writer.execute(table);
715  } catch (const pdal::pdal_error& p) {
716  CVLog::Error(QString("PDAL exception: %1").arg(p.what()));
718  } catch (const std::exception& e) {
719  CVLog::Error(QString("PDAL generic exception: %1").arg(e.what()));
721  } catch (...) {
723  }
724 
725  return callbackError;
726 }
727 
728 QSharedPointer<LASOpenDlg> s_lasOpenDlg(nullptr);
729 
731 class Tiler {
732 public:
733  Tiler() : w(1), h(1), X(0), Y(1), Z(2) {}
734 
735  ~Tiler() = default;
736 
737  inline size_t tileCount() const { return tilePointViews.size(); }
738 
739  bool init(unsigned width,
740  unsigned height,
741  unsigned Zdim,
742  const QString& absoluteBaseFilename,
743  const CCVector3d& bbMin,
744  const CCVector3d& bbMax,
745  PointTableRef table,
746  const LasHeader& header) {
747  // init tiling dimensions
748  assert(Zdim < 3);
749  Z = Zdim;
750  X = (Z == 2 ? 0 : Z + 1);
751  Y = (X == 2 ? 0 : X + 1);
752 
753  bbMinCorner = bbMin;
754  tileDiag = bbMax - bbMin;
755  tileDiag.u[X] /= width;
756  tileDiag.u[Y] /= height;
757  unsigned count = width * height;
758 
759  try {
760  tilePointViews.resize(count);
761  fileNames.resize(count);
762  } catch (const std::bad_alloc&) {
763  // not enough memory
764  return false;
765  }
766 
767  w = width;
768  h = height;
769 
770  // File extension
771  QString ext = (header.compressed() ? "laz" : "las");
772 
773  for (unsigned i = 0; i < width; ++i) {
774  for (unsigned j = 0; j < height; ++j) {
775  unsigned ii = index(i, j);
776  QString filename = absoluteBaseFilename +
777  QString("_%1_%2.%3")
778  .arg(QString::number(i),
779  QString::number(j), ext);
780 
781  fileNames[ii] = filename;
782  tilePointViews[ii] = std::make_shared<PointView>(table);
783  }
784  }
785 
786  return true;
787  }
788 
789  void addPoint(const PointViewPtr& buffer, unsigned pointIndex) {
790  // determine the right tile
791  CCVector3d Prel =
792  CCVector3d(buffer->getFieldAs<double>(Id::X, pointIndex),
793  buffer->getFieldAs<double>(Id::Y, pointIndex),
794  buffer->getFieldAs<double>(Id::Z, pointIndex));
795  Prel -= bbMinCorner;
796  int ii = static_cast<int>(floor(Prel.u[X] / tileDiag.u[X]));
797  int ji = static_cast<int>(floor(Prel.u[Y] / tileDiag.u[Y]));
798  unsigned i = std::min(static_cast<unsigned>(std::max(ii, 0)), w - 1);
799  unsigned j = std::min(static_cast<unsigned>(std::max(ji, 0)), h - 1);
800  PointViewPtr outputView = tilePointViews[index(i, j)];
801  outputView->appendPoint(*buffer, pointIndex);
802  }
803 
804  void writeAll() {
805  for (unsigned i = 0; i < tilePointViews.size(); ++i) {
806  LasWriter writer;
807  Options writerOptions;
808  PointTable table;
809  BufferReader bufferReader;
810 
811  writerOptions.add("filename", fileNames[i].toStdString());
812  if (tilePointViews[i]->empty()) continue;
813  try {
814  bufferReader.addView(tilePointViews[i]);
815  writer.setInput(bufferReader);
816  writer.setOptions(writerOptions);
817  writer.prepare(table);
818  writer.execute(table);
819  } catch (const pdal_error& e) {
820  CVLog::Error(QString("PDAL exception '%1'").arg(e.what()));
821  }
822  }
823  }
824 
825 protected:
826  inline unsigned index(unsigned i, unsigned j) const { return i + j * w; }
827 
828  unsigned w, h;
829  unsigned X, Y, Z;
831  std::vector<PointViewPtr> tilePointViews;
832  std::vector<QString> fileNames;
833 };
834 
836  LasCloudChunk() : loadedCloud(nullptr), size(0) {}
837 
839  std::vector<LasField::Shared> lasFields;
840  unsigned size;
841 
843 
844  bool hasColors() const { return loadedCloud->hasColors(); }
845 
846  bool reserveSize(unsigned nbPoints) {
847  size = nbPoints;
848  loadedCloud = new ccPointCloud();
849  bool success = loadedCloud->reserveThePointsTable(nbPoints);
850  if (!success) delete loadedCloud;
851 
852  return success;
853  }
854 
855  void createFieldsToLoad(const IdList& extraFieldsToLoad,
856  const StringList& extraNamesToLoad) {
857  if (!s_lasOpenDlg) {
858  assert(false);
859  return;
860  }
861 
862  // DGM: from now on, we only enable scalar fields when we detect a valid
863  // value!
864  if (s_lasOpenDlg->doLoad(LAS_CLASSIFICATION))
865  lasFields.push_back(LasField::Shared(
866  new LasField(LAS_CLASSIFICATION, 0, 0,
867  255))); // unsigned char: between 0 and 255
868  if (s_lasOpenDlg->doLoad(LAS_CLASSIF_VALUE))
869  lasFields.push_back(LasField::Shared(new LasField(
870  LAS_CLASSIF_VALUE, 0, 0, 31))); // 5 bits: between 0 and 31
871  if (s_lasOpenDlg->doLoad(LAS_CLASSIF_SYNTHETIC))
872  lasFields.push_back(LasField::Shared(new LasField(
873  LAS_CLASSIF_SYNTHETIC, 0, 0, 1))); // 1 bit: 0 or 1
874  if (s_lasOpenDlg->doLoad(LAS_CLASSIF_KEYPOINT))
875  lasFields.push_back(LasField::Shared(new LasField(
876  LAS_CLASSIF_KEYPOINT, 0, 0, 1))); // 1 bit: 0 or 1
877  if (s_lasOpenDlg->doLoad(LAS_CLASSIF_WITHHELD))
878  lasFields.push_back(LasField::Shared(new LasField(
879  LAS_CLASSIF_WITHHELD, 0, 0, 1))); // 1 bit: 0 or 1
880  if (s_lasOpenDlg->doLoad(LAS_CLASSIF_OVERLAP))
881  lasFields.push_back(LasField::Shared(new LasField(
882  LAS_CLASSIF_OVERLAP, 0, 0, 1))); // 1 bit: 0 or 1
883  if (s_lasOpenDlg->doLoad(LAS_INTENSITY))
884  lasFields.push_back(LasField::Shared(
885  new LasField(LAS_INTENSITY, 0, 0,
886  65535))); // 16 bits: between 0 and 65536
887  if (s_lasOpenDlg->doLoad(LAS_TIME))
888  lasFields.push_back(LasField::Shared(new LasField(
889  LAS_TIME, 0, 0,
890  -1.0))); // 8 bytes (double) --> we use global shift!
891  if (s_lasOpenDlg->doLoad(LAS_RETURN_NUMBER))
892  lasFields.push_back(LasField::Shared(new LasField(
893  LAS_RETURN_NUMBER, 1, 1, 7))); // 3 bits: between 1 and 7
894  if (s_lasOpenDlg->doLoad(LAS_NUMBER_OF_RETURNS))
895  lasFields.push_back(LasField::Shared(
897  7))); // 3 bits: between 1 and 7
898  if (s_lasOpenDlg->doLoad(LAS_SCAN_DIRECTION))
899  lasFields.push_back(LasField::Shared(new LasField(
900  LAS_SCAN_DIRECTION, 0, 0, 1))); // 1 bit: 0 or 1
901  if (s_lasOpenDlg->doLoad(LAS_FLIGHT_LINE_EDGE))
902  lasFields.push_back(LasField::Shared(new LasField(
903  LAS_FLIGHT_LINE_EDGE, 0, 0, 1))); // 1 bit: 0 or 1
904  if (s_lasOpenDlg->doLoad(LAS_SCAN_ANGLE_RANK))
905  lasFields.push_back(LasField::Shared(
906  new LasField(LAS_SCAN_ANGLE_RANK, 0, -90,
907  90))); // signed char: between -90 and +90
908  if (s_lasOpenDlg->doLoad(LAS_USER_DATA))
909  lasFields.push_back(LasField::Shared(
910  new LasField(LAS_USER_DATA, 0, 0,
911  255))); // unsigned char: between 0 and 255
912  if (s_lasOpenDlg->doLoad(LAS_POINT_SOURCE_ID))
913  lasFields.push_back(LasField::Shared(
914  new LasField(LAS_POINT_SOURCE_ID, 0, 0,
915  65535))); // 16 bits: between 0 and 65536
916 
917  // extra fields
918  for (size_t i = 0; i < extraNamesToLoad.size(); ++i) {
919  QString name = QString::fromStdString(extraNamesToLoad[i]);
920  ExtraLasField* eField =
921  new ExtraLasField(name, extraFieldsToLoad[i]);
922  lasFields.emplace_back(eField);
923  }
924  }
925 
927  if (loadedCloud == nullptr) return;
928 
929  while (!lasFields.empty()) {
930  LasField::Shared& field = lasFields.back();
931  if (field && field->sf) {
932  field->sf->computeMinAndMax();
933 
934  if (field->type == LAS_CLASSIFICATION ||
935  field->type == LAS_CLASSIF_VALUE ||
936  field->type == LAS_CLASSIF_SYNTHETIC ||
937  field->type == LAS_CLASSIF_KEYPOINT ||
938  field->type == LAS_CLASSIF_WITHHELD ||
939  field->type == LAS_CLASSIF_OVERLAP ||
940  field->type == LAS_RETURN_NUMBER ||
941  field->type == LAS_NUMBER_OF_RETURNS) {
942  int cMin = static_cast<int>(field->sf->getMin());
943  int cMax = static_cast<int>(field->sf->getMax());
944  field->sf->setColorRampSteps(
945  std::min<int>(cMax - cMin + 1, 256));
946  // classifSF->setMinSaturation(cMin);
947 
948  } else if (field->type == LAS_INTENSITY) {
949  field->sf->setColorScale(
952  }
953 
954  int sfIndex = loadedCloud->addScalarField(field->sf);
955  if (sfIndex >= 0 && !loadedCloud->hasDisplayedScalarField()) {
958  }
959  field->sf->release();
960  field->sf = nullptr;
961  } else {
963  QString("[LAS] All '%1' values were the same (%2)! We "
964  "ignored them...")
965  .arg(field->type == LAS_EXTRA
966  ? field->getName()
967  : QString(LAS_FIELD_NAMES
968  [field->type]))
969  .arg(field->firstValue));
970  }
971 
972  lasFields.pop_back();
973  }
974  }
975 };
976 
978  std::string name;
979  pdal::Dimension::Type dimType;
980 };
981 
983  ccHObject& container,
984  LoadParameters& parameters) {
985  try {
986  Options las_opts;
987  las_opts.add("filename", filename.toStdString());
988  las_opts.add("use_eb_vlr", true);
989  LasReader lasReader;
990  lasReader.setOptions(las_opts);
991  FixedPointTable fields(100);
992  PointLayoutPtr layout(fields.layout());
993  if (nullptr == layout) {
994  CVLog::Warning("PDAL failed to retrieve the file layout");
996  }
997  lasReader.prepare(fields);
998  LasHeader lasHeader = lasReader.header();
999 
1000  unsigned nbOfPoints = static_cast<unsigned>(lasHeader.pointCount());
1001  if (nbOfPoints == 0) {
1002  ccPointCloud* emptyCloud = new ccPointCloud("empty");
1003  container.addChild(emptyCloud);
1004  // strange file ;)
1005  return CC_FERR_NO_ERROR; // Its still strange
1006  }
1007 
1008  CCVector3d bbMin(lasHeader.minX(), lasHeader.minY(), lasHeader.minZ());
1009  CCVector3d bbMax(lasHeader.maxX(), lasHeader.maxY(), lasHeader.maxZ());
1010  CCVector3d lasScale(lasHeader.scaleX(), lasHeader.scaleY(),
1011  lasHeader.scaleZ());
1012  CCVector3d lasOffset(lasHeader.offsetX(), lasHeader.offsetY(),
1013  lasHeader.offsetZ());
1014 
1015  const uint8_t pointFormat = lasHeader.pointFormat();
1016  CVLog::Print("[LAS] Point format: " + QString::number(pointFormat));
1017 
1018  if (!s_lasOpenDlg) {
1019  s_lasOpenDlg.reset(new LASOpenDlg());
1020  }
1021 
1022  QuickInfo file_info = lasReader.preview();
1023  s_lasOpenDlg->setDimensions(file_info.m_dimNames);
1024  s_lasOpenDlg->clearEVLRs();
1025  s_lasOpenDlg->setInfos(filename, nbOfPoints, bbMin, bbMax);
1026  if (pointFormat <= 5) {
1027  s_lasOpenDlg->classifOverlapCheckBox->setEnabled(false);
1028  s_lasOpenDlg->classifOverlapCheckBox->setVisible(false);
1029  }
1030 
1031  std::vector<ExtraDimDescriptor> extraDims;
1032  if (nullptr != layout) {
1033  for (const pdal::Dimension::Id pdalId : layout->dims()) {
1034  switch (pdalId) {
1045  case pdal::Dimension::Id::ScanChannel:
1046  case pdal::Dimension::Id::Infrared:
1047  case pdal::Dimension::Id::ClassFlags:
1048  case pdal::Dimension::Id::PointId:
1050  case pdal::Dimension::Id::Y:
1051  case pdal::Dimension::Id::Z:
1052  case pdal::Dimension::Id::Red:
1053  case pdal::Dimension::Id::Green:
1054  case pdal::Dimension::Id::Blue:
1055  // standard fields
1056  break;
1057  default:
1058  // extended fields
1059  ExtraDimDescriptor dim;
1060  dim.name = layout->dimName(pdalId);
1061  dim.dimType = layout->dimType(pdalId);
1062  extraDims.push_back(dim);
1063 
1064  QString desanitizedSFName = LasField::DesanitizeString(
1065  QString::fromStdString(dim.name));
1066  s_lasOpenDlg->addEVLR(desanitizedSFName);
1067  break;
1068  }
1069  }
1070  extraDims.shrink_to_fit();
1071  }
1072 
1073  if (parameters.sessionStart) {
1074  // we do this AFTER restoring the previous context because it may
1075  // still be good that the previous configuration is restored even
1076  // though the user needs to confirm it
1077  s_lasOpenDlg->resetApplyAll();
1078  }
1079 
1080  if (parameters.alwaysDisplayLoadDialog &&
1081  !s_lasOpenDlg->autoSkipMode() && !s_lasOpenDlg->exec()) {
1082  return CC_FERR_CANCELED_BY_USER;
1083  }
1084 
1085  bool ignoreDefaultFields =
1086  s_lasOpenDlg->ignoreDefaultFieldsCheckBox->isChecked();
1087 
1088  bool loadRGBComponent[3] = {s_lasOpenDlg->doLoad(LAS_RED),
1089  s_lasOpenDlg->doLoad(LAS_GREEN),
1090  s_lasOpenDlg->doLoad(LAS_BLUE)};
1091  bool loadColor = (loadRGBComponent[0] || loadRGBComponent[1] ||
1092  loadRGBComponent[2]);
1093 
1094  // by default we read colors as triplets of 8 bits integers but we might
1095  // dynamically change this if we encounter values using 16 bits (16 bits
1096  // is the standard!)
1097  unsigned char colorCompBitShift = 0;
1098  bool forced8bitRgbMode = s_lasOpenDlg->forced8bitRgbMode();
1099  ecvColor::Rgb rgb(0, 0, 0);
1100 
1101  StringList extraNamesToLoad;
1102  std::string extraDimsArg;
1103  for (unsigned i = 0; i < extraDims.size(); ++i) {
1104  if (s_lasOpenDlg->doLoadEVLR(i)) {
1105  extraDimsArg += extraDims[i].name + "=" +
1106  interpretationName(extraDims[i].dimType) + ",";
1107  extraNamesToLoad.push_back(extraDims[i].name);
1108  }
1109  }
1110 
1111  if (!extraNamesToLoad.empty()) {
1112  // If extra fields are requested, reload the file with the new
1113  // extra_dims parameters
1114  Options las_opts2;
1115  las_opts2.add("extra_dims", extraDimsArg);
1116 
1117  lasReader.addOptions(las_opts2);
1118  lasReader.prepare(fields);
1119  }
1120 
1121  std::vector<Id> extraDimensionsIds;
1122  for (std::string& dim : extraNamesToLoad) {
1123  extraDimensionsIds.push_back(layout->findDim(dim));
1124  }
1125 
1126  bool tiling = s_lasOpenDlg->tileGroupBox->isChecked();
1127 
1128  QScopedPointer<ecvProgressDialog> pDlg(nullptr);
1129  if (parameters.parentWidget) {
1130  pDlg.reset(new ecvProgressDialog(
1131  true, parameters.parentWidget)); // cancel available
1132  pDlg->setMethodTitle(QObject::tr("Open LAS file"));
1133  pDlg->setInfo(QObject::tr("Points: %L1").arg(nbOfPoints));
1134  pDlg->start();
1135  }
1136 
1137  if (tiling) {
1138  Tiler tiler;
1139  PointTable table;
1140  PointViewSet pointViewSet;
1141 
1142  // tiling (vertical) dimension
1143  unsigned vertDim = 2;
1144  switch (s_lasOpenDlg->tileDimComboBox->currentIndex()) {
1145  case 0: // XY
1146  vertDim = 2;
1147  break;
1148  case 1: // XZ
1149  vertDim = 1;
1150  break;
1151  case 2: // YZ
1152  vertDim = 0;
1153  break;
1154  default:
1155  assert(false);
1156  break;
1157  }
1158 
1159  auto w = static_cast<unsigned>(s_lasOpenDlg->wTileSpinBox->value());
1160  auto h = static_cast<unsigned>(s_lasOpenDlg->hTileSpinBox->value());
1161 
1162  QString outputBaseName = s_lasOpenDlg->outputPathLineEdit->text() +
1163  "/" + QFileInfo(filename).baseName();
1164  if (!tiler.init(w, h, vertDim, outputBaseName, bbMin, bbMax, table,
1165  lasHeader)) {
1167  }
1168 
1169  auto prepareAndExecute = [&lasReader, &table]() -> PointViewSet {
1170  lasReader.prepare(table);
1171  lasReader.prepare(table);
1172  return lasReader.execute(table);
1173  };
1174 
1175  if (parameters.parentWidget) {
1176  pDlg.reset(
1177  new ecvProgressDialog(false, parameters.parentWidget));
1178  pDlg->setMethodTitle(QObject::tr("LAS file"));
1179  pDlg->setInfo(
1180  QObject::tr("Please wait... reading in progress"));
1181  pDlg->setRange(0, 0);
1182  pDlg->setModal(true);
1183  pDlg->start();
1184  }
1185 
1186  QFutureWatcher<PointViewSet> reader;
1187  QObject::connect(&reader, SIGNAL(finished()), pDlg.data(),
1188  SLOT(reset()));
1189  reader.setFuture(QtConcurrent::run(prepareAndExecute));
1190 
1191  if (pDlg) {
1192  pDlg->exec();
1193  }
1194  reader.waitForFinished();
1195 
1196  PointViewSet viewSet = reader.result();
1197  PointViewPtr pointView = *viewSet.begin();
1198 
1199  if (parameters.parentWidget && pDlg) {
1200  pDlg.reset(new ecvProgressDialog(
1201  true, parameters.parentWidget)); // cancel available
1202  pDlg->setMethodTitle(QObject::tr("Tiling points"));
1203  pDlg->setInfo(QObject::tr("Points: %L1").arg(nbOfPoints));
1204  pDlg->start();
1205  }
1206  cloudViewer::NormalizedProgress nProgress(pDlg.data(), nbOfPoints);
1207 
1208  for (PointId idx = 0; idx < pointView->size(); ++idx) {
1209  if (pDlg->isCancelRequested()) return CC_FERR_CANCELED_BY_USER;
1210  tiler.addPoint(pointView, idx);
1211  nProgress.oneStep();
1212  }
1213 
1214  // Now the tiler will actually write the points
1215  if (parameters.parentWidget) {
1216  pDlg.reset(
1217  new ecvProgressDialog(false, parameters.parentWidget));
1218  pDlg->setMethodTitle(QObject::tr("LAS file"));
1219  pDlg->setInfo(
1220  QObject::tr("Please wait... writing in progress"));
1221  pDlg->setRange(0, 0);
1222  pDlg->setModal(true);
1223  pDlg->start();
1224  }
1225 
1226  QFutureWatcher<void> writer;
1227  QObject::connect(&writer, SIGNAL(finished()), pDlg.data(),
1228  SLOT(reset()));
1229  writer.setFuture(
1230  QtConcurrent::run([&tiler]() { tiler.writeAll(); }));
1231 
1232  pDlg->exec();
1233  writer.waitForFinished();
1234 
1235  return CC_FERR_NO_ERROR;
1236  }
1237 
1238  cloudViewer::NormalizedProgress nProgress(pDlg.data(), nbOfPoints);
1239  CCVector3d Pshift(0, 0, 0);
1240  bool preserveCoordinateShift = true;
1241 
1242  unsigned fileChunkSize = 0;
1243  unsigned nbPointsRead = 0;
1244 
1245  StreamCallbackFilter f;
1246  f.setInput(lasReader);
1247 
1248  unsigned nbOfChunks =
1249  (nbOfPoints / CC_MAX_NUMBER_OF_POINTS_PER_CLOUD) + 1;
1250  std::vector<LasCloudChunk> chunks(nbOfChunks, LasCloudChunk());
1251 
1252  CC_FILE_ERROR callbackError = CC_FERR_NO_ERROR;
1253  auto ccProcessOne = [&](PointRef& point) {
1254  if (pDlg && pDlg->isCancelRequested()) {
1255  callbackError = CC_FERR_CANCELED_BY_USER;
1256  return false;
1257  }
1258 
1259  LasCloudChunk& pointChunk =
1260  chunks[nbPointsRead / CC_MAX_NUMBER_OF_POINTS_PER_CLOUD];
1261 
1262  if (pointChunk.getLoadedCloud() == nullptr) {
1263  // create a new cloud
1264  unsigned pointsToRead = nbOfPoints - nbPointsRead;
1265  fileChunkSize = std::min(pointsToRead,
1267  if (!pointChunk.reserveSize(fileChunkSize)) {
1268  CVLog::Warning("[LAS] Not enough memory!");
1269  callbackError = CC_FERR_NOT_ENOUGH_MEMORY;
1270  return false;
1271  }
1272 
1273  if (preserveCoordinateShift) {
1274  pointChunk.loadedCloud->setGlobalShift(Pshift);
1275  }
1276 
1277  // save the Spatial reference as meta-data
1278  SpatialReference srs = lasHeader.srs();
1279  if (!srs.empty()) {
1280  QString wkt = QString::fromStdString(srs.getWKT());
1281  CVLog::Print("[LAS] Spatial reference: " + wkt);
1282  pointChunk.loadedCloud->setMetaData(s_LAS_SRS_Key, wkt);
1283  } else {
1284  CVLog::Print("[LAS] Spatial reference: None");
1285  }
1286 
1287  pointChunk.createFieldsToLoad(extraDimensionsIds,
1288  extraNamesToLoad);
1289  }
1290 
1291  ccPointCloud* loadedCloud = pointChunk.loadedCloud;
1292  std::vector<LasField::Shared>& fieldsToLoad = pointChunk.lasFields;
1293 
1294  // first point check for 'big' coordinates
1295  if (nbPointsRead == 0) {
1296  CCVector3d P(static_cast<PointCoordinateType>(
1297  point.getFieldAs<int>(Id::X)),
1298  static_cast<PointCoordinateType>(
1299  point.getFieldAs<int>(Id::Y)),
1300  static_cast<PointCoordinateType>(
1301  point.getFieldAs<int>(Id::Z)));
1302 
1303  // backup input global parameters
1304  ecvGlobalShiftManager::Mode csModeBackup =
1305  parameters.shiftHandlingMode;
1306  bool useLasOffset = false;
1307  // set the LAS offset as default if none was provided
1308  CCVector3d lasOffsetXY(lasOffset.x, lasOffset.y, 0.0);
1309  if (lasOffsetXY.norm2() != 0 &&
1310  ((nullptr == parameters.coordinatesShiftEnabled) ||
1311  (false == *parameters.coordinatesShiftEnabled))) {
1312  if (csModeBackup !=
1314  NO_DIALOG) // No dialog, practically means that
1315  // we don't want any shift!
1316  {
1317  useLasOffset = true;
1318  Pshift = -lasOffsetXY;
1319  if (csModeBackup !=
1321  parameters.shiftHandlingMode =
1324  }
1325  }
1326  }
1327 
1328  if (HandleGlobalShift(P, Pshift, preserveCoordinateShift,
1329  parameters, useLasOffset)) {
1330  if (preserveCoordinateShift) {
1331  loadedCloud->setGlobalShift(Pshift);
1332  }
1334  "[LAS] Cloud has been recentered! Translation: "
1335  "(%.2f ; %.2f ; %.2f)",
1336  Pshift.x, Pshift.y, Pshift.z);
1337  }
1338 
1339  // restore previous parameters
1340  parameters.shiftHandlingMode = csModeBackup;
1341  }
1342 
1343  CCVector3 P(static_cast<PointCoordinateType>(
1344  point.getFieldAs<double>(Id::X) + Pshift.x),
1345  static_cast<PointCoordinateType>(
1346  point.getFieldAs<double>(Id::Y) + Pshift.y),
1347  static_cast<PointCoordinateType>(
1348  point.getFieldAs<double>(Id::Z) + Pshift.z));
1349  loadedCloud->addPoint(P);
1350 
1351  if (loadColor) {
1352  uint16_t red = loadRGBComponent[0]
1353  ? point.getFieldAs<uint16_t>(Id::Red)
1354  : 0;
1355  uint16_t green = loadRGBComponent[1]
1356  ? point.getFieldAs<uint16_t>(Id::Green)
1357  : 0;
1358  uint16_t blue = loadRGBComponent[2]
1359  ? point.getFieldAs<uint16_t>(Id::Blue)
1360  : 0;
1361 
1362  // if we don't have reserved a color field yet, we check that
1363  // color is not black
1364  bool pushColor = true;
1365  if (!loadedCloud->hasColors()) {
1366  if (red || green || blue) {
1367  if (loadedCloud->reserveTheRGBTable()) {
1368  // we must set the color (black) of all previously
1369  // skipped points
1370  for (unsigned i = 0; i + 1 < loadedCloud->size();
1371  ++i) {
1372  loadedCloud->addRGBColor(ecvColor::black);
1373  }
1374  } else {
1376  "[LAS]: Not enough memory, color field "
1377  "will be ignored!");
1378  loadColor = false; // no need to retry with the
1379  // other chunks anyway
1380  pushColor = false;
1381  }
1382  } else // otherwise we ignore it for the moment (we'll add
1383  // it later if necessary)
1384  {
1385  pushColor = false;
1386  }
1387  }
1388  if (pushColor) {
1389  // we test if the color components are coded on 16 bits
1390  // (standard) or only on 8 bits (it happens ;)
1391  if (!forced8bitRgbMode && colorCompBitShift == 0) {
1392  if ((red & 0xFF00) || (green & 0xFF00) ||
1393  (blue & 0xFF00)) {
1394  // the color components are coded on 16 bits!
1395  CVLog::Print(
1396  "[LAS] Color components are coded on 16 "
1397  "bits");
1398  colorCompBitShift = 8;
1399  // we fix all the previously read colors
1400  for (unsigned i = 0; i + 1 < loadedCloud->size();
1401  ++i) {
1402  loadedCloud->setPointColor(
1403  i, ecvColor::black); // 255 >> 8 = 0!
1404  }
1405  }
1406  }
1407  rgb.r = static_cast<ColorCompType>(red >>
1408  colorCompBitShift);
1409  rgb.g = static_cast<ColorCompType>(green >>
1410  colorCompBitShift);
1411  rgb.b = static_cast<ColorCompType>(blue >>
1412  colorCompBitShift);
1413 
1414  loadedCloud->addRGBColor(rgb);
1415  }
1416  }
1417 
1418  // additional fields
1419  for (LasField::Shared& field : fieldsToLoad) {
1420  double value = 0.0;
1421  Id pdalId = typeToId(field->type, pointFormat);
1422 
1423  switch (field->type) {
1424  case LAS_EXTRA: {
1425  ExtraLasField* extraField =
1426  static_cast<ExtraLasField*>(field.data());
1427  value = point.getFieldAs<double>(extraField->pdalId);
1428  break;
1429  }
1430  case LAS_TIME:
1431  value = point.getFieldAs<double>(Id::GpsTime);
1432  if (field->sf) {
1433  // shift time values (so as to avoid losing
1434  // accuracy)
1435  value -= field->sf->getGlobalShift();
1436  }
1437  break;
1438  case LAS_CLASSIF_VALUE:
1439  if (pointFormat <= 5)
1440  value = (point.getFieldAs<int>(pdalId) &
1441  31); // bit #0-4 of the 'Classification'
1442  // field
1443  else
1444  value = point.getFieldAs<int>(pdalId);
1445  break;
1446  case LAS_CLASSIF_SYNTHETIC:
1447  if (pointFormat <= 5)
1448  value = (point.getFieldAs<int>(pdalId) & 32)
1449  ? 1.0
1450  : 0.0; // bit #5 of the
1451  // 'Classification' field
1452  else
1453  value = (point.getFieldAs<int>(pdalId) & 1)
1454  ? 1.0
1455  : 0.0; // bit #0 of the
1456  // 'Classification Flags'
1457  // field
1458  break;
1459  case LAS_CLASSIF_KEYPOINT:
1460  if (pointFormat <= 5)
1461  value = (point.getFieldAs<int>(pdalId) & 64)
1462  ? 1.0
1463  : 0.0; // bit #6 of the
1464  // 'Classification' field
1465  else
1466  value = (point.getFieldAs<int>(pdalId) & 2)
1467  ? 1.0
1468  : 0.0; // bit #1 of the
1469  // 'Classification Flags'
1470  // field
1471  break;
1472  case LAS_CLASSIF_WITHHELD:
1473  if (pointFormat <= 5)
1474  value = (point.getFieldAs<int>(pdalId) & 128)
1475  ? 1.0
1476  : 0.0; // bit #7 of the
1477  // 'Classification' field
1478  else
1479  value = (point.getFieldAs<int>(pdalId) & 4)
1480  ? 1.0
1481  : 0.0; // bit #2 of the
1482  // 'Classification Flags'
1483  // field
1484  break;
1485  case LAS_CLASSIF_OVERLAP:
1486  if (pointFormat <= 5) {
1487  assert(false); // not present before point format 6
1488  } else {
1489  value = (point.getFieldAs<int>(pdalId) & 8)
1490  ? 1.0
1491  : 0.0; // bit #3 of the
1492  // 'Classification Flags'
1493  // field
1494  }
1495  break;
1496  default:
1497  value = point.getFieldAs<double>(pdalId);
1498  break;
1499  }
1500  if (field->sf) {
1501  auto s = static_cast<ScalarType>(value);
1502  field->sf->addElement(s);
1503  } else {
1504  // first point? we track its value
1505  if (loadedCloud->size() == 1) {
1506  field->firstValue = value;
1507  }
1508  if (!ignoreDefaultFields || value != field->firstValue ||
1509  (field->firstValue != field->defaultValue &&
1510  field->firstValue >= field->minValue)) {
1511  field->sf = new ccScalarField(qPrintable(
1512  LasField::DesanitizeString(field->getName())));
1513  if (field->sf->reserveSafe(fileChunkSize)) {
1514  field->sf->link();
1515  if (field->type == LAS_TIME) {
1516  double timeShift = 0.0;
1517  if (!s_lasOpenDlg->getTimeShift(timeShift)) {
1518  // we use the first value as 'global shift'
1519  // (otherwise we will lose accuracy)
1520  timeShift = static_cast<int64_t>(
1521  field->firstValue /
1522  10000.0) *
1523  10000.0;
1524  }
1525  field->sf->setGlobalShift(timeShift);
1526  value -= timeShift;
1527  if (value < 1.0e5) {
1529  "[LAS] Time SF has been shifted to "
1530  "prevent a loss of accuracy (%.2f)",
1531  timeShift);
1532  } else if (timeShift > 0.0) {
1534  "[LAS] Time SF has been shifted "
1535  "but accuracy may not be preserved "
1536  "(%.2f)",
1537  timeShift);
1538  } else {
1540  "[LAS] Time SF has not been "
1541  "shifted. Accuracy may not be "
1542  "preserved.");
1543  }
1544  field->firstValue = value;
1545  }
1546 
1547  auto defaultValue = static_cast<ScalarType>(
1548  field->defaultValue);
1549  for (unsigned i = 1; i < loadedCloud->size(); ++i) {
1550  field->sf->emplace_back(defaultValue);
1551  }
1552  auto s = static_cast<ScalarType>(value);
1553  field->sf->emplace_back(s);
1554  } else {
1556  QString("[LAS] Not enough memory: '%1' "
1557  "field will be ignored!")
1558  .arg(LAS_FIELD_NAMES[field->type]));
1559  field->sf->release();
1560  field->sf = nullptr;
1561  }
1562  }
1563  }
1564  }
1565  ++nbPointsRead;
1566  nProgress.oneStep();
1567  return true;
1568  };
1569 
1570  f.setCallback(ccProcessOne);
1571  f.prepare(fields);
1572  f.execute(fields);
1573 
1574  if (callbackError != CC_FERR_NO_ERROR) {
1575  return callbackError;
1576  }
1577 
1578  for (auto& chunk : chunks) {
1579  chunk.addLasFieldsToCloud();
1580  ccPointCloud* loadedCloud = chunk.getLoadedCloud();
1581 
1582  if (loadedCloud) {
1583  if (loadedCloud->size()) {
1584  bool thisChunkHasColors = chunk.hasColors();
1585  loadedCloud->showColors(thisChunkHasColors);
1586  if (loadColor && !thisChunkHasColors) {
1588  "[LAS] Color field was all black! We ignored "
1589  "it...");
1590  }
1591 
1592  // if we had reserved too much memory
1593  if (loadedCloud->size() < loadedCloud->capacity()) {
1594  loadedCloud->resize(loadedCloud->size());
1595  }
1596 
1597  QString chunkName("unnamed - Cloud");
1598  unsigned n = container.getChildrenNumber();
1599  if (n != 0) {
1600  if (n == 1) {
1601  container.getChild(0)->setName(chunkName +
1602  QString(" #1"));
1603  }
1604  chunkName += QString(" #%1").arg(n + 1);
1605  }
1606  loadedCloud->setName(chunkName);
1607 
1608  loadedCloud->setMetaData(LAS_SCALE_X_META_DATA,
1609  QVariant(lasScale.x));
1610  loadedCloud->setMetaData(LAS_SCALE_Y_META_DATA,
1611  QVariant(lasScale.y));
1612  loadedCloud->setMetaData(LAS_SCALE_Z_META_DATA,
1613  QVariant(lasScale.z));
1614  loadedCloud->setMetaData(LAS_OFFSET_X_META_DATA,
1615  QVariant(lasOffset.x));
1616  loadedCloud->setMetaData(LAS_OFFSET_Y_META_DATA,
1617  QVariant(lasOffset.y));
1618  loadedCloud->setMetaData(LAS_OFFSET_Z_META_DATA,
1619  QVariant(lasOffset.z));
1620  loadedCloud->setMetaData(
1622  QVariant(lasHeader.globalEncoding()));
1623 
1624  const pdal::Uuid projectUUID = lasHeader.projectId();
1625  if (!projectUUID.isNull()) {
1626  loadedCloud->setMetaData(
1628  QVariant(QString::fromStdString(
1629  projectUUID.toString())));
1630  }
1631 
1632  loadedCloud->setMetaData(
1634  QVariant(lasHeader.versionMajor()));
1635  loadedCloud->setMetaData(
1637  QVariant(lasHeader.versionMinor()));
1639  QVariant(pointFormat));
1640 
1641  container.addChild(loadedCloud);
1642  loadedCloud = nullptr;
1643  } else {
1644  // empty cloud?!
1645  delete loadedCloud;
1646  loadedCloud = nullptr;
1647  }
1648  }
1649  }
1650  } catch (const pdal::pdal_error& p) {
1651  CVLog::Error(QString("PDAL exception: %1").arg(p.what()));
1653  } catch (const std::exception& e) {
1654  CVLog::Error(QString("PDAL generic exception: %1").arg(e.what()));
1656  } catch (...) {
1658  }
1659 
1660  return CC_FERR_NO_ERROR;
1661 }
Vector3Tpl< double > CCVector3d
Double 3D Vector.
Definition: CVGeom.h:804
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int64_t CV_CLASS_ENUM
Type of object type flags (64 bits)
Definition: CVTypes.h:97
std::string filename
int width
std::vector< PCLPointField > fields
std::string name
int height
int count
int offset
char type
CC_FILE_ERROR
Typical I/O filter errors.
Definition: FileIOFilter.h:20
@ CC_FERR_CANCELED_BY_USER
Definition: FileIOFilter.h:30
@ CC_FERR_THIRD_PARTY_LIB_FAILURE
Definition: FileIOFilter.h:36
@ CC_FERR_THIRD_PARTY_LIB_EXCEPTION
Definition: FileIOFilter.h:37
@ CC_FERR_BAD_ARGUMENT
Definition: FileIOFilter.h:22
@ CC_FERR_NO_SAVE
Definition: FileIOFilter.h:27
@ CC_FERR_NO_ERROR
Definition: FileIOFilter.h:21
@ CC_FERR_BAD_ENTITY_TYPE
Definition: FileIOFilter.h:29
@ CC_FERR_NOT_ENOUGH_MEMORY
Definition: FileIOFilter.h:31
static const char LAS_VERSION_MAJOR_META_DATA[]
Definition: LASFields.h:29
static const char LAS_OFFSET_X_META_DATA[]
Definition: LASFields.h:26
static const char LAS_SCALE_X_META_DATA[]
Definition: LASFields.h:23
const char LAS_FIELD_NAMES[][28]
Definition: LASFields.h:63
static const char LAS_OFFSET_Y_META_DATA[]
Definition: LASFields.h:27
static const char LAS_PROJECT_UUID_META_DATA[]
Definition: LASFields.h:33
LAS_FIELDS
Definition: LASFields.h:35
@ LAS_INVALID
Definition: LASFields.h:60
@ LAS_CLASSIF_KEYPOINT
Definition: LASFields.h:56
@ LAS_Z
Definition: LASFields.h:38
@ LAS_SCAN_DIRECTION
Definition: LASFields.h:42
@ LAS_POINT_SOURCE_ID
Definition: LASFields.h:47
@ LAS_CLASSIF_OVERLAP
Definition: LASFields.h:58
@ LAS_RETURN_NUMBER
Definition: LASFields.h:40
@ LAS_Y
Definition: LASFields.h:37
@ LAS_CLASSIF_VALUE
Definition: LASFields.h:54
@ LAS_FLIGHT_LINE_EDGE
Definition: LASFields.h:43
@ LAS_TIME
Definition: LASFields.h:51
@ LAS_NUMBER_OF_RETURNS
Definition: LASFields.h:41
@ LAS_SCAN_ANGLE_RANK
Definition: LASFields.h:45
@ LAS_EXTRA
Definition: LASFields.h:52
@ LAS_CLASSIF_WITHHELD
Definition: LASFields.h:57
@ LAS_BLUE
Definition: LASFields.h:50
@ LAS_X
Definition: LASFields.h:36
@ LAS_GREEN
Definition: LASFields.h:49
@ LAS_RED
Definition: LASFields.h:48
@ LAS_CLASSIF_SYNTHETIC
Definition: LASFields.h:55
@ LAS_USER_DATA
Definition: LASFields.h:46
@ LAS_CLASSIFICATION
Definition: LASFields.h:44
@ LAS_INTENSITY
Definition: LASFields.h:39
static const char LAS_SCALE_Z_META_DATA[]
Definition: LASFields.h:25
static const char LAS_SCALE_Y_META_DATA[]
Definition: LASFields.h:24
static const char LAS_OFFSET_Z_META_DATA[]
Definition: LASFields.h:28
static const char LAS_POINT_FORMAT_META_DATA[]
Definition: LASFields.h:31
static const char LAS_GLOBAL_ENCODING_META_DATA[]
Definition: LASFields.h:32
static const char LAS_VERSION_MINOR_META_DATA[]
Definition: LASFields.h:30
QSharedPointer< LASOpenDlg > s_lasOpenDlg(nullptr)
QSharedPointer< LASSaveDlg > s_saveDlg(nullptr)
Semi persistent save dialog.
pdal::Dimension::Id typeToId(LAS_FIELDS sfType, uint8_t pointFormat)
Definition: LASFilter.cpp:152
static const char s_LAS_SRS_Key[]
Definition: LASFilter.cpp:62
void * X
Definition: SmallVector.cpp:45
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
Generic file I/O filter.
Definition: FileIOFilter.h:46
static constexpr float DEFAULT_PRIORITY
Definition: FileIOFilter.h:313
static bool HandleGlobalShift(const CCVector3d &P, CCVector3d &Pshift, bool &preserveCoordinateShift, LoadParameters &loadParameters, bool useInputCoordinatesShiftIfPossible=false)
Shortcut to the ecvGlobalShiftManager mechanism specific for files.
bool canSave(CV_CLASS_ENUM type, bool &multiple, bool &exclusive) const override
Returns whether this I/O filter can save the specified type of entity.
Definition: LASFilter.cpp:139
CC_FILE_ERROR saveToFile(ccHObject *entity, const QString &filename, const SaveParameters &parameters) override
Saves an entity (or a group of) to a file.
Definition: LASFilter.cpp:208
CC_FILE_ERROR loadFile(const QString &filename, ccHObject &container, LoadParameters &parameters) override
Loads one or more entities from a file.
Definition: LASFilter.cpp:982
Dialog to choose the LAS fields to load.
Definition: LASOpenDlg.h:25
LAS Save dialog.
LASSaveDlg(QWidget *parent=nullptr)
Definition: LASFilter.cpp:101
void addEVLR(const QString &description)
Definition: LASFilter.cpp:113
bool doSaveEVLR(size_t index) const
Definition: LASFilter.cpp:123
void clearEVLRs()
Definition: LASFilter.cpp:107
Class describing the current tiling process.
Definition: LASFilter.cpp:731
std::vector< PointViewPtr > tilePointViews
Definition: LASFilter.cpp:831
CCVector3d bbMinCorner
Definition: LASFilter.cpp:830
unsigned Z
Definition: LASFilter.cpp:829
~Tiler()=default
std::vector< QString > fileNames
Definition: LASFilter.cpp:832
void addPoint(const PointViewPtr &buffer, unsigned pointIndex)
Definition: LASFilter.cpp:789
bool init(unsigned width, unsigned height, unsigned Zdim, const QString &absoluteBaseFilename, const CCVector3d &bbMin, const CCVector3d &bbMax, PointTableRef table, const LasHeader &header)
Definition: LASFilter.cpp:739
unsigned h
Definition: LASFilter.cpp:828
CCVector3d tileDiag
Definition: LASFilter.cpp:830
unsigned Y
Definition: LASFilter.cpp:829
unsigned X
Definition: LASFilter.cpp:829
void writeAll()
Definition: LASFilter.cpp:804
unsigned index(unsigned i, unsigned j) const
Definition: LASFilter.cpp:826
size_t tileCount() const
Definition: LASFilter.cpp:737
unsigned w
Definition: LASFilter.cpp:828
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
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
static ccColorScale::Shared GetDefaultScale(DEFAULT_SCALES scale=BGYR)
Returns a pre-defined color scale (static shortcut)
virtual bool hasColors() const
Returns whether colors are enabled or not.
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual const ecvColor::Rgb & getPointColor(unsigned pointIndex) const =0
Returns color corresponding to a given point.
static ccGenericPointCloud * ToGenericPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccGenericPointCloud.
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
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.
bool hasMetaData(const QString &key) const
Returns whether a meta-data element with the given key exists or not.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
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.
bool hasDisplayedScalarField() const override
Returns whether an active scalar field is available or not.
bool reserveTheRGBTable()
Reserves memory to store the RGB colors.
bool hasColors() const override
Returns whether colors are enabled or not.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
void addRGBColor(const ecvColor::Rgb &C)
Pushes an RGB color on stack.
bool reserveThePointsTable(unsigned _numberOfPoints)
Reserves memory to store the points coordinates.
A scalar field associated to display-related parameters.
bool getOwnGlobalBB(CCVector3d &minCorner, CCVector3d &maxCorner) override
CCVector3d toGlobal3d(const Vector3Tpl< T > &Plocal) const
Returns the point back-projected into the original coordinates system.
bool isShifted() const
Returns whether the cloud is shifted or not.
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 unsigned size() const =0
Returns the number of points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
bool oneStep()
Increments total progress value of a single unit.
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
unsigned capacity() const
Returns cloud capacity (i.e. reserved size)
const char * getName() const
Returns scalar field name.
Definition: ScalarField.h:43
RGB color structure.
Definition: ecvColorTypes.h:49
static bool NeedShift(const CCVector3d &P)
Returns whether a particular point (coordinates) is too big or not.
Mode
Strategy to handle coordinates shift/scale.
Graphical progress indicator (thread-safe)
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
Q_DECLARE_METATYPE(ccPclPluginInterface *)
const unsigned CC_MAX_NUMBER_OF_POINTS_PER_CLOUD
@ POINT_CLOUD
Definition: CVTypes.h:104
constexpr const char * ScanDirectionFlag
Definition: LasDetails.h:62
constexpr const char * EdgeOfFlightLine
Definition: LasDetails.h:63
constexpr const char * NumberOfReturns
Definition: LasDetails.h:61
constexpr const char * UserData
Definition: LasDetails.h:69
constexpr const char * Classification
Definition: LasDetails.h:64
constexpr const char * ReturnNumber
Definition: LasDetails.h:60
constexpr const char * ScanAngleRank
Definition: LasDetails.h:68
constexpr const char * GpsTime
Definition: LasDetails.h:71
constexpr const char * Intensity
Definition: LasDetails.h:59
constexpr const char * PointSourceId
Definition: LasDetails.h:70
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
std::vector< std::string > StringList
Definition: Common.h:193
constexpr Rgb black(0, 0, 0)
constexpr Rgb red(MAX, 0, 0)
constexpr Rgb blue(0, 0, MAX)
constexpr Rgb green(0, MAX, 0)
cloudViewer::NormalizedProgress * nProgress
#define fileChunkSize(nChunkSize)
Definition: sqlite3.c:96908
std::string name
Definition: LASFilter.cpp:978
pdal::Dimension::Type dimType
Definition: LASFilter.cpp:979
Custom ("Extra bytes") field (EVLR)
QSharedPointer< ExtraLasField > Shared
Definition: LASFilter.cpp:88
ExtraLasField(QString name, Id id, double defaultVal=0.0, double min=0.0, double max=-1.0)
Default constructor.
Definition: LASFilter.cpp:70
double scale
Definition: LASFilter.cpp:94
QString getName() const override
Returns official field name.
Definition: LASFilter.cpp:90
double offset
Definition: LASFilter.cpp:95
Generic loading parameters.
Definition: FileIOFilter.h:51
ecvGlobalShiftManager::Mode shiftHandlingMode
How to handle big coordinates.
Definition: FileIOFilter.h:64
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:78
bool sessionStart
Session start (whether the load action is the first of a session)
Definition: FileIOFilter.h:80
bool * coordinatesShiftEnabled
Whether shift on load has been applied after loading (optional)
Definition: FileIOFilter.h:69
Generic saving parameters.
Definition: FileIOFilter.h:84
QWidget * parentWidget
Parent widget (if any)
Definition: FileIOFilter.h:93
void createFieldsToLoad(const IdList &extraFieldsToLoad, const StringList &extraNamesToLoad)
Definition: LASFilter.cpp:855
ccPointCloud * getLoadedCloud() const
Definition: LASFilter.cpp:842
std::vector< LasField::Shared > lasFields
Definition: LASFilter.cpp:839
unsigned size
Definition: LASFilter.cpp:840
bool reserveSize(unsigned nbPoints)
Definition: LASFilter.cpp:846
void addLasFieldsToCloud()
Definition: LASFilter.cpp:926
ccPointCloud * loadedCloud
Definition: LASFilter.cpp:838
bool hasColors() const
Definition: LASFilter.cpp:844
LAS field descriptor.
Definition: LASFields.h:89
static QString DesanitizeString(const QString &str)
Definition: LASFields.h:308
static bool GetLASFields(ccPointCloud *cloud, std::vector< LasField > &fieldsToSave, uint8_t &minPointFormat)
Returns the (compliant) LAS fields in a point cloud.
Definition: LASFields.h:113
static uint8_t UpdateMinPointFormat(uint8_t minPointFormat, bool withRGB, bool withFWF, bool allowLegacyFormats=true)
Definition: LASFields.h:233
QSharedPointer< LasField > Shared
Shared type.
Definition: LASFields.h:91
Definition: lsd.c:149