void copyScalarFields(const ccPointCloud *inCloud, ccPointCloud *outCloud, pcl::PointIndicesPtr &in2outMapping, bool overwrite = true) { int n_in = inCloud->size(); int n_out = outCloud->size(); assert(in2outMapping->indices.size() == outCloud->size()); int n_scalars = inCloud->getNumberOfScalarFields(); for (int i = 0; i < n_scalars; ++i) { CCLib::ScalarField * field = inCloud->getScalarField(i); const char * name = field->getName(); //we need to verify no scalar field with the same name exists in the output cloud int id = outCloud->getScalarFieldIndexByName(name); ccScalarField * new_field = new ccScalarField; //resize the scalar field to the outcloud size new_field->reserve(outCloud->size()); new_field->setName(name); if (id >= 0) //a scalar field with the same name exists { if (overwrite) outCloud->deleteScalarField(id); else break; } //now perform point to point copy for (unsigned int j = 0; j < outCloud->size(); ++j) { new_field->setValue(j, field->getValue(in2outMapping->indices.at(j))); } //recompute stats new_field->computeMinAndMax(); ccScalarField * casted_field = static_cast<ccScalarField *> (new_field); casted_field->computeMinAndMax(); //now put back the scalar field to the outCloud if (id < 0) outCloud->addScalarField(casted_field); } }
CC_FILE_ERROR VTKFilter::saveToFile(ccHObject* entity, QString filename, SaveParameters& parameters) { if (!entity || filename.isEmpty()) return CC_FERR_BAD_ARGUMENT; //look for either a cloud or a mesh ccMesh* mesh = ccHObjectCaster::ToMesh(entity); unsigned triCount = 0; ccGenericPointCloud* vertices = 0; if (mesh) { //input entity is a mesh triCount = mesh->size(); if (triCount == 0) { ccLog::Warning("[VTK] Input mesh has no triangle?!"); return CC_FERR_NO_SAVE; } vertices = mesh->getAssociatedCloud(); } else { //no mesh? maybe the input entity is a cloud? vertices = ccHObjectCaster::ToGenericPointCloud(entity); } //in any case, we must have a valid 'vertices' entity now if (!vertices) { ccLog::Warning("[VTK] No point cloud nor mesh in input selection!"); return CC_FERR_BAD_ENTITY_TYPE; } unsigned ptsCount = vertices->size(); if (!ptsCount) { ccLog::Warning("[VTK] No point/vertex to save?!"); return CC_FERR_NO_SAVE; } //open ASCII file for writing QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return CC_FERR_WRITING; QTextStream outFile(&file); outFile.setRealNumberPrecision(sizeof(PointCoordinateType) == 4 ? 8 : 12); //write header outFile << "# vtk DataFile Version 3.0" << endl; outFile << "vtk output" << endl; outFile << "ASCII" << endl; outFile << "DATASET " << (mesh ? "POLYDATA" : "UNSTRUCTURED_GRID") << endl; //data type QString floatType = (sizeof(PointCoordinateType) == 4 ? "float" : "double"); /*** what shall we save now? ***/ // write the points { outFile << "POINTS " << ptsCount << " " << floatType << endl; for (unsigned i=0; i<ptsCount; ++i) { const CCVector3* P = vertices->getPoint(i); CCVector3d Pglobal = vertices->toGlobal3d<PointCoordinateType>(*P); outFile << Pglobal.x << " " << Pglobal.y << " " << Pglobal.z << endl; } } // write triangles if (mesh) { outFile << "POLYGONS " << triCount << " " << 4*triCount << endl; mesh->placeIteratorAtBegining(); for (unsigned i=0; i<triCount; ++i) { const CCLib::VerticesIndexes* tsi = mesh->getNextTriangleVertIndexes(); //DGM: getNextTriangleVertIndexes is faster for mesh groups! outFile << "3 " << tsi->i1 << " " << tsi->i2 << " " << tsi->i3 << endl; } } else { // write cell data outFile << "CELLS " << ptsCount << " " << 2*ptsCount << endl; for (unsigned i=0; i<ptsCount; ++i) outFile << "1 " << i << endl; outFile << "CELL_TYPES " << ptsCount << endl; for (unsigned i=0; i<ptsCount; ++i) outFile << "1 " << endl; } outFile << "POINT_DATA " << ptsCount << endl; // write normals if (vertices->hasNormals()) { outFile << "NORMALS Normals "<< floatType << endl; for (unsigned i=0; i<ptsCount; ++i) { const CCVector3& N = vertices->getPointNormal(i); outFile << N.x << " " << N.y << " " << N.z << endl; } } // write colors if (vertices->hasColors()) { outFile << "COLOR_SCALARS RGB 3" << endl; for (unsigned i=0; i<ptsCount; ++i) { const colorType* C = vertices->getPointColor(i); outFile << static_cast<float>(C[0])/ccColor::MAX << " " << static_cast<float>(C[1])/ccColor::MAX << " " << static_cast<float>(C[2])/ccColor::MAX << endl; } } // write scalar field(s)? if (vertices->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pointCloud = static_cast<ccPointCloud*>(vertices); unsigned sfCount = pointCloud->getNumberOfScalarFields(); for (unsigned i=0;i<sfCount;++i) { CCLib::ScalarField* sf = pointCloud->getScalarField(i); outFile << "SCALARS " << QString(sf->getName()).replace(" ","_") << (sizeof(ScalarType)==4 ? " float" : " double") << " 1" << endl; outFile << "LOOKUP_TABLE default" << endl; for (unsigned j=0;j<ptsCount; ++j) outFile << sf->getValue(j) << endl; } } else //virtual point cloud, we only have access to its currently displayed scalar field { if (vertices->hasScalarFields()) { outFile << "SCALARS ScalarField" << (sizeof(ScalarType)==4 ? " float" : " double") << " 1" << endl; outFile << "LOOKUP_TABLE default" << endl; for (unsigned j=0;j<ptsCount; ++j) outFile << vertices->getPointDisplayedDistance(j) << endl; } } file.close(); return CC_FERR_NO_ERROR; }
CC_FILE_ERROR VTKFilter::saveToFile(ccHObject* entity, const char* filename) { if (!entity || !filename) return CC_FERR_BAD_ARGUMENT; //look for either a cloud or a mesh ccHObject::Container clouds,meshes; if (entity->isA(CC_TYPES::POINT_CLOUD)) clouds.push_back(entity); else if (entity->isKindOf(CC_TYPES::MESH)) meshes.push_back(entity); else //group? { for (unsigned i=0; i<entity->getChildrenNumber(); ++i) { ccHObject* child = entity->getChild(i); if (child->isKindOf(CC_TYPES::POINT_CLOUD)) clouds.push_back(child); else if (child->isKindOf(CC_TYPES::MESH)) meshes.push_back(child); } } if (clouds.empty() && meshes.empty()) { ccLog::Error("No point cloud nor mesh in input selection!"); return CC_FERR_BAD_ENTITY_TYPE; } else if (clouds.size()+meshes.size()>1) { ccLog::Error("Can't save more than one entity per VTK file!"); return CC_FERR_BAD_ENTITY_TYPE; } //the cloud to save ccGenericPointCloud* vertices = 0; ccMesh* mesh = 0; unsigned triCount = 0; if (!clouds.empty()) //1 cloud, no mesh { vertices = ccHObjectCaster::ToGenericPointCloud(clouds[0]); } else //1 mesh, with vertices as cloud { mesh = static_cast<ccMesh*>(meshes[0]); triCount = mesh->size(); if (triCount == 0) { ccLog::Error("Mesh has no triangle?!"); return CC_FERR_NO_SAVE; } vertices = mesh->getAssociatedCloud(); } assert(vertices); unsigned ptsCount = vertices->size(); if (!ptsCount) { ccLog::Error("No point/vertex to save?!"); return CC_FERR_NO_SAVE; } //open ASCII file for writing QFile file(filename); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) return CC_FERR_WRITING; QTextStream outFile(&file); outFile.setRealNumberPrecision(sizeof(PointCoordinateType) == 4 ? 8 : 12); //write header outFile << "# vtk DataFile Version 3.0" << endl; outFile << "vtk output" << endl; outFile << "ASCII" << endl; outFile << "DATASET " << (mesh ? "POLYDATA" : "UNSTRUCTURED_GRID") << endl; //data type QString floatType = (sizeof(PointCoordinateType) == 4 ? "float" : "double"); /*** what shall we save now? ***/ // write the points { outFile << "POINTS " << ptsCount << " " << floatType << endl; for (unsigned i=0; i<ptsCount; ++i) { const CCVector3* P = vertices->getPoint(i); CCVector3d Pglobal = vertices->toGlobal3d<PointCoordinateType>(*P); outFile << Pglobal.x << " " << Pglobal.y << " " << Pglobal.z << endl; } } // write triangles if (mesh) { outFile << "POLYGONS " << triCount << " " << 4*triCount << endl; mesh->placeIteratorAtBegining(); for (unsigned i=0; i<triCount; ++i) { const CCLib::TriangleSummitsIndexes* tsi = mesh->getNextTriangleIndexes(); //DGM: getNextTriangleIndexes is faster for mesh groups! outFile << "3 " << tsi->i1 << " " << tsi->i2 << " " << tsi->i3 << endl; } } else { // write cell data outFile << "CELLS " << ptsCount << " " << 2*ptsCount << endl; for (unsigned i=0; i<ptsCount; ++i) outFile << "1 " << i << endl; outFile << "CELL_TYPES " << ptsCount << endl; for (unsigned i=0; i<ptsCount; ++i) outFile << "1 " << endl; } outFile << "POINT_DATA " << ptsCount << endl; // write normals if (vertices->hasNormals()) { outFile << "NORMALS Normals "<< floatType << endl; for (unsigned i=0; i<ptsCount; ++i) { const CCVector3& N = vertices->getPointNormal(i); outFile << N.x << " " << N.y << " " << N.z << endl; } } // write colors if (vertices->hasColors()) { outFile << "COLOR_SCALARS RGB 3" << endl; for (unsigned i=0; i<ptsCount; ++i) { const colorType* C = vertices->getPointColor(i); outFile << (float)C[0]/(float)MAX_COLOR_COMP << " " << (float)C[1]/(float)MAX_COLOR_COMP << " " << (float)C[2]/(float)MAX_COLOR_COMP << endl; } } // write scalar field(s)? if (vertices->isA(CC_TYPES::POINT_CLOUD)) { ccPointCloud* pointCloud = static_cast<ccPointCloud*>(vertices); unsigned sfCount = pointCloud->getNumberOfScalarFields(); for (unsigned i=0;i<sfCount;++i) { CCLib::ScalarField* sf = pointCloud->getScalarField(i); outFile << "SCALARS " << QString(sf->getName()).replace(" ","_") << (sizeof(ScalarType)==4 ? " float" : " double") << " 1" << endl; outFile << "LOOKUP_TABLE default" << endl; for (unsigned j=0;j<ptsCount; ++j) outFile << sf->getValue(j) << endl; } } else //virtual point cloud, we only have access to its currently displayed scalar field { if (vertices->hasScalarFields()) { outFile << "SCALARS ScalarField" << (sizeof(ScalarType)==4 ? " float" : " double") << " 1" << endl; outFile << "LOOKUP_TABLE default" << endl; for (unsigned j=0;j<ptsCount; ++j) outFile << vertices->getPointDisplayedDistance(j) << endl; } } file.close(); return CC_FERR_NO_ERROR; }
void ccRasterizeTool::generateRaster() const { #ifdef CC_GDAL_SUPPORT if (!m_cloud || !m_grid.isValid()) return; GDALAllRegister(); ccLog::PrintDebug("(GDAL drivers: %i)", GetGDALDriverManager()->GetDriverCount()); const char *pszFormat = "GTiff"; GDALDriver *poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat); if (!poDriver) { ccLog::Error("[GDAL] Driver %s is not supported", pszFormat); return; } char** papszMetadata = poDriver->GetMetadata(); if( !CSLFetchBoolean( papszMetadata, GDAL_DCAP_CREATE, FALSE ) ) { ccLog::Error("[GDAL] Driver %s doesn't support Create() method", pszFormat); return; } //which (and how many) bands shall we create? bool heightBand = true; //height by default bool densityBand = false; bool allSFBands = false; int sfBandIndex = -1; //scalar field index int totalBands = 0; bool interpolateSF = (getTypeOfSFInterpolation() != INVALID_PROJECTION_TYPE); ccPointCloud* pc = m_cloud->isA(CC_TYPES::POINT_CLOUD) ? static_cast<ccPointCloud*>(m_cloud) : 0; bool hasSF = interpolateSF && pc && !m_grid.scalarFields.empty(); RasterExportOptionsDlg reoDlg; reoDlg.dimensionsLabel->setText(QString("%1 x %2").arg(m_grid.width).arg(m_grid.height)); reoDlg.exportHeightsCheckBox->setChecked(heightBand); reoDlg.exportDensityCheckBox->setChecked(densityBand); reoDlg.exportDisplayedSFCheckBox->setEnabled(hasSF); reoDlg.exportAllSFCheckBox->setEnabled(hasSF); reoDlg.exportAllSFCheckBox->setChecked(allSFBands); if (!reoDlg.exec()) return; //we ask the output filename AFTER displaying the export parameters ;) QString outputFilename; { QSettings settings; settings.beginGroup(ccPS::HeightGridGeneration()); QString imageSavePath = settings.value("savePathImage",QApplication::applicationDirPath()).toString(); outputFilename = QFileDialog::getSaveFileName(0,"Save height grid raster",imageSavePath+QString("/raster.tif"),"geotiff (*.tif)"); if (outputFilename.isNull()) return; //save current export path to persistent settings settings.setValue("savePathImage",QFileInfo(outputFilename).absolutePath()); } heightBand = reoDlg.exportHeightsCheckBox->isChecked(); densityBand = reoDlg.exportDensityCheckBox->isChecked(); if (hasSF) { assert(pc); allSFBands = reoDlg.exportAllSFCheckBox->isChecked() && hasSF; if (!allSFBands && reoDlg.exportDisplayedSFCheckBox->isChecked()) { sfBandIndex = pc->getCurrentDisplayedScalarFieldIndex(); if (sfBandIndex < 0) ccLog::Warning("[Rasterize] Cloud has no active (displayed) SF!"); } } totalBands = heightBand ? 1 : 0; if (densityBand) { ++totalBands; } if (allSFBands) { assert(hasSF); for (size_t i=0; i<m_grid.scalarFields.size(); ++i) if (m_grid.scalarFields[i]) ++totalBands; } else if (sfBandIndex >= 0) { ++totalBands; } if (totalBands == 0) { ccLog::Warning("[Rasterize] Warning, can't output a raster with no band! (check export parameters)"); return; } //data type GDALDataType dataType = (std::max(sizeof(PointCoordinateType),sizeof(ScalarType)) > 4 ? GDT_Float64 : GDT_Float32); char **papszOptions = NULL; GDALDataset* poDstDS = poDriver->Create(qPrintable(outputFilename), static_cast<int>(m_grid.width), static_cast<int>(m_grid.height), totalBands, dataType, papszOptions); if (!poDstDS) { ccLog::Error("[GDAL] Failed to create output raster (not enough memory?)"); return; } ccBBox box = getCustomBBox(); assert(box.isValid()); //vertical dimension const unsigned char Z = getProjectionDimension(); assert(Z >= 0 && Z <= 2); const unsigned char X = Z == 2 ? 0 : Z +1; const unsigned char Y = X == 2 ? 0 : X +1; double shiftX = box.minCorner().u[X]; double shiftY = box.minCorner().u[Y]; double stepX = m_grid.gridStep; double stepY = m_grid.gridStep; if (pc) { const CCVector3d& shift = pc->getGlobalShift(); shiftX -= shift.u[X]; shiftY -= shift.u[Y]; double scale = pc->getGlobalScale(); assert(scale != 0); stepX /= scale; stepY /= scale; } double adfGeoTransform[6] = { shiftX, //top left x stepX, //w-e pixel resolution (can be negative) 0, //0 shiftY, //top left y 0, //0 stepY //n-s pixel resolution (can be negative) }; poDstDS->SetGeoTransform( adfGeoTransform ); //OGRSpatialReference oSRS; //oSRS.SetUTM( 11, TRUE ); //oSRS.SetWellKnownGeogCS( "NAD27" ); //char *pszSRS_WKT = NULL; //oSRS.exportToWkt( &pszSRS_WKT ); //poDstDS->SetProjection( pszSRS_WKT ); //CPLFree( pszSRS_WKT ); double* scanline = (double*) CPLMalloc(sizeof(double)*m_grid.width); int currentBand = 0; //exort height band? if (heightBand) { GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand); assert(poBand); poBand->SetColorInterpretation(GCI_Undefined); EmptyCellFillOption fillEmptyCellsStrategy = getFillEmptyCellsStrategy(fillEmptyCellsComboBox); double emptyCellHeight = 0; switch (fillEmptyCellsStrategy) { case LEAVE_EMPTY: emptyCellHeight = m_grid.minHeight-1.0; poBand->SetNoDataValue(emptyCellHeight); //should be transparent! break; case FILL_MINIMUM_HEIGHT: emptyCellHeight = m_grid.minHeight; break; case FILL_MAXIMUM_HEIGHT: emptyCellHeight = m_grid.maxHeight; break; case FILL_CUSTOM_HEIGHT: emptyCellHeight = getCustomHeightForEmptyCells(); break; case FILL_AVERAGE_HEIGHT: emptyCellHeight = m_grid.meanHeight; break; default: assert(false); } for (unsigned j=0; j<m_grid.height; ++j) { const RasterCell* aCell = m_grid.data[j]; for (unsigned i=0; i<m_grid.width; ++i,++aCell) { scanline[i] = aCell->h == aCell->h ? aCell->h : emptyCellHeight; } if (poBand->RasterIO( GF_Write, 0, static_cast<int>(j), static_cast<int>(m_grid.width), 1, scanline, static_cast<int>(m_grid.width), 1, GDT_Float64, 0, 0 ) != CE_None) { ccLog::Error("[GDAL] An error occurred while writing the height band!"); if (scanline) CPLFree(scanline); GDALClose( (GDALDatasetH) poDstDS ); return; } } } //export density band if (densityBand) { GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand); assert(poBand); poBand->SetColorInterpretation(GCI_Undefined); for (unsigned j=0; j<m_grid.height; ++j) { const RasterCell* aCell = m_grid.data[j]; for (unsigned i=0; i<m_grid.width; ++i,++aCell) { scanline[i] = aCell->nbPoints; } if (poBand->RasterIO( GF_Write, 0, static_cast<int>(j), static_cast<int>(m_grid.width), 1, scanline, static_cast<int>(m_grid.width), 1, GDT_Float64, 0, 0 ) != CE_None) { ccLog::Error("[GDAL] An error occurred while writing the height band!"); if (scanline) CPLFree(scanline); GDALClose( (GDALDatasetH) poDstDS ); return; } } } //export SF bands if (allSFBands || sfBandIndex >= 0) { for (size_t k=0; k<m_grid.scalarFields.size(); ++k) { double* _sfGrid = m_grid.scalarFields[k]; if (_sfGrid && (allSFBands || sfBandIndex == static_cast<int>(k))) //valid SF grid { GDALRasterBand* poBand = poDstDS->GetRasterBand(++currentBand); double sfNanValue = static_cast<double>(CCLib::ScalarField::NaN()); poBand->SetNoDataValue(sfNanValue); //should be transparent! assert(poBand); poBand->SetColorInterpretation(GCI_Undefined); for (unsigned j=0; j<m_grid.height; ++j) { const RasterCell* aCell = m_grid.data[j]; for (unsigned i=0; i<m_grid.width; ++i,++_sfGrid,++aCell) { scanline[i] = aCell->nbPoints ? *_sfGrid : sfNanValue; } if (poBand->RasterIO( GF_Write, 0, static_cast<int>(j), static_cast<int>(m_grid.width), 1, scanline, static_cast<int>(m_grid.width), 1, GDT_Float64, 0, 0 ) != CE_None) { //the corresponding SF should exist on the input cloud CCLib::ScalarField* formerSf = pc->getScalarField(static_cast<int>(k)); assert(formerSf); ccLog::Error(QString("[GDAL] An error occurred while writing the '%1' scalar field band!").arg(formerSf->getName())); k = m_grid.scalarFields.size(); //quick stop break; } } } } } if (scanline) CPLFree(scanline); scanline = 0; /* Once we're done, close properly the dataset */ GDALClose( (GDALDatasetH) poDstDS ); ccLog::Print(QString("[Rasterize] Raster '%1' succesfully saved").arg(outputFilename)); #else assert(false); ccLog::Error("[Rasterize] GDAL not supported by this version! Can't generate a raster..."); #endif }