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