1 /***************************************************************************
2   qgspdaleptgenerationtask.cpp
3   ------------------------
4   Date                 : December 2020
5   Copyright            : (C) 2020 by Peter Petrik
6   Email                : zilolv at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgspdaleptgenerationtask.h"
17 
18 #include <vector>
19 #include <string>
20 #include <QDebug>
21 #include <QThread>
22 #include <QFileInfo>
23 #include <QDir>
24 #include <QProcessEnvironment>
25 
26 #include "qgsapplication.h"
27 #include "QgisUntwine.hpp"
28 #include "qgsmessagelog.h"
29 #include "qgis.h"
30 
QgsPdalEptGenerationTask(const QString & file,const QString & outputDir,const QString & name)31 QgsPdalEptGenerationTask::QgsPdalEptGenerationTask( const QString &file, const QString &outputDir, const QString &name )
32   : QgsTask( tr( "Indexing Point Cloud (%1)" ).arg( name ) )
33   , mOutputDir( outputDir )
34   , mFile( file )
35 {
36   mUntwineExecutableBinary = guessUntwineExecutableBinary();
37 }
38 
run()39 bool QgsPdalEptGenerationTask::run()
40 {
41   if ( isCanceled() || !prepareOutputDir() )
42     return false;
43 
44   if ( isCanceled() || !runUntwine() )
45     return false;
46 
47   if ( isCanceled() )
48     return false;
49 
50   cleanTemp();
51 
52   return true;
53 }
54 
cleanTemp()55 void QgsPdalEptGenerationTask::cleanTemp()
56 {
57   QDir tmpDir( mOutputDir + QStringLiteral( "/temp" ) );
58   if ( tmpDir.exists() )
59   {
60     QgsMessageLog::logMessage( tr( "Removing temporary files in %1" ).arg( tmpDir.dirName() ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Info );
61     tmpDir.removeRecursively();
62   }
63 }
64 
runUntwine()65 bool QgsPdalEptGenerationTask::runUntwine()
66 {
67   const QFileInfo executable( mUntwineExecutableBinary );
68   if ( !executable.isExecutable() )
69   {
70     QgsMessageLog::logMessage( tr( "Untwine executable not found %1" ).arg( mUntwineExecutableBinary ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Critical );
71     return false;
72   }
73   else
74   {
75     QgsMessageLog::logMessage( tr( "Using executable %1" ).arg( mUntwineExecutableBinary ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Info );
76   }
77 
78   untwine::QgisUntwine untwineProcess( mUntwineExecutableBinary.toStdString() );
79 
80   untwine::QgisUntwine::Options options;
81   // By default Untwine does not calculate stats for attributes, but they are very useful for us:
82   // we can use them to set automatically set valid range for the data without having to scan the points again.
83   options.push_back( { "stats", std::string() } );
84 
85   const std::vector<std::string> files = {mFile.toStdString()};
86   untwineProcess.start( files, mOutputDir.toStdString(), options );
87   const int lastPercent = 0;
88   while ( true )
89   {
90     QThread::msleep( 100 );
91     const int percent = untwineProcess.progressPercent();
92     if ( lastPercent != percent )
93     {
94       const QString message = QString::fromStdString( untwineProcess.progressMessage() );
95       if ( !message.isEmpty() )
96         QgsMessageLog::logMessage( message, QObject::tr( "Point clouds" ), Qgis::MessageLevel::Info );
97 
98       setProgress( percent );
99     }
100 
101     if ( isCanceled() )
102     {
103       untwineProcess.stop();
104       return false;
105     }
106 
107     if ( !untwineProcess.running() )
108     {
109       setProgress( 100 );
110       return true;
111     }
112   }
113 }
114 
untwineExecutableBinary() const115 QString QgsPdalEptGenerationTask::untwineExecutableBinary() const
116 {
117   return mUntwineExecutableBinary;
118 }
119 
setUntwineExecutableBinary(const QString & untwineExecutableBinary)120 void QgsPdalEptGenerationTask::setUntwineExecutableBinary( const QString &untwineExecutableBinary )
121 {
122   mUntwineExecutableBinary = untwineExecutableBinary;
123 }
124 
guessUntwineExecutableBinary() const125 QString QgsPdalEptGenerationTask::guessUntwineExecutableBinary() const
126 {
127   QString untwineExecutable = QProcessEnvironment::systemEnvironment().value( QStringLiteral( "QGIS_UNTWINE_EXECUTABLE" ) );
128   if ( untwineExecutable.isEmpty() )
129   {
130 #if defined(Q_OS_WIN)
131     untwineExecutable = QgsApplication::libexecPath() + "untwine.exe";
132 #else
133     untwineExecutable = QgsApplication::libexecPath() + "untwine";
134 #endif
135   }
136   return QString( untwineExecutable );
137 }
138 
outputDir() const139 QString QgsPdalEptGenerationTask::outputDir() const
140 {
141   return mOutputDir;
142 }
143 
prepareOutputDir()144 bool QgsPdalEptGenerationTask::prepareOutputDir()
145 {
146   const QFileInfo fi( mOutputDir + "/ept.json" );
147   if ( fi.exists() )
148   {
149     QgsMessageLog::logMessage( tr( "File %1 is already indexed" ).arg( mFile ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Info );
150     return true;
151   }
152   else
153   {
154     if ( QDir( mOutputDir ).exists() )
155     {
156       if ( !QDir( mOutputDir ).isEmpty() )
157       {
158         if ( QDir( mOutputDir + "/temp" ).exists() )
159         {
160           QgsMessageLog::logMessage( tr( "Another indexing process is running (or finished with crash) in directory %1" ).arg( mOutputDir ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Warning );
161           return false;
162         }
163         else
164         {
165           QgsMessageLog::logMessage( tr( "Folder %1 is non-empty, but there isn't ept.json present." ).arg( mOutputDir ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Critical );
166           return false;
167         }
168       }
169     }
170     else
171     {
172       const bool success = QDir().mkdir( mOutputDir );
173       if ( success )
174       {
175         QgsMessageLog::logMessage( tr( "Created output directory %1" ).arg( mOutputDir ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Info );
176       }
177       else
178       {
179         QgsMessageLog::logMessage( tr( "Unable to create output directory %1" ).arg( mOutputDir ), QObject::tr( "Point clouds" ), Qgis::MessageLevel::Critical );
180         return false;
181       }
182     }
183   }
184 
185   return true;
186 }
187