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