/* ---- This file is part of SECONDO. Copyright (C) 2017, University in Hagen, Department of Computer Science, Database Systems for New Applications. SECONDO is free software{} you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation{} either version 2 of the License, or (at your option) any later version. SECONDO is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY{} without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with SECONDO{} if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ---- */ #ifndef SECONDO_IMPORTPOINTCLOUD_H #define SECONDO_IMPORTPOINTCLOUD_H #include "Algebra.h" #include "NestedList.h" #include "QueryProcessor.h" #include "AlgebraManager.h" #include "StandardTypes.h" #include "Symbols.h" #include "Tools/Flob/DbArray.h" #include "Stream.h" #include "ListUtils.h" #include #include #include #include #include #include #include #include #include #include "PointCloud.h" #include "lasreader/lasreader.h" #include "lasreader/laspoint.h" namespace pointcloud { /* ~importpointcloud~ operator Imports LAS point data into stream(pointcloud) {string, text} -> stream(pointcloud) Added and maintained by Gundula Swidersky, Dec 2017 */ class ImportPointCloud { public: /* Type Mapping */ static ListExpr importpointcloudTM(ListExpr args); /* Value Mapping */ static int importpointcloudVM(Word *args, Word &result, int message, Word &local, Supplier s); /* Local Info Class */ template class importpointcloudLI { public: importpointcloudLI(T* arg) { pointCloudArray = 0; std::string lasFileDir = ""; if(arg->IsDefined()) { lasFileDir = arg->GetValue(); } // Initialization of vars lasFileList.clear(); maxNoPoints = 2400; // amount of points calc per PC for grid maxPointsPC = 2499; // max 2500 cell per stored PC noPCElems = 0; noGridCells = 0; // initialize the files to be read readLasFileNames(lasFileDir); } ~importpointcloudLI() { if(pointCloudArray) delete[] pointCloudArray; while(!resultList.empty()){ PointCloud* pc = resultList.front(); resultList.pop(); pc->DestroyPointCloud(); delete pc; } } /* Read lasFiles names in provided directory and build array with filenames of lasFiles to be worked through later on demand. */ void readLasFileNames(const std::string &lasDir) { cout << "Las files to be processed:" << endl; if (lasDir.substr((lasDir.length() -1), 1) != "/") { // file name provided lasFileList.push_front(lasDir); } else { if (DIR *dir = opendir(lasDir.c_str())) { while (struct dirent *entry = readdir(dir)) { if (strcmp(entry->d_name, ".") == 0) { continue; } if (strcmp(entry->d_name, "..") == 0) { continue; } if ((entry->d_type & DT_DIR) == DT_DIR && !((entry->d_type & DT_LNK) == DT_LNK)) { continue; } if ((entry->d_type & DT_REG) == DT_REG) { std::string lasFilNam = lasDir + entry->d_name; cout << lasFilNam << endl; lasFileList.push_front(lasFilNam); } } closedir(dir); } } } // implementation using a lasreader int readLasFile1(const std::string& fileName){ fError = false; pError = false; // test output las file name to be read cout << endl; cout << "Processing las file : " << fileName << endl; lasreader reader(fileName); if(!reader.isOk()){ fError = true; std::cerr << "Error in reading global file information" << std::endl; return -2; } fNumPointRecs = reader.getNumPoints(); reader.getBox(fMinX, fMinY, fMinZ, fMaxX, fMaxY, fMaxZ); // create Arrays to store point data initPointCloudArray(); double minXArray[noGridCells]; double maxXArray[noGridCells]; double minYArray[noGridCells]; double maxYArray[noGridCells]; maxX = fMaxX; minX = fMinX; maxY = fMaxY; minY = fMinY; maxZ = fMaxZ; minZ = fMinZ; cout << std::setprecision(14); cout << "Absolute Min Point (" << minX << ", " << minY << ", " << minZ << ")" << endl; cout << "Absolute Max Point (" << maxX << ", " << maxY << ", " << maxZ << ")" << endl; lasPoint* point; size_t icount = 0; while( (point = reader.next())!=0){ if (calcPointData(icount, point, reader, minXArray, maxXArray, minYArray, maxYArray) == -1) { // unexpected error during calculation // wenn return code eq to -2 just ignore the current // point and continue reading next point return 0; } delete point; icount++; } cout << "Number of points read: " << icount - 1 << endl; // Set Min Max values of PC setMinMaxValues(minXArray, maxXArray, minYArray, maxYArray); return 0; } int readLasFile(const std::string& laspcFileName) { fError = false; pError = false; // test output las file name to be read cout << endl; cout << "Processing las file : " << laspcFileName << endl; std::ifstream lasFile(laspcFileName.c_str(), std::ios::in|std::ios::binary); if (!lasFile) { // handle error fError = true; cout << "Error opening lasfile." << endl; return -2; } // read Header data lasFile.read(reinterpret_cast(&fSignature), sizeof(fSignature)); sSignature = std::string(fSignature,4); if (sSignature != "LASF") { // Error Handling cout << "Error: 'LASF' Signature missing." << "Not a valid lasfile???" << endl; fError = true; return -2; } cout << "Signature (LASF): " << sSignature << endl; uint16_t fileSourceId; lasFile.read(reinterpret_cast(&fileSourceId), 2); cout << "fileSourceId is " << fileSourceId << endl; uint16_t globalEncoding; lasFile.read(reinterpret_cast(&globalEncoding),2); // ignore project ids lasFile.seekg(static_cast(24), std::ios::beg); lasFile.read(reinterpret_cast(&fVerMajor), 1); lasFile.read(reinterpret_cast(&fVerMinor), 1); unsigned int sVerMajor = (unsigned int) fVerMajor; unsigned int sVerMinor = (unsigned int) fVerMinor; cout << "Version: " << sVerMajor << "." << sVerMinor << endl; cout << "globalEncoding" << globalEncoding << endl; char systemIdentifier[32]; char generatingSoftware[32]; uint16_t dayOfYear; uint16_t year; uint16_t headerSize; lasFile.read(systemIdentifier,32); lasFile.read(generatingSoftware,32); lasFile.read(reinterpret_cast(&dayOfYear),2); lasFile.read(reinterpret_cast(&year),2); lasFile.read(reinterpret_cast(&headerSize),2); cout << "system identifier " << systemIdentifier << endl; cout << "generatingSoftware " << generatingSoftware << endl; cout << "&dayOfYear " << dayOfYear << endl; cout << "year " << year << endl; cout << "headersize " << headerSize << endl; lasFile.read(reinterpret_cast (&fOffsetToPointData), 4); cout << "Offset to point data: " << fOffsetToPointData << endl; lasFile.seekg(static_cast(5), std::ios::cur); lasFile.read(reinterpret_cast (&fPointDataRecLength), 2); cout << "Point data record length: " << fPointDataRecLength << endl; lasFile.read(reinterpret_cast (&fNumPointRecs), 4); cout << "No of point records: " << fNumPointRecs << endl; cellOffsetX = (fMaxX - fMinX) / noGridCellsSide; lasFile.seekg(static_cast(20), std::ios::cur); lasFile.read(reinterpret_cast(&fScaleX), 8); lasFile.read(reinterpret_cast(&fScaleY), 8); lasFile.read(reinterpret_cast(&fScaleZ), 8); lasFile.read(reinterpret_cast(&fOffsetX), 8); lasFile.read(reinterpret_cast(&fOffsetY), 8); lasFile.read(reinterpret_cast(&fOffsetZ), 8); lasFile.read(reinterpret_cast(&fMaxX), 8); lasFile.read(reinterpret_cast(&fMinX), 8); lasFile.read(reinterpret_cast(&fMaxY), 8); lasFile.read(reinterpret_cast(&fMinY), 8); lasFile.read(reinterpret_cast(&fMaxZ), 8); lasFile.read(reinterpret_cast(&fMinZ), 8); // min and max values are already absolute values. // So do not calculate scale and offset here // create Arrays to store point data initPointCloudArray(); double minXArray[noGridCells]; double maxXArray[noGridCells]; double minYArray[noGridCells]; double maxYArray[noGridCells]; // Output some status information during run cout << "noGridCellsSide: " << noGridCellsSide << endl; cout << "cellOffsetX: " << cellOffsetX << endl; cout << "cellOffsetY: " << cellOffsetY << endl; cout << "noGridCells: " << noGridCells << endl; maxX = fMaxX; minX = fMinX; maxY = fMaxY; minY = fMinY; maxZ = fMaxZ; minZ = fMinZ; cout << std::setprecision(14); cout << "Absolute Min Point (" << minX << ", " << minY << ", " << minZ << ")" << endl; cout << "Absolute Max Point (" << maxX << ", " << maxY << ", " << maxZ << ")" << endl; // read point data and build 2D Tree lasFile.seekg(static_cast (fOffsetToPointData), std::ios::beg); maxRead = fNumPointRecs; long icount = 1; while ((icount <= maxRead) && (!lasFile.eof())) { lasFile.read(reinterpret_cast(&fPointX), 4); lasFile.read(reinterpret_cast(&fPointY), 4); lasFile.read(reinterpret_cast(&fPointZ), 4); // lasFile.read(reinterpret_cast(&fIntensity), 2); if (!lasFile.eof()) { lasFile.seekg(static_cast (fPointDataRecLength-12), std::ios::cur); } if (calcPointData(icount, minXArray, maxXArray, minYArray, maxYArray) == -1) { // unexpected error during calculation // wenn return code eq to -2 just ignore the current // point and continue reading next point return 0; } icount++; } cout << "Number of points read: " << icount - 1 << endl; // Set Min Max values of PC setMinMaxValues(minXArray, maxXArray, minYArray, maxYArray); if (lasFile) { lasFile.close(); } if ((pError) || ((icount - 1) < fNumPointRecs)) { cout << "Error with point data in las file: " << laspcFileName << endl; } if (fError) { cout << "Error processing file: " << laspcFileName << endl; return 0; } return 0; } /* Help method to read and calculate point data */ int calcPointData(size_t icount, lasPoint* point, lasreader& reader, double* minXArray, double* maxXArray, double* minYArray, double* maxYArray) { double x,y,z; bool valid; if(!reader.toLatLon(point,x,y,z,valid)){ return -1; } if(!valid) return -2; return insertPointData(icount,x, y, z, minXArray, maxXArray, minYArray, maxYArray); } int calcPointData(long icount, double* minXArray, double* maxXArray, double* minYArray, double* maxYArray) { // calculate absolute value (scale and offset) pointX = (fPointX * fScaleX) + fOffsetX; pointY = (fPointY * fScaleY) + fOffsetY; pointZ = (fPointZ * fScaleZ) + fOffsetZ; return insertPointData(icount,pointX, pointY, pointZ, minXArray, maxXArray, minYArray, maxYArray); } int insertPointData(size_t icount, double& pointX, double& pointY, double& pointZ, double* minXArray, double* maxXArray, double* minYArray, double* maxYArray){ cellNoX = (long) (1.0 + (pointX - minX) / cellOffsetX); if (cellNoX > noGridCellsSide) { if (cellNoX > (noGridCellsSide + 1)) { pError = true; cout << "No " << icount << " Error: point value not within min" << " max area (" << pointX << ", " << pointY << ", " << pointZ << ")" << " Will continue with reading next point..." << endl; return -2; } cellNoX = noGridCellsSide; } cellNoY = (long) (1.0 + (pointY - minY) / cellOffsetY); if (cellNoY > noGridCellsSide) { if (cellNoY > (noGridCellsSide + 1)) { pError = true; cout << "No " << icount << " Error: point value not within min" << " max area (" << pointX << ", " << pointY << ", " << pointZ << ")" << " Will continue with reading next point..." << endl; return -2; } cellNoY = noGridCellsSide; } cellNo = cellNoX + (noGridCellsSide * (cellNoY - 1)); if ((cellNo < 1) || (cellNo > noGridCells)) { pError = true; cout << "No " << icount << " Error: point value not within min" << " max area (" << pointX << ", " << pointY << ", " << pointZ << ")" << " Will continue with reading next point..." << endl; return -2; } // addCpointnode (pointCloudArray[cellNo -1])->insert(pointX, pointY, pointZ); if ((pointCloudArray[cellNo -1])->GetNoCpointnodes() > 1) { // calculate new min max val for the cell (actual PC) if (pointX < minXArray[cellNo - 1]) { minXArray[cellNo - 1] = pointX; } else { if (pointX > maxXArray[cellNo - 1]) { maxXArray[cellNo - 1] = pointX; } } if (pointY < minYArray[cellNo - 1]) { minYArray[cellNo - 1] = pointY; } else { if (pointY > maxYArray[cellNo - 1]) { maxYArray[cellNo - 1] = pointY; } } } else { // initialize min max with current value minXArray[cellNo - 1] = pointX; maxXArray[cellNo - 1] = pointX; minYArray[cellNo - 1] = pointY; maxYArray[cellNo - 1] = pointY; } return 0; } /* initPointCloudArray PointCloud area was broken into a grid due to the high amount pointdata read from file */ void initPointCloudArray() { assert(!pointCloudArray); noGridCellsSide = (long) (sqrt(fNumPointRecs / maxNoPoints) + 1.0); cellOffsetX = (fMaxX - fMinX) / noGridCellsSide; cellOffsetY = (fMaxY - fMinY) / noGridCellsSide; noGridCells = noGridCellsSide * noGridCellsSide; noPCElems = noGridCells; pointCloudArray = new PointCloud*[noPCElems]; for (int i = 0; i < noPCElems; i++) { pointCloudArray[i] = new PointCloud(0); } } /* setMinMaxValues of PointClouds */ void setMinMaxValues(double* minXArray, double* maxXArray, double* minYArray, double* maxYArray) { int sizePC; for (int i = 0; i < noPCElems; i++) { sizePC = pointCloudArray[i]->GetNoCpointnodes(); if (sizePC > 0) { pointCloudArray[i]->setMinX(minXArray[i]); pointCloudArray[i]->setMinY(minYArray[i]); pointCloudArray[i]->setMaxX(maxXArray[i]); pointCloudArray[i]->setMaxY(maxYArray[i]); } } } /* calcNewMinMax */ void calcNewMinMax(double curX, double curY, double* pcCurMinMax) { // [0] minX, [1] minY, [2] maxX, [3] maxY if (curX < pcCurMinMax[0]) { pcCurMinMax[0] = curX; } else { if (curX > pcCurMinMax[2]) { pcCurMinMax[2] = curX; } } if (curY < pcCurMinMax[1]) { pcCurMinMax[1] = curY; } else { if (curY > pcCurMinMax[3]) { pcCurMinMax[3] = curY; } } } /* getNext Provide next PointCloud object for the stream. */ PointCloud* getNext() { while(true){ if(!resultList.empty()){ PointCloud* result = resultList.front(); resultList.pop(); return result; } if(lasFileList.empty()){ return 0; } std::string fileName = lasFileList.front(); lasFileList.pop_front(); fillResultList(fileName); } } /* Reads in the next file and stores the contained point clouds into result list */ void fillResultList(const std::string& fileName){ if(readLasFile1(fileName) == 0){ // no Error movePCsToResultList(); } else { assert(pointCloudArray==0); } } /* moves non-empty points clouds from point cloud array into the resultList, splits point clouds if necessary */ void movePCsToResultList() { assert(resultList.empty()); for(int i=0;iGetNoCpointnodes(); if(numPoints==0){ // empty point clouds are useless pc->DestroyPointCloud(); delete pc; return; } if(numPoints > maxPointsPC){ // too much points, split required std::vector splitPCs; splitPointCloud(pc,splitPCs); for(size_t i=0;i& result){ assert(result.empty()); std::queue q; q.push(pc); while(!q.empty()){ PointCloud* currentPC = q.front(); q.pop(); size_t numPoints = currentPC->GetNoCpointnodes(); if(numPoints == 0){ // do not include empty pcs currentPC->DestroyPointCloud(); delete currentPC; } else if(numPoints > maxPointsPC) { // split if too large PointCloud* pc1=0; PointCloud* pc2=0; splitSinglePC(currentPC, pc1, pc2); assert(pc1); assert(pc2); currentPC->DestroyPointCloud(); delete currentPC; q.push(pc1); q.push(pc2); } else { // insert if number of points is good result.push_back(currentPC); } } } void splitSinglePC(PointCloud* original, PointCloud*& part1, PointCloud*& part2){ part1 = new PointCloud(0); part2 = new PointCloud(0); // calculate new min max value with split of the longer side double pcMin[2]; double pcMax[2]; pcMin[0] = original->getMinX(); pcMax[0] = original->getMaxX(); pcMin[1] = original->getMinY(); pcMax[1] = original->getMaxY(); bool xDim; // split dimension double min2X; // split value x-direction double min2Y; // split value y-direction if ( (pcMax[0] - pcMin[0]) > (pcMax[1] - pcMin[1]) ) { // split x dimension xDim = true; // cout << "Split x dim "; min2X = (pcMin[0] + (pcMax[0] - pcMin[0]) / 2.0); min2Y = pcMin[1]; } else { // split y dimension xDim = false; // cout << "Split y dim "; min2X = pcMin[0]; min2Y = (pcMin[1] + (pcMax[1] - pcMin[1]) / 2.0); } // Read actual PointCloud and divide into 2 PCs, // No special procedure as every point needs to be read. size_t pcSize = original->GetNoCpointnodes(); Cpointnode pcNode; double pc1MinMax[] = {0,0,0,0}; double pc2MinMax[] = {0,0,0,0}; for (size_t k = 0; k < pcSize; k++) { pcNode = original->GetCpointnode(k); double curX = pcNode.getX(); double curY = pcNode.getY(); double curZ = pcNode.getZ(); // Determine to which PC append the node // and calculate the new min max values. if ( ((xDim) && (curX > min2X)) || ((!xDim) && (curY > min2Y)) ) { // PC1 part1->insert(curX,curY,curZ); if (part1->GetNoCpointnodes() > 1) { calcNewMinMax(curX, curY, pc1MinMax); } else { // [0] minX, [1] minY, [2] maxX, [3] maxY pc1MinMax[0] = curX; pc1MinMax[1] = curY; pc1MinMax[2] = curX; pc1MinMax[3] = curY; } } else { // PC2 part2->insert(curX,curY,curZ); if (part2->GetNoCpointnodes() > 1) { calcNewMinMax(curX, curY, pc2MinMax); } else { pc2MinMax[0] = curX; pc2MinMax[1] = curY; pc2MinMax[2] = curX; pc2MinMax[3] = curY; } } } // set calculated MinMaxValues part1->setMinX(pc1MinMax[0]); part1->setMinY(pc1MinMax[1]); part1->setMaxX(pc1MinMax[2]); part1->setMaxY(pc1MinMax[3]); part2->setMinX(pc2MinMax[0]); part2->setMinY(pc2MinMax[1]); part2->setMaxX(pc2MinMax[2]); part2->setMaxY(pc2MinMax[3]); } private: // vars for lasfile treatment and interim PointClouds std::deque lasFileList; int noPCElems; bool fError, pError; unsigned int maxRead; char fSignature[4]; std::string sSignature; unsigned char fVerMajor, fVerMinor; unsigned int fOffsetToPointData; unsigned short fPointDataRecLength; unsigned int fNumPointRecs; double fScaleX, fScaleY, fScaleZ; double fOffsetX, fOffsetY, fOffsetZ; double fMaxX, fMinX, fMaxY, fMinY; double fMaxZ, fMinZ; int fPointX, fPointY, fPointZ; double maxX, minX, maxY, minY; double maxZ, minZ; double pointX, pointY, pointZ; unsigned short fIntensity; double cellOffsetX; double cellOffsetY; long noGridCellsSide; long noGridCells, cellNo, cellNoY, cellNoX; size_t maxNoPoints, maxPointsPC; PointCloud** pointCloudArray; std::queue resultList; // for one file }; // end of class importpointcloudLI }; // end of class ImportPointCloud } // end of namespace pointcloud #endif //SECONDO_IMPORTPOINTCLOUD_H