QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgspointcloudeditingindex.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudeditingindex.cpp
3 ---------------------
4 begin : December 2024
5 copyright : (C) 2024 by Stefanos Natsis
6 email : uclaros at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17
20#include "qgscopcupdate.h"
21#include "qgslazdecoder.h"
22#include "qgspointcloudlayer.h"
24
25#include <QDir>
26#include <QFileInfo>
27#include <QMutex>
28#include <QString>
29
30using namespace Qt::StringLiterals;
31
33{
34 if ( !layer ||
35 !layer->dataProvider() ||
36 !layer->dataProvider()->hasValidIndex() ||
38 return;
39
40 mUri = layer->source();
41 mIndex = layer->dataProvider()->index();
42
43 mAttributes = mIndex.attributes();
44 mScale = mIndex.scale();
45 mOffset = mIndex.offset();
46 mExtent = mIndex.extent();
47 mZMin = mIndex.zMin();
48 mZMax = mIndex.zMax();
49 mRootBounds = mIndex.rootNodeBounds();
50 mSpan = mIndex.span();
51 mIsValid = true;
52}
53
54void QgsPointCloudEditingIndex::load( const QString &, const QString & )
55{
56 return;
57}
58
60{
61 return mIsValid && mIndex.isValid();
62}
63
65{
66 return mIndex.accessType();
67}
68
70{
71 return mIndex.crs();
72}
73
75{
76 return mIndex.pointCount();
77}
78
80{
81 return mIndex.originalMetadata();
82}
83
85{
86 return mIndex.hasNode( n );
87}
88
90{
91 return mIndex.getNode( id );
92}
93
94bool QgsPointCloudEditingIndex::setSubsetString( const QString &subset )
95{
96 return mIndex.setSubsetString( subset );
97}
98
100{
101 return mIndex.subsetString();
102}
103
104std::unique_ptr< QgsPointCloudBlock > QgsPointCloudEditingIndex::nodeData( const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request )
105{
106 mEditedNodeDataMutex.lock(); // Unlocked in both branches!
107 if ( mEditedNodeData.contains( n ) )
108 {
109 // we need to create a copy of the expression to pass to the decoder
110 // as the same QgsPointCloudExpression object mighgt be concurrently
111 // used on another thread, for example in a 3d view
112 QgsPointCloudExpression filterExpression = QgsPointCloudExpression( request.ignoreIndexFilterEnabled() ? QString() : subsetString() );
113 QgsPointCloudAttributeCollection requestAttributes = request.attributes();
114 requestAttributes.extend( attributes(), filterExpression.referencedAttributes() );
115
116 QgsRectangle filterRect = request.filterRect();
117
118 QByteArray rawBlockData = mEditedNodeData[n];
119 mEditedNodeDataMutex.unlock();
120
121 QgsCopcPointCloudIndex *copcIndex = static_cast<QgsCopcPointCloudIndex *>( mIndex.get() );
122
123 int pointCount = copcIndex->mHierarchy.value( n );
124
125 return QgsLazDecoder::decompressCopc( rawBlockData, *copcIndex->mLazInfo.get(), pointCount, requestAttributes, filterExpression, filterRect );
126 }
127 else
128 {
129 mEditedNodeDataMutex.unlock();
130 return mIndex.nodeData( n, request );
131 }
132}
133
135{
136 Q_ASSERT( false );
137 return nullptr;
138}
139
144
146{
147 QMutexLocker locker( &mEditedNodeDataMutex );
148
149 return mEditedNodeData.value( n );
150}
151
153{
154 QMutexLocker locker( &mEditedNodeDataMutex );
155
156 mEditedNodeData.remove( n );
157}
158
159bool QgsPointCloudEditingIndex::commitChanges( QString *errorMessage )
160{
161 QMutexLocker locker( &mEditedNodeDataMutex );
162
163 if ( mEditedNodeData.isEmpty() )
164 return true;
165
166 QHash<QgsPointCloudNodeId, QgsCopcUpdate::UpdatedChunk> updatedChunks;
167 for ( auto it = mEditedNodeData.constBegin(); it != mEditedNodeData.constEnd(); ++it )
168 {
169 QgsPointCloudNodeId n = it.key();
170 // right now we're assuming there's no change of point count
171 qint32 nodePointCount = static_cast<qint32>( getNode( n ).pointCount() );
172 updatedChunks[n] = QgsCopcUpdate::UpdatedChunk{ nodePointCount, it.value() };
173 }
174
175 QFileInfo fileInfo( mUri );
176 const QString outputFilename = fileInfo.dir().filePath( fileInfo.baseName() + u"-update.copc.laz"_s );
177
178 if ( !QgsCopcUpdate::writeUpdatedFile( mUri, outputFilename, updatedChunks, errorMessage ) )
179 {
180 return false;
181 }
182
183 // reset the underlying index - we will reload it at the end
184 QgsCopcPointCloudIndex *copcIndex = static_cast<QgsCopcPointCloudIndex *>( mIndex.get() );
185 copcIndex->reset();
186
187 const QString originalFilename = fileInfo.dir().filePath( fileInfo.baseName() + u"-original.copc.laz"_s );
188 if ( !QFile::rename( mUri, originalFilename ) )
189 {
190 if ( errorMessage )
191 *errorMessage = u"Rename of the old COPC failed!"_s;
192 QFile::remove( outputFilename );
193 return false;
194 }
195
196 if ( !QFile::rename( outputFilename, mUri ) )
197 {
198 if ( errorMessage )
199 *errorMessage = u"Rename of the new COPC failed!"_s;
200 QFile::rename( originalFilename, mUri );
201 QFile::remove( outputFilename );
202 return false;
203 }
204
205 if ( !QFile::remove( originalFilename ) )
206 {
207 if ( errorMessage )
208 *errorMessage = u"Removal of the old COPC failed!"_s;
209 // TODO: cleanup here as well?
210 return false;
211 }
212
213 mEditedNodeData.clear();
214
215 // now let's reload
216 copcIndex->load( mUri );
217
218 return true;
219}
220
222{
223 QMutexLocker locker( &mEditedNodeDataMutex );
224
225 return !mEditedNodeData.isEmpty();
226}
227
229{
230 QMutexLocker locker( &mEditedNodeDataMutex );
231
232 return mEditedNodeData.contains( n );
233}
234
235QList<QgsPointCloudNodeId> QgsPointCloudEditingIndex::updatedNodes() const
236{
237 QMutexLocker locker( &mEditedNodeDataMutex );
238
239 return mEditedNodeData.keys();
240}
241
242bool QgsPointCloudEditingIndex::updateNodeData( const QHash<QgsPointCloudNodeId, QByteArray> &data )
243{
244 QMutexLocker locker( &mEditedNodeDataMutex );
245
246 for ( auto it = data.constBegin(); it != data.constEnd(); ++it )
247 {
248 mEditedNodeData[it.key()] = it.value();
249 }
250
251 // get rid of cached keys that got modified
252 {
253 QMutexLocker locker( &sBlockCacheMutex );
254 const QList<QgsPointCloudCacheKey> cacheKeys = sBlockCache.keys();
255 for ( const QgsPointCloudCacheKey &cacheKey : cacheKeys )
256 {
257 if ( cacheKey.uri() == mUri && data.contains( cacheKey.node() ) )
258 sBlockCache.remove( cacheKey );
259 }
260 }
261
262 return true;
263}
PointCloudAccessType
The access type of the data, local is for local files and remote for remote files (over HTTP).
Definition qgis.h:6340
QgsPointCloudAttributeCollection mAttributes
QgsBox3D mRootBounds
Bounds of the root node's cube (in int32 coordinates).
QgsVector3D mOffset
Offset of our int32 coordinates compared to CRS coords.
QgsPointCloudAttributeCollection attributes() const
Returns all attributes that are stored in the file.
static QCache< QgsPointCloudCacheKey, QgsPointCloudBlock > sBlockCache
int mSpan
All native attributes stored in the file.
double mZMax
Vertical extent of data.
QgsRectangle mExtent
2D extent of data
QgsVector3D mScale
Scale of our int32 coordinates compared to CRS coords.
Represents a coordinate reference system (CRS).
static bool writeUpdatedFile(const QString &inputFilename, const QString &outputFilename, const QHash< QgsPointCloudNodeId, UpdatedChunk > &updatedChunks, QString *errorMessage=nullptr)
Convenience function to do the whole process in one go: load a COPC file, then write a new COPC file ...
QString source() const
Returns the source for the layer.
A 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.
Container class for QgsPointCloudBlock cache keys.
@ ChangeAttributeValues
Provider can modify the values of point attributes.
virtual QgsPointCloudIndex index() const
Returns the point cloud index associated with the provider.
virtual QgsPointCloudDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities for the data provider.
bool hasValidIndex() const
Returns whether provider has index which is valid.
QList< QgsPointCloudNodeId > updatedNodes() const
Returns a list of node IDs that have been modified.
bool hasNode(const QgsPointCloudNodeId &n) const override
Returns whether the octree contain given node.
void load(const QString &fileName, const QString &authcfg=QString()) override
Loads the index from the uri, using an optional authcfg for network requests.
bool isModified() const
Returns true if there are uncommitted changes, false otherwise.
QgsPointCloudIndex backingIndex() const
Returns index for the underlying non-edited data.
QVariantMap originalMetadata() const override
Returns the original metadata map.
qint64 pointCount() const override
Returns the number of points in the point cloud.
bool commitChanges(QString *errorMessage=nullptr)
Tries to store pending changes to the data provider.
bool setSubsetString(const QString &subset) override
Sets the string used to define a subset of the point cloud.
void resetNodeEdits(QgsPointCloudNodeId n)
Removes node edits from index, returning it to its original state.
const QByteArray rawEditedNodeData(QgsPointCloudNodeId n) const
Returns the raw, encoded, compressed data for a node or empty if missing.
QgsPointCloudBlockRequest * asyncNodeData(const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request) override
Returns a handle responsible for loading a node data block.
QgsPointCloudEditingIndex(QgsPointCloudLayer *layer)
Ctor.
bool isNodeModified(QgsPointCloudNodeId n) const
Returns true if this node was modified.
QString subsetString() const override
Returns the string used to define a subset of the point cloud.
bool isValid() const override
Returns whether index is loaded and valid.
std::unique_ptr< QgsPointCloudBlock > nodeData(const QgsPointCloudNodeId &n, const QgsPointCloudRequest &request) override
Returns node data block.
QgsPointCloudNode getNode(const QgsPointCloudNodeId &id) const override
Returns object for a given node.
Qgis::PointCloudAccessType accessType() const override
Returns the access type of the data If the access type is Remote, data will be fetched from an HTTP s...
bool updateNodeData(const QHash< QgsPointCloudNodeId, QByteArray > &data) override
Tries to update the data for the specified nodes.
QgsCoordinateReferenceSystem crs() const override
Returns the coordinate reference system of the point cloud index.
Smart pointer for QgsAbstractPointCloudIndex.
Represents a map layer supporting display of point clouds.
QgsPointCloudDataProvider * dataProvider() override
Returns the layer's data provider, it may be nullptr.
Represents an indexed point cloud node's position in octree.
Keeps metadata for an indexed point cloud node.
qint64 pointCount() const
Returns number of points contained in node data.
Point cloud data request.
bool ignoreIndexFilterEnabled() const
Returns whether the request will ignore the point cloud index's filter expression,...
QgsPointCloudAttributeCollection attributes() const
Returns attributes.
QgsRectangle filterRect() const
Returns the rectangle from which points will be taken, in point cloud's crs.
A rectangle specified with double values.
const QString cacheKey(const QString &pathIn)
Keeps information how points of a single chunk has been modified.