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