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