QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
qgsremotecopcpointcloudindex.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsremotecopcpointcloudindex.cpp
3 --------------------
4 begin : March 2022
5 copyright : (C) 2022 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
33#include "qgslogger.h"
35#include "qgsapplication.h"
37#include "qgspointcloudexpression.h"
39
41
42QgsRemoteCopcPointCloudIndex::QgsRemoteCopcPointCloudIndex() = default;
43
44QgsRemoteCopcPointCloudIndex::~QgsRemoteCopcPointCloudIndex() = default;
45
46std::unique_ptr<QgsPointCloudIndex> QgsRemoteCopcPointCloudIndex::clone() const
47{
48 QgsRemoteCopcPointCloudIndex *clone = new QgsRemoteCopcPointCloudIndex;
49 QMutexLocker locker( &mHierarchyMutex );
50 copyCommonProperties( clone );
51 return std::unique_ptr<QgsPointCloudIndex>( clone );
52}
53
54QList<IndexedPointCloudNode> QgsRemoteCopcPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const
55{
56 fetchNodeHierarchy( n );
57
58 mHierarchyMutex.lock();
59 Q_ASSERT( mHierarchy.contains( n ) );
60 QList<IndexedPointCloudNode> lst;
61 lst.reserve( 8 );
62 const int d = n.d() + 1;
63 const int x = n.x() * 2;
64 const int y = n.y() * 2;
65 const int z = n.z() * 2;
66 mHierarchyMutex.unlock();
67
68 for ( int i = 0; i < 8; ++i )
69 {
70 int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
71 const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz );
72 if ( fetchNodeHierarchy( n2 ) && mHierarchy[n] >= 0 )
73 lst.append( n2 );
74 }
75 return lst;
76}
77
78void QgsRemoteCopcPointCloudIndex::load( const QString &url )
79{
80 mUrl = QUrl( url );
81 mLazInfo.reset( new QgsLazInfo( QgsLazInfo::fromUrl( mUrl ) ) );
82 mIsValid = mLazInfo->isValid();
83 if ( mIsValid )
84 {
85 mIsValid = loadSchema( *mLazInfo.get() );
86 if ( mIsValid )
87 {
88 loadHierarchy();
89 }
90 }
91 if ( !mIsValid )
92 {
93 mError = tr( "Unable to recognize %1 as a LAZ file: \"%2\"" ).arg( url, mLazInfo->error() );
94 }
95}
96
97QgsPointCloudBlock *QgsRemoteCopcPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
98{
99 std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
100 if ( !blockRequest )
101 return nullptr;
102
103 QEventLoop loop;
104 connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
105 loop.exec();
106
107 if ( !blockRequest->block() )
108 {
109 QgsDebugError( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
110 }
111
112 return blockRequest->block();
113}
114
115QgsPointCloudBlockRequest *QgsRemoteCopcPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
116{
117 if ( !fetchNodeHierarchy( n ) )
118 return nullptr;
119 QMutexLocker locker( &mHierarchyMutex );
120
121 // we need to create a copy of the expression to pass to the decoder
122 // as the same QgsPointCloudExpression object might be concurrently
123 // used on another thread, for example in a 3d view
124 QgsPointCloudExpression filterExpression = mFilterExpression;
125 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
126 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
127 auto [ blockOffset, blockSize ] = mHierarchyNodePos.value( n );
128 int pointCount = mHierarchy.value( n );
129
130 return new QgsCopcPointCloudBlockRequest( n, mUrl.toString(), attributes(), requestAttributes,
131 scale(), offset(), filterExpression, request.filterRect(),
132 blockOffset, blockSize, pointCount, *mLazInfo.get() );
133}
134
135bool QgsRemoteCopcPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const
136{
137 return fetchNodeHierarchy( n );
138}
139
140bool QgsRemoteCopcPointCloudIndex::fetchNodeHierarchy( const IndexedPointCloudNode &n ) const
141{
142 QMutexLocker locker( &mHierarchyMutex );
143
144 QVector<IndexedPointCloudNode> ancestors;
145 IndexedPointCloudNode foundRoot = n;
146 while ( !mHierarchy.contains( foundRoot ) )
147 {
148 ancestors.push_front( foundRoot );
149 foundRoot = foundRoot.parentNode();
150 }
151 ancestors.push_front( foundRoot );
152 for ( IndexedPointCloudNode n : ancestors )
153 {
154 auto hierarchyIt = mHierarchy.constFind( n );
155 if ( hierarchyIt == mHierarchy.constEnd() )
156 return false;
157
158 int nodesCount = *hierarchyIt;
159 if ( nodesCount < 0 )
160 {
161 auto hierarchyNodePos = mHierarchyNodePos.constFind( n );
162 fetchHierarchyPage( hierarchyNodePos->first, hierarchyNodePos->second );
163 }
164 }
165 return mHierarchy.contains( n );
166}
167
168bool QgsRemoteCopcPointCloudIndex::isValid() const
169{
170 return mIsValid;
171}
172
173void QgsRemoteCopcPointCloudIndex::fetchHierarchyPage( uint64_t offset, uint64_t byteSize ) const
174{
175 QNetworkRequest nr( mUrl );
176 QgsSetRequestInitiatorClass( nr, QStringLiteral( "QgsRemoteCopcPointCloudIndex" ) );
177 nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
178 nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
179 QByteArray queryRange = QStringLiteral( "bytes=%1-%2" ).arg( offset ).arg( offset + byteSize - 1 ).toLocal8Bit();
180 nr.setRawHeader( "Range", queryRange );
181
182 std::unique_ptr<QgsTileDownloadManagerReply> reply( QgsApplication::tileDownloadManager()->get( nr ) );
183
184 QEventLoop loop;
185 connect( reply.get(), &QgsTileDownloadManagerReply::finished, &loop, &QEventLoop::quit );
186 loop.exec();
187
188 if ( reply->error() != QNetworkReply::NoError )
189 {
190 QgsDebugError( QStringLiteral( "Request failed: " ) + mUrl.toString() );
191 return;
192 }
193
194 QByteArray data = reply->data();
195
196 struct CopcVoxelKey
197 {
198 int32_t level;
199 int32_t x;
200 int32_t y;
201 int32_t z;
202 };
203
204 struct CopcEntry
205 {
206 CopcVoxelKey key;
207 uint64_t offset;
208 int32_t byteSize;
209 int32_t pointCount;
210 };
211
212 for ( uint64_t i = 0; i < byteSize; i += sizeof( CopcEntry ) )
213 {
214 CopcEntry *entry = reinterpret_cast<CopcEntry *>( data.data() + i );
215 const IndexedPointCloudNode nodeId( entry->key.level, entry->key.x, entry->key.y, entry->key.z );
216 mHierarchy[nodeId] = entry->pointCount;
217 mHierarchyNodePos.insert( nodeId, QPair<uint64_t, int32_t>( entry->offset, entry->byteSize ) );
218 }
219}
220
221void QgsRemoteCopcPointCloudIndex::copyCommonProperties( QgsRemoteCopcPointCloudIndex *destination ) const
222{
223 QgsCopcPointCloudIndex::copyCommonProperties( destination );
224
225 // QgsRemoteCopcPointCloudIndex specific fields
226 destination->mUrl = mUrl;
227 destination->mHierarchyNodes = mHierarchyNodes;
228}
229
Represents a indexed point cloud node in octree.
int y() const
Returns y.
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.
Base class for handling loading QgsPointCloudBlock asynchronously from a remote COPC dataset.
Class for extracting information contained in LAZ file such as the public header block and variable l...
Definition: qgslazinfo.h:39
static QgsLazInfo fromUrl(QUrl &url)
Static function to create a QgsLazInfo class from a file over network.
Definition: qgslazinfo.cpp:293
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 QgsDebugError(str)
Definition: qgslogger.h:38
#define QgsSetRequestInitiatorClass(request, _class)