QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
20#include <QFile>
21#include <QFileInfo>
22#include <QDir>
23#include <QJsonArray>
24#include <QJsonDocument>
25#include <QJsonObject>
26#include <QTime>
27#include <QtDebug>
28#include <QQueue>
29#include <QTimer>
30
31#include "qgsapplication.h"
34#include "qgslogger.h"
35#include "qgsfeedback.h"
40#include "qgspointcloudexpression.h"
43
45
46QgsRemoteEptPointCloudIndex::QgsRemoteEptPointCloudIndex() : QgsEptPointCloudIndex()
47{
48 mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) );
49}
50
51QgsRemoteEptPointCloudIndex::~QgsRemoteEptPointCloudIndex() = default;
52
53std::unique_ptr<QgsPointCloudIndex> QgsRemoteEptPointCloudIndex::clone() const
54{
55 QgsRemoteEptPointCloudIndex *clone = new QgsRemoteEptPointCloudIndex;
56 QMutexLocker locker( &mHierarchyMutex );
57 copyCommonProperties( clone );
58 return std::unique_ptr<QgsPointCloudIndex>( clone );
59}
60
61QList<IndexedPointCloudNode> QgsRemoteEptPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const
62{
63 QList<IndexedPointCloudNode> lst;
64 if ( !loadNodeHierarchy( n ) )
65 return lst;
66
67 const int d = n.d() + 1;
68 const int x = n.x() * 2;
69 const int y = n.y() * 2;
70 const int z = n.z() * 2;
71
72 lst.reserve( 8 );
73 for ( int i = 0; i < 8; ++i )
74 {
75 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
76 const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz );
77 if ( loadNodeHierarchy( n2 ) )
78 lst.append( n2 );
79 }
80 return lst;
81}
82
83void QgsRemoteEptPointCloudIndex::load( const QString &uri )
84{
85 mUri = uri;
86
87 QStringList splitUrl = uri.split( '/' );
88
89 mUrlFileNamePart = splitUrl.back();
90 splitUrl.pop_back();
91 mUrlDirectoryPart = splitUrl.join( '/' );
92
93 QNetworkRequest nr = QNetworkRequest( QUrl( mUri ) );
94
96 const QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
98 {
99 QgsDebugError( QStringLiteral( "Request failed: " ) + uri );
100 mIsValid = false;
101 mError = req.errorMessage();
102 return;
103 }
104
105 const QgsNetworkReplyContent reply = req.reply();
106 mIsValid = loadSchema( reply.content() );
107}
108
109std::unique_ptr<QgsPointCloudBlock> QgsRemoteEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
110{
111 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
112 {
113 return std::unique_ptr<QgsPointCloudBlock>( cached );
114 }
115
116 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
117 if ( !blockRequest )
118 return nullptr;
119
120 QEventLoop loop;
121 connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
122 loop.exec();
123
124 std::unique_ptr<QgsPointCloudBlock> block = blockRequest->takeBlock();
125 if ( !block )
126 {
127 QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
128 }
129
130 storeNodeDataToCache( block.get(), n, request );
131 return block;
132}
133
134QgsPointCloudBlockRequest *QgsRemoteEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
135{
136 if ( QgsPointCloudBlock *cached = getNodeDataFromCache( n, request ) )
137 {
138 return new QgsCachedPointCloudBlockRequest( cached, n, mUri, attributes(), request.attributes(),
139 scale(), offset(), mFilterExpression, request.filterRect() );
140 }
141
142 if ( !loadNodeHierarchy( n ) )
143 return nullptr;
144
145 QString fileUrl;
146 if ( mDataType == QLatin1String( "binary" ) )
147 {
148 fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() );
149 }
150 else if ( mDataType == QLatin1String( "zstandard" ) )
151 {
152 fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() );
153 }
154 else if ( mDataType == QLatin1String( "laszip" ) )
155 {
156 fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() );
157 }
158 else
159 {
160 return nullptr;
161 }
162
163 // we need to create a copy of the expression to pass to the decoder
164 // as the same QgsPointCloudExpression object might be concurrently
165 // used on another thread, for example in a 3d view
166 QgsPointCloudExpression filterExpression = mFilterExpression;
167 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
168 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
169 return new QgsEptPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), requestAttributes, scale(), offset(), filterExpression, request.filterRect() );
170}
171
172bool QgsRemoteEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const
173{
174 return loadNodeHierarchy( n );
175}
176
177bool QgsRemoteEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const
178{
179 mHierarchyMutex.lock();
180 bool found = mHierarchy.contains( nodeId );
181 mHierarchyMutex.unlock();
182 if ( found )
183 return true;
184
185 QVector<IndexedPointCloudNode> nodePathToRoot;
186 {
187 IndexedPointCloudNode currentNode = nodeId;
188 do
189 {
190 nodePathToRoot.push_back( currentNode );
191 currentNode = currentNode.parentNode();
192 }
193 while ( currentNode.d() >= 0 );
194 }
195
196 for ( int i = nodePathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i )
197 {
198 const IndexedPointCloudNode node = nodePathToRoot[i];
200 mHierarchyMutex.lock();
201 const bool foundInHierarchy = mHierarchy.contains( node );
202 const bool foundInHierarchyNodes = mHierarchyNodes.contains( node );
203 mHierarchyMutex.unlock();
204 if ( foundInHierarchy )
205 continue;
206
207 if ( !foundInHierarchyNodes )
208 continue;
209
210 const QString fileUrl = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, node.toString() );
211 QNetworkRequest nr( fileUrl );
212 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsRemoteEptPointCloudIndex" ) );
213 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
214 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
215
216 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
217
218 QEventLoop loop;
219 connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
220 loop.exec();
221
222 if ( reply->error() != QNetworkReply::NoError )
223 {
224 QgsDebugError( QStringLiteral( "Request failed: " ) + mUri );
225 return false;
226 }
227
228 const QByteArray dataJsonH = reply->data();
229 QJsonParseError errH;
230 const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
231 if ( errH.error != QJsonParseError::NoError )
232 {
233 QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( fileUrl ), 2 );
234 return false;
235 }
236
237 const QJsonObject rootHObj = docH.object();
238 for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
239 {
240 const QString nodeIdStr = it.key();
241 const int nodePointCount = it.value().toInt();
243 mHierarchyMutex.lock();
244 if ( nodePointCount >= 0 )
245 mHierarchy[nodeId] = nodePointCount;
246 else if ( nodePointCount == -1 )
247 mHierarchyNodes.insert( nodeId );
248 mHierarchyMutex.unlock();
249 }
250 }
251
252 mHierarchyMutex.lock();
253 found = mHierarchy.contains( nodeId );
254 mHierarchyMutex.unlock();
255
256 return found;
257}
258
259bool QgsRemoteEptPointCloudIndex::isValid() const
260{
261 return mIsValid;
262}
263
264void QgsRemoteEptPointCloudIndex::copyCommonProperties( QgsRemoteEptPointCloudIndex *destination ) const
265{
266 QgsEptPointCloudIndex::copyCommonProperties( destination );
267
268 // QgsRemoteEptPointCloudIndex specific fields
269 destination->mUrlDirectoryPart = mUrlDirectoryPart;
270 destination->mUrlFileNamePart = mUrlFileNamePart;
271 destination->mHierarchyNodes = mHierarchyNodes;
272}
273
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...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
QString errorMessage() const
Returns the error message string, after a get(), post(), head() or put() request has been made.
@ 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)