QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
qgsremoteeptpointcloudindex.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsremoteeptpointcloudindex.cpp
3 --------------------
4 begin : March 2021
5 copyright : (C) 2021 by Belgacem Nedjima
6 email : belgacem dot nedjima at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "moc_qgsremoteeptpointcloudindex.cpp"
20
21#include <QFile>
22#include <QFileInfo>
23#include <QDir>
24#include <QJsonArray>
25#include <QJsonDocument>
26#include <QJsonObject>
27#include <QTime>
28#include <QtDebug>
29#include <QQueue>
30#include <QTimer>
31
32#include "qgsapplication.h"
35#include "qgslogger.h"
36#include "qgsfeedback.h"
41#include "qgspointcloudexpression.h"
44
46
47QgsRemoteEptPointCloudIndex::QgsRemoteEptPointCloudIndex() : QgsEptPointCloudIndex()
48{
49 mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) );
50}
51
52QgsRemoteEptPointCloudIndex::~QgsRemoteEptPointCloudIndex() = default;
53
54std::unique_ptr<QgsPointCloudIndex> QgsRemoteEptPointCloudIndex::clone() const
55{
56 QgsRemoteEptPointCloudIndex *clone = new QgsRemoteEptPointCloudIndex;
57 QMutexLocker locker( &mHierarchyMutex );
58 copyCommonProperties( clone );
59 return std::unique_ptr<QgsPointCloudIndex>( clone );
60}
61
62QList<IndexedPointCloudNode> QgsRemoteEptPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const
63{
64 QList<IndexedPointCloudNode> lst;
65 if ( !loadNodeHierarchy( n ) )
66 return lst;
67
68 const int d = n.d() + 1;
69 const int x = n.x() * 2;
70 const int y = n.y() * 2;
71 const int z = n.z() * 2;
72
73 lst.reserve( 8 );
74 for ( int i = 0; i < 8; ++i )
75 {
76 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
77 const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz );
78 if ( loadNodeHierarchy( n2 ) )
79 lst.append( n2 );
80 }
81 return lst;
82}
83
84void QgsRemoteEptPointCloudIndex::load( const QString &uri )
85{
86 mUri = uri;
87
88 QStringList splitUrl = uri.split( '/' );
89
90 mUrlFileNamePart = splitUrl.back();
91 splitUrl.pop_back();
92 mUrlDirectoryPart = splitUrl.join( '/' );
93
94 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
95
97 const QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
99 {
100 QgsDebugError( QStringLiteral( "Request failed: " ) + uri );
101 mIsValid = false;
102 mError = req.errorMessage();
103 return;
104 }
105
106 const QgsNetworkReplyContent reply = req.reply();
107 mIsValid = loadSchema( reply.content() );
108}
109
110std::unique_ptr<QgsPointCloudBlock> QgsRemoteEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
111{
112 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
113 {
114 return std::unique_ptr<QgsPointCloudBlock>( cached );
115 }
116
117 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
118 if ( !blockRequest )
119 return nullptr;
120
121 QEventLoop loop;
122 connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
123 loop.exec();
124
125 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
126 if ( !block )
127 {
128 QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
129 }
130
131 storeNodeDataToCache( block.get(), n, request );
132 return block;
133}
134
135QgsPointCloudBlockRequest *QgsRemoteEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
136{
137 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
138 {
139 return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(),
140 scale(), offset(), mFilterExpression, request.filterRect() );
141 }
142
143 if ( !loadNodeHierarchy( n ) )
144 return nullptr;
145
146 QString fileUrl;
147 if ( mDataType == QLatin1String( "binary" ) )
148 {
149 fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() );
150 }
151 else if ( mDataType == QLatin1String( "zstandard" ) )
152 {
153 fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() );
154 }
155 else if ( mDataType == QLatin1String( "laszip" ) )
156 {
157 fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() );
158 }
159 else
160 {
161 return nullptr;
162 }
163
164 // we need to create a copy of the expression to pass to the decoder
165 // as the same QgsPointCloudExpression object might be concurrently
166 // used on another thread, for example in a 3d view
167 QgsPointCloudExpression filterExpression = mFilterExpression;
168 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
169 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
170 return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() );
171}
172
173bool QgsRemoteEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const
174{
175 return loadNodeHierarchy( n );
176}
177
178bool QgsRemoteEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const
179{
180 mHierarchyMutex.lock();
181 bool found = mHierarchy.contains( nodeId );
182 mHierarchyMutex.unlock();
183 if ( found )
184 return true;
185
186 QVector<IndexedPointCloudNode> nodePathToRoot;
187 {
188 IndexedPointCloudNode currentNode = nodeId;
189 do
190 {
191 nodePathToRoot.push_back( currentNode );
192 currentNode = currentNode.parentNode();
193 }
194 while ( currentNode.d() >= 0 );
195 }
196
197 for ( int i = nodePathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i )
198 {
199 const IndexedPointCloudNode node = nodePathToRoot[i];
201 mHierarchyMutex.lock();
202 const bool foundInHierarchy = mHierarchy.contains( node );
203 const bool foundInHierarchyNodes = mHierarchyNodes.contains( node );
204 mHierarchyMutex.unlock();
205 if ( foundInHierarchy )
206 continue;
207
208 if ( !foundInHierarchyNodes )
209 continue;
210
211 const QString fileUrl = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, node.toString() );
212 QNetworkRequest nr( fileUrl );
213 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsRemoteEptPointCloudIndex" ) );
214 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
215 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
216
217 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
218
219 QEventLoop loop;
220 connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
221 loop.exec();
222
223 if ( reply->error() != QNetworkReply::NoError )
224 {
225 QgsDebugError( QStringLiteral( "Request failed: " ) + mUri );
226 return false;
227 }
228
229 const QByteArray dataJsonH = reply->data();
230 QJsonParseError errH;
231 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
232 if ( errH.error != QJsonParseError::NoError )
233 {
234 QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( fileUrl ), 2 );
235 return false;
236 }
237
238 const QJsonObject rootHObj = docH.object();
239 for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
240 {
241 const QString nodeIdStr = it.key();
242 const int nodePointCount = it.value().toInt();
244 mHierarchyMutex.lock();
245 if ( nodePointCount >= 0 )
246 mHierarchy[nodeId] = nodePointCount;
247 else if ( nodePointCount == -1 )
248 mHierarchyNodes.insert( nodeId );
249 mHierarchyMutex.unlock();
250 }
251 }
252
253 mHierarchyMutex.lock();
254 found = mHierarchy.contains( nodeId );
255 mHierarchyMutex.unlock();
256
257 return found;
258}
259
260bool QgsRemoteEptPointCloudIndex::isValid() const
261{
262 return mIsValid;
263}
264
265void QgsRemoteEptPointCloudIndex::copyCommonProperties( QgsRemoteEptPointCloudIndex *destination ) const
266{
267 QgsEptPointCloudIndex::copyCommonProperties( destination );
268
269 // QgsRemoteEptPointCloudIndex specific fields
270 destination->mUrlDirectoryPart = mUrlDirectoryPart;
271 destination->mUrlFileNamePart = mUrlFileNamePart;
272 destination->mHierarchyNodes = mHierarchyNodes;
273}
274
Represents a indexed point cloud node in octree.
int y() const
Returns y.
static IndexedPointCloudNode fromString(const QString &str)
Creates node from string.
int x() const
Returns x.
QString toString() const
Encode node to string.
int d() const
Returns d.
IndexedPointCloudNode parentNode() const
Returns the parent of the node.
int z() const
Returns z.
static QgsTileDownloadManager * tileDownloadManager()
Returns the application's tile download manager, used for download of map tiles when rendering.
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr, RequestFlags requestFlags=QgsBlockingNetworkRequest::RequestFlags())
Performs a "get" operation on the specified request.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Class for handling a QgsPointCloudBlockRequest using existing cached QgsPointCloudBlock.
Base class for handling loading QgsPointCloudBlock asynchronously from a remote EPT dataset.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
QNetworkReply::NetworkError error() const
Returns the reply's error message, or QNetworkReply::NoError if no error was encountered.
Collection of point cloud attributes.
void extend(const QgsPointCloudAttributeCollection &otherCollection, const QSet< QString > &matchingNames)
Adds specific missing attributes from another QgsPointCloudAttributeCollection.
Base class for handling loading QgsPointCloudBlock asynchronously.
void finished()
Emitted when the request processing has finished.
Base class for storing raw data from point cloud nodes.
Point cloud data request.
QgsPointCloudAttributeCollection attributes() const
Returns attributes.
QgsRectangle filterRect() const
Returns the rectangle from which points will be taken, in point cloud's crs.
void finished()
Emitted when the reply has finished (either with a success or with a failure)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)