QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
33 #include "qgspointcloudrequest.h"
34 #include "qgspointcloudattribute.h"
35 #include "qgslogger.h"
36 #include "qgsfeedback.h"
37 #include "qgsmessagelog.h"
38 
39 #include "qgstiledownloadmanager.h"
41 
42 #include "qgsfileutils.h"
43 #include "qgsapplication.h"
45 
47 
48 QgsRemoteEptPointCloudIndex::QgsRemoteEptPointCloudIndex() : QgsEptPointCloudIndex()
49 {
50  mHierarchyNodes.insert( IndexedPointCloudNode( 0, 0, 0, 0 ) );
51 }
52 
53 QgsRemoteEptPointCloudIndex::~QgsRemoteEptPointCloudIndex() = default;
54 
55 QList<IndexedPointCloudNode> QgsRemoteEptPointCloudIndex::nodeChildren( const IndexedPointCloudNode &n ) const
56 {
57  QList<IndexedPointCloudNode> lst;
58  if ( !loadNodeHierarchy( n ) )
59  return lst;
60 
61  const int d = n.d() + 1;
62  const int x = n.x() * 2;
63  const int y = n.y() * 2;
64  const int z = n.z() * 2;
65 
66  for ( int i = 0; i < 8; ++i )
67  {
68  int dx = i & 1, dy = !!( i & 2 ), dz = !!( i & 4 );
69  const IndexedPointCloudNode n2( d, x + dx, y + dy, z + dz );
70  if ( loadNodeHierarchy( n2 ) )
71  lst.append( n2 );
72  }
73  return lst;
74 }
75 
76 void QgsRemoteEptPointCloudIndex::load( const QString &url )
77 {
78  mUrl = QUrl( url );
79 
80  QStringList splitUrl = url.split( '/' );
81 
82  mUrlFileNamePart = splitUrl.back();
83  splitUrl.pop_back();
84  mUrlDirectoryPart = splitUrl.join( '/' );
85 
86  QNetworkRequest nr( url );
87 
89  const QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
90  if ( errCode != QgsBlockingNetworkRequest::NoError )
91  {
92  QgsDebugMsg( QStringLiteral( "Request failed: " ) + url );
93  mIsValid = false;
94  return;
95  }
96 
97  const QgsNetworkReplyContent reply = req.reply();
98  mIsValid = loadSchema( reply.content() );
99 }
100 
101 QgsPointCloudBlock *QgsRemoteEptPointCloudIndex::nodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
102 {
103  std::unique_ptr<QgsPointCloudBlockRequest> blockRequest( asyncNodeData( n, request ) );
104  if ( !blockRequest )
105  return nullptr;
106 
107  QEventLoop loop;
108  connect( blockRequest.get(), &QgsPointCloudBlockRequest::finished, &loop, &QEventLoop::quit );
109  loop.exec();
110 
111  if ( !blockRequest->block() )
112  {
113  QgsDebugMsg( QStringLiteral( "Error downloading node %1 data, error : %2 " ).arg( n.toString(), blockRequest->errorStr() ) );
114  }
115 
116  return blockRequest->block();
117 }
118 
119 QgsPointCloudBlockRequest *QgsRemoteEptPointCloudIndex::asyncNodeData( const IndexedPointCloudNode &n, const QgsPointCloudRequest &request )
120 {
121  if ( !loadNodeHierarchy( n ) )
122  return nullptr;
123 
124  QString fileUrl;
125  if ( mDataType == QLatin1String( "binary" ) )
126  {
127  fileUrl = QStringLiteral( "%1/ept-data/%2.bin" ).arg( mUrlDirectoryPart, n.toString() );
128  }
129  else if ( mDataType == QLatin1String( "zstandard" ) )
130  {
131  fileUrl = QStringLiteral( "%1/ept-data/%2.zst" ).arg( mUrlDirectoryPart, n.toString() );
132  }
133  else if ( mDataType == QLatin1String( "laszip" ) )
134  {
135  fileUrl = QStringLiteral( "%1/ept-data/%2.laz" ).arg( mUrlDirectoryPart, n.toString() );
136  }
137  else
138  {
139  return nullptr;
140  }
141 
142  return new QgsPointCloudBlockRequest( n, fileUrl, mDataType, attributes(), request.attributes(), scale(), offset() );
143 }
144 
145 bool QgsRemoteEptPointCloudIndex::hasNode( const IndexedPointCloudNode &n ) const
146 {
147  return loadNodeHierarchy( n );
148 }
149 
150 bool QgsRemoteEptPointCloudIndex::loadNodeHierarchy( const IndexedPointCloudNode &nodeId ) const
151 {
152  mHierarchyMutex.lock();
153  bool found = mHierarchy.contains( nodeId );
154  mHierarchyMutex.unlock();
155  if ( found )
156  return true;
157 
158  QVector<IndexedPointCloudNode> nodePathToRoot;
159  {
160  IndexedPointCloudNode currentNode = nodeId;
161  do
162  {
163  nodePathToRoot.push_back( currentNode );
164  currentNode = currentNode.parentNode();
165  }
166  while ( currentNode.d() >= 0 );
167  }
168 
169  for ( int i = nodePathToRoot.size() - 1; i >= 0 && !mHierarchy.contains( nodeId ); --i )
170  {
171  const IndexedPointCloudNode node = nodePathToRoot[i];
173  mHierarchyMutex.lock();
174  const bool foundInHierarchy = mHierarchy.contains( node );
175  const bool foundInHierarchyNodes = mHierarchyNodes.contains( node );
176  mHierarchyMutex.unlock();
177  if ( foundInHierarchy )
178  continue;
179 
180  if ( !foundInHierarchyNodes )
181  continue;
182 
183  const QString fileUrl = QStringLiteral( "%1/ept-hierarchy/%2.json" ).arg( mUrlDirectoryPart, node.toString() );
184  QNetworkRequest nr( fileUrl );
185 
186  nr.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
187  nr.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
188 
190  const QgsBlockingNetworkRequest::ErrorCode errCode = req.get( nr );
191  if ( errCode != QgsBlockingNetworkRequest::NoError )
192  {
193  QgsDebugMsgLevel( QStringLiteral( "unable to read hierarchy from file %1" ).arg( fileUrl ), 2 );
194  return false;
195  }
196 
197  const QgsNetworkReplyContent reply = req.reply();
198 
199  const QByteArray dataJsonH = reply.content();
200  QJsonParseError errH;
201  const QJsonDocument docH = QJsonDocument::fromJson( dataJsonH, &errH );
202  if ( errH.error != QJsonParseError::NoError )
203  {
204  QgsDebugMsgLevel( QStringLiteral( "QJsonParseError when reading hierarchy from file %1" ).arg( fileUrl ), 2 );
205  return false;
206  }
207 
208  const QJsonObject rootHObj = docH.object();
209  for ( auto it = rootHObj.constBegin(); it != rootHObj.constEnd(); ++it )
210  {
211  const QString nodeIdStr = it.key();
212  const int nodePointCount = it.value().toInt();
213  const IndexedPointCloudNode nodeId = IndexedPointCloudNode::fromString( nodeIdStr );
214  mHierarchyMutex.lock();
215  if ( nodePointCount > 0 )
216  mHierarchy[nodeId] = nodePointCount;
217  else if ( nodePointCount == -1 )
218  mHierarchyNodes.insert( nodeId );
219  mHierarchyMutex.unlock();
220  }
221  }
222 
223  mHierarchyMutex.lock();
224  found = mHierarchy.contains( nodeId );
225  mHierarchyMutex.unlock();
226 
227  return found;
228 }
229 
230 bool QgsRemoteEptPointCloudIndex::isValid() const
231 {
232  return mIsValid;
233 }
234 
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() or post() request has been made.
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
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