QGIS API Documentation 3.41.0-Master (d2aaa9c6e02)
Loading...
Searching...
No Matches
qgspointcloudlayereditutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudlayereditutils.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#include "qgspointcloudlayer.h"
18#include "qgslazdecoder.h"
21
22#include <lazperf/readers.hpp>
23#include <lazperf/writers.hpp>
24
25
30
31bool QgsPointCloudLayerEditUtils::changeAttributeValue( const QgsPointCloudNodeId &n, const QVector<int> &pts, const QgsPointCloudAttribute &attribute, double value )
32{
33 // Cannot allow x,y,z editing as points may get moved outside the node extents
34 if ( attribute.name().compare( QLatin1String( "X" ), Qt::CaseInsensitive ) == 0 ||
35 attribute.name().compare( QLatin1String( "Y" ), Qt::CaseInsensitive ) == 0 ||
36 attribute.name().compare( QLatin1String( "Z" ), Qt::CaseInsensitive ) == 0 )
37 return false;
38
39 if ( !n.isValid() || !mIndex.hasNode( n ) ) // todo: should not have to check if n.isValid
40 return false;
41
42 const QgsPointCloudAttributeCollection attributeCollection = mIndex.attributes();
43
44 int attributeOffset;
45 const QgsPointCloudAttribute *at = attributeCollection.find( attribute.name(), attributeOffset );
46
47 if ( !at ||
48 at->size() != attribute.size() ||
49 at->type() != attribute.type() )
50 {
51 return false;
52 }
53
54 if ( !isAttributeValueValid( attribute, value ) )
55 {
56 return false;
57 }
58
59 const QSet<int> uniquePoints( pts.constBegin(), pts.constEnd() );
60 QVector<int> sortedPoints( uniquePoints.constBegin(), uniquePoints.constEnd() );
61 std::sort( sortedPoints.begin(), sortedPoints.end() );
62
63 if ( sortedPoints.constFirst() < 0 ||
64 sortedPoints.constLast() > mIndex.getNode( n ).pointCount() )
65 return false;
66
67 QgsPointCloudEditingIndex *editIndex = static_cast<QgsPointCloudEditingIndex *>( mIndex.get() );
68 QgsCopcPointCloudIndex *copcIndex = static_cast<QgsCopcPointCloudIndex *>( editIndex->mIndex.get() );
69
70 QByteArray chunkData;
71 if ( editIndex->mEditedNodeData.contains( n ) )
72 {
73 chunkData = editIndex->mEditedNodeData[n];
74 }
75 else
76 {
77 QPair<uint64_t, int32_t> offsetSizePair = copcIndex->mHierarchyNodePos[n];
78 chunkData = copcIndex->readRange( offsetSizePair.first, offsetSizePair.second );
79 }
80
81 QByteArray data = updateChunkValues( copcIndex, chunkData, *at, value, n, pts );
82
83 return mIndex.updateNodeData( {{n, data}} );
84}
85
86
87static void updatePoint( char *pointBuffer, int pointFormat, const QString &attributeName, double newValue )
88{
89 if ( attributeName == QLatin1String( "Intensity" ) ) // unsigned short
90 {
91 quint16 newValueShort = static_cast<quint16>( newValue );
92 memcpy( pointBuffer + 12, &newValueShort, sizeof( qint16 ) );
93 }
94 else if ( attributeName == QLatin1String( "ReturnNumber" ) ) // bits 0-3
95 {
96 uchar newByteValue = static_cast<uchar>( newValue ) & 0xf;
97 pointBuffer[14] = static_cast<char>( ( pointBuffer[14] & 0xf0 ) | newByteValue );
98 }
99 else if ( attributeName == QLatin1String( "NumberOfReturns" ) ) // bits 4-7
100 {
101 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0xf ) << 4;
102 pointBuffer[14] = static_cast<char>( ( pointBuffer[14] & 0xf ) | newByteValue );
103 }
104 else if ( attributeName == QLatin1String( "Synthetic" ) ) // bit 0
105 {
106 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 );
107 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfe ) | newByteValue );
108 }
109 else if ( attributeName == QLatin1String( "KeyPoint" ) ) // bit 1
110 {
111 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 1;
112 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfd ) | newByteValue );
113 }
114 else if ( attributeName == QLatin1String( "Withheld" ) ) // bit 2
115 {
116 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 2;
117 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfb ) | newByteValue );
118 }
119 else if ( attributeName == QLatin1String( "Overlap" ) ) // bit 3
120 {
121 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 3;
122 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xf7 ) | newByteValue );
123 }
124 else if ( attributeName == QLatin1String( "ScannerChannel" ) ) // bits 4-5
125 {
126 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x3 ) << 4;
127 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xcf ) | newByteValue );
128 }
129 else if ( attributeName == QLatin1String( "ScanDirectionFlag" ) ) // bit 6
130 {
131 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 6;
132 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xbf ) | newByteValue );
133 }
134 else if ( attributeName == QLatin1String( "EdgeOfFlightLine" ) ) // bit 7
135 {
136 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 7;
137 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0x7f ) | newByteValue );
138 }
139 else if ( attributeName == QLatin1String( "Classification" ) ) // unsigned char
140 {
141 pointBuffer[16] = static_cast<char>( static_cast<uchar>( newValue ) );
142 }
143 else if ( attributeName == QLatin1String( "UserData" ) ) // unsigned char
144 {
145 pointBuffer[17] = static_cast<char>( static_cast<uchar>( newValue ) );
146 }
147 else if ( attributeName == QLatin1String( "ScanAngleRank" ) ) // short
148 {
149 qint16 newValueShort = static_cast<qint16>( newValue );
150 memcpy( pointBuffer + 18, &newValueShort, sizeof( qint16 ) );
151 }
152 else if ( attributeName == QLatin1String( "PointSourceId" ) ) // unsigned short
153 {
154 quint16 newValueShort = static_cast<quint16>( newValue );
155 memcpy( pointBuffer + 20, &newValueShort, sizeof( quint16 ) );
156 }
157 else if ( attributeName == QLatin1String( "GpsTime" ) ) // double
158 {
159 memcpy( pointBuffer + 22, &newValue, sizeof( double ) );
160 }
161 else if ( pointFormat == 7 || pointFormat == 8 )
162 {
163 if ( attributeName == QLatin1String( "Red" ) ) // unsigned short
164 {
165 quint16 newValueShort = static_cast<quint16>( newValue );
166 memcpy( pointBuffer + 30, &newValueShort, sizeof( quint16 ) );
167 }
168 else if ( attributeName == QLatin1String( "Green" ) ) // unsigned short
169 {
170 quint16 newValueShort = static_cast<quint16>( newValue );
171 memcpy( pointBuffer + 32, &newValueShort, sizeof( quint16 ) );
172 }
173 else if ( attributeName == QLatin1String( "Blue" ) ) // unsigned short
174 {
175 quint16 newValueShort = static_cast<quint16>( newValue );
176 memcpy( pointBuffer + 34, &newValueShort, sizeof( quint16 ) );
177 }
178 else if ( pointFormat == 8 )
179 {
180 if ( attributeName == QLatin1String( "Infrared" ) ) // unsigned short
181 {
182 quint16 newValueShort = static_cast<quint16>( newValue );
183 memcpy( pointBuffer + 36, &newValueShort, sizeof( quint16 ) );
184 }
185 }
186 }
187}
188
189
190QByteArray QgsPointCloudLayerEditUtils::updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, double newValue, const QgsPointCloudNodeId &n, const QVector<int> &pointIndices )
191{
192 Q_ASSERT( copcIndex->mHierarchy.contains( n ) );
193 Q_ASSERT( copcIndex->mHierarchyNodePos.contains( n ) );
194
195 int pointCount = copcIndex->mHierarchy[n];
196
197 lazperf::header14 header = copcIndex->mLazInfo->header();
198
199 lazperf::reader::chunk_decompressor decompressor( header.pointFormat(), header.ebCount(), chunkData.constData() );
200 lazperf::writer::chunk_compressor compressor( header.pointFormat(), header.ebCount() );
201
202 std::unique_ptr<char []> decodedData( new char[ header.point_record_length ] );
203
204 // only PDRF 6/7/8 is allowed by COPC
205 Q_ASSERT( header.pointFormat() == 6 || header.pointFormat() == 7 || header.pointFormat() == 8 );
206
207 QSet<int> pointIndicesSet( pointIndices.constBegin(), pointIndices.constEnd() );
208
209 QString attributeName = attribute.name();
210
211 for ( int i = 0 ; i < pointCount; ++i )
212 {
213 decompressor.decompress( decodedData.get() );
214 char *buf = decodedData.get();
215
216 if ( pointIndicesSet.contains( i ) )
217 {
218 // TODO: support for extrabytes attributes
219 updatePoint( buf, header.point_format_id, attributeName, newValue );
220 }
221
222 compressor.compress( decodedData.get() );
223 }
224
225 std::vector<unsigned char> data = compressor.done();
226 return QByteArray( ( const char * ) data.data(), ( int ) data.size() ); // QByteArray makes a deep copy
227}
228
229
230QByteArray QgsPointCloudLayerEditUtils::dataForAttributes( const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request )
231{
232 const QVector<QgsPointCloudAttribute> attributes = allAttributes.attributes();
233 const int nPoints = data.size() / allAttributes.pointRecordSize();
234 const char *ptr = data.data();
235
236 QByteArray outData;
237 for ( int i = 0; i < nPoints; ++i )
238 {
239 for ( const QgsPointCloudAttribute &attr : attributes )
240 {
241 if ( request.attributes().indexOf( attr.name() ) >= 0 )
242 {
243 outData.append( ptr, attr.size() );
244 }
245 ptr += attr.size();
246 }
247 }
248
249 //
250 Q_ASSERT( nPoints == outData.size() / request.attributes().pointRecordSize() );
251
252 return outData;
253}
254
256{
257 const QString name = attribute.name().toUpper();
258
259 if ( name == QLatin1String( "INTENSITY" ) )
260 return value >= 0 && value <= 65535;
261 if ( name == QLatin1String( "RETURNNUMBER" ) )
262 return value >= 0 && value <= 15;
263 if ( name == QLatin1String( "NUMBEROFRETURNS" ) )
264 return value >= 0 && value <= 15;
265 if ( name == QLatin1String( "SCANCHANNEL" ) )
266 return value >= 0 && value <= 3;
267 if ( name == QLatin1String( "SCANDIRECTIONFLAG" ) )
268 return value >= 0 && value <= 1;
269 if ( name == QLatin1String( "EDGEOFFLIGHTLINE" ) )
270 return value >= 0 && value <= 1;
271 if ( name == QLatin1String( "CLASSIFICATION" ) )
272 return value >= 0 && value <= 255;
273 if ( name == QLatin1String( "USERDATA" ) )
274 return value >= 0 && value <= 255;
275 if ( name == QLatin1String( "SCANANGLE" ) )
276 return value >= -30'000 && value <= 30'000;
277 if ( name == QLatin1String( "POINTSOURCEID" ) )
278 return value >= 0 && value <= 65535;
279 if ( name == QLatin1String( "GPSTIME" ) )
280 return value >= 0;
281 if ( name == QLatin1String( "SYNTHETIC" ) )
282 return value >= 0 && value <= 1;
283 if ( name == QLatin1String( "KEYPOINT" ) )
284 return value >= 0 && value <= 1;
285 if ( name == QLatin1String( "WITHHELD" ) )
286 return value >= 0 && value <= 1;
287 if ( name == QLatin1String( "OVERLAP" ) )
288 return value >= 0 && value <= 1;
289 if ( name == QLatin1String( "RED" ) )
290 return value >= 0 && value <= 65535;
291 if ( name == QLatin1String( "GREEN" ) )
292 return value >= 0 && value <= 65535;
293 if ( name == QLatin1String( "BLUE" ) )
294 return value >= 0 && value <= 65535;
295
296 return true;
297}
Collection of point cloud attributes.
int pointRecordSize() const
Returns total size of record.
const QgsPointCloudAttribute * find(const QString &attributeName, int &offset) const
Finds the attribute with the name.
QVector< QgsPointCloudAttribute > attributes() const
Returns all attributes.
int indexOf(const QString &name) const
Returns the index of the attribute with the specified name.
Attribute for point cloud data pair of name and size in bytes.
int size() const
Returns size of the attribute in bytes.
QString name() const
Returns name of the attribute.
DataType type() const
Returns the data type.
The QgsPointCloudEditingIndex class is a QgsPointCloudIndex that is used as an editing buffer when ed...
bool updateNodeData(const QHash< QgsPointCloudNodeId, QByteArray > &data)
Tries to update the data for the specified nodes.
QgsPointCloudNode getNode(const QgsPointCloudNodeId &id) const
Returns object for a given node.
bool hasNode(const QgsPointCloudNodeId &id) const
Returns whether the octree contain given node.
QgsAbstractPointCloudIndex * get()
Returns pointer to the implementation class.
QgsPointCloudAttributeCollection attributes() const
Returns all attributes that are stored in the file.
QgsPointCloudLayerEditUtils(QgsPointCloudLayer *layer)
Ctor.
static QByteArray dataForAttributes(const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request)
Takes data comprising of allAttributes and returns a QByteArray with data only for the attributes inc...
static bool isAttributeValueValid(const QgsPointCloudAttribute &attribute, double value)
Check if value is within proper range for the attribute.
bool changeAttributeValue(const QgsPointCloudNodeId &n, const QVector< int > &points, const QgsPointCloudAttribute &attribute, double value)
Attempts to modify attribute values for specific points in the editing buffer.
Represents a map layer supporting display of point clouds.
Represents a indexed point cloud node's position in octree.
bool isValid() const
Returns whether node is valid.
qint64 pointCount() const
Returns number of points contained in node data.
Point cloud data request.
QgsPointCloudAttributeCollection attributes() const
Returns attributes.