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