QGIS API Documentation 3.41.0-Master (f75d66fa9f9)
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 "qgslazdecoder.h"
19
20#include <lazperf/readers.hpp>
21#include <lazperf/writers.hpp>
22
23
24static void updatePoint( char *pointBuffer, int pointFormat, const QString &attributeName, double newValue )
25{
26 if ( attributeName == QLatin1String( "Intensity" ) ) // unsigned short
27 {
28 quint16 newValueShort = static_cast<quint16>( newValue );
29 memcpy( pointBuffer + 12, &newValueShort, sizeof( qint16 ) );
30 }
31 else if ( attributeName == QLatin1String( "ReturnNumber" ) ) // bits 0-3
32 {
33 uchar newByteValue = static_cast<uchar>( newValue ) & 0xf;
34 pointBuffer[14] = static_cast<char>( ( pointBuffer[14] & 0xf0 ) | newByteValue );
35 }
36 else if ( attributeName == QLatin1String( "NumberOfReturns" ) ) // bits 4-7
37 {
38 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0xf ) << 4;
39 pointBuffer[14] = static_cast<char>( ( pointBuffer[14] & 0xf ) | newByteValue );
40 }
41 else if ( attributeName == QLatin1String( "Synthetic" ) ) // bit 0
42 {
43 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 );
44 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfe ) | newByteValue );
45 }
46 else if ( attributeName == QLatin1String( "KeyPoint" ) ) // bit 1
47 {
48 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 1;
49 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfd ) | newByteValue );
50 }
51 else if ( attributeName == QLatin1String( "Withheld" ) ) // bit 2
52 {
53 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 2;
54 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xfb ) | newByteValue );
55 }
56 else if ( attributeName == QLatin1String( "Overlap" ) ) // bit 3
57 {
58 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 3;
59 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xf7 ) | newByteValue );
60 }
61 else if ( attributeName == QLatin1String( "ScannerChannel" ) ) // bits 4-5
62 {
63 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x3 ) << 4;
64 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xcf ) | newByteValue );
65 }
66 else if ( attributeName == QLatin1String( "ScanDirectionFlag" ) ) // bit 6
67 {
68 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 6;
69 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0xbf ) | newByteValue );
70 }
71 else if ( attributeName == QLatin1String( "EdgeOfFlightLine" ) ) // bit 7
72 {
73 uchar newByteValue = ( static_cast<uchar>( newValue ) & 0x1 ) << 7;
74 pointBuffer[15] = static_cast<char>( ( pointBuffer[15] & 0x7f ) | newByteValue );
75 }
76 else if ( attributeName == QLatin1String( "Classification" ) ) // unsigned char
77 {
78 pointBuffer[16] = static_cast<char>( static_cast<uchar>( newValue ) );
79 }
80 else if ( attributeName == QLatin1String( "UserData" ) ) // unsigned char
81 {
82 pointBuffer[17] = static_cast<char>( static_cast<uchar>( newValue ) );
83 }
84 else if ( attributeName == QLatin1String( "ScanAngleRank" ) ) // short
85 {
86 qint16 newValueShort = static_cast<qint16>( newValue );
87 memcpy( pointBuffer + 18, &newValueShort, sizeof( qint16 ) );
88 }
89 else if ( attributeName == QLatin1String( "PointSourceId" ) ) // unsigned short
90 {
91 quint16 newValueShort = static_cast<quint16>( newValue );
92 memcpy( pointBuffer + 20, &newValueShort, sizeof( quint16 ) );
93 }
94 else if ( attributeName == QLatin1String( "GpsTime" ) ) // double
95 {
96 memcpy( pointBuffer + 22, &newValue, sizeof( double ) );
97 }
98 else if ( pointFormat == 7 || pointFormat == 8 )
99 {
100 if ( attributeName == QLatin1String( "Red" ) ) // unsigned short
101 {
102 quint16 newValueShort = static_cast<quint16>( newValue );
103 memcpy( pointBuffer + 30, &newValueShort, sizeof( quint16 ) );
104 }
105 else if ( attributeName == QLatin1String( "Green" ) ) // unsigned short
106 {
107 quint16 newValueShort = static_cast<quint16>( newValue );
108 memcpy( pointBuffer + 32, &newValueShort, sizeof( quint16 ) );
109 }
110 else if ( attributeName == QLatin1String( "Blue" ) ) // unsigned short
111 {
112 quint16 newValueShort = static_cast<quint16>( newValue );
113 memcpy( pointBuffer + 34, &newValueShort, sizeof( quint16 ) );
114 }
115 else if ( pointFormat == 8 )
116 {
117 if ( attributeName == QLatin1String( "Infrared" ) ) // unsigned short
118 {
119 quint16 newValueShort = static_cast<quint16>( newValue );
120 memcpy( pointBuffer + 36, &newValueShort, sizeof( quint16 ) );
121 }
122 }
123 }
124}
125
126
127QByteArray QgsPointCloudLayerEditUtils::updateChunkValues( QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, const QgsPointCloudNodeId &n, const QHash<int, double> &pointValues, std::optional<double> newValue )
128{
129 Q_ASSERT( copcIndex->mHierarchy.contains( n ) );
130 Q_ASSERT( copcIndex->mHierarchyNodePos.contains( n ) );
131
132 int pointCount = copcIndex->mHierarchy[n];
133
134 lazperf::header14 header = copcIndex->mLazInfo->header();
135
136 lazperf::reader::chunk_decompressor decompressor( header.pointFormat(), header.ebCount(), chunkData.constData() );
137 lazperf::writer::chunk_compressor compressor( header.pointFormat(), header.ebCount() );
138
139 std::unique_ptr<char []> decodedData( new char[ header.point_record_length ] );
140
141 // only PDRF 6/7/8 is allowed by COPC
142 Q_ASSERT( header.pointFormat() == 6 || header.pointFormat() == 7 || header.pointFormat() == 8 );
143
144 QString attributeName = attribute.name();
145
146 for ( int i = 0 ; i < pointCount; ++i )
147 {
148 decompressor.decompress( decodedData.get() );
149 char *buf = decodedData.get();
150
151 if ( pointValues.contains( i ) )
152 {
153 // TODO: support for extrabytes attributes
154 updatePoint( buf, header.point_format_id, attributeName, newValue ? *newValue : pointValues[i] );
155 }
156
157 compressor.compress( decodedData.get() );
158 }
159
160 std::vector<unsigned char> data = compressor.done();
161 return QByteArray( ( const char * ) data.data(), ( int ) data.size() ); // QByteArray makes a deep copy
162}
163
164
165QByteArray QgsPointCloudLayerEditUtils::dataForAttributes( const QgsPointCloudAttributeCollection &allAttributes, const QByteArray &data, const QgsPointCloudRequest &request )
166{
167 const QVector<QgsPointCloudAttribute> attributes = allAttributes.attributes();
168 const int nPoints = data.size() / allAttributes.pointRecordSize();
169 const char *ptr = data.data();
170
171 QByteArray outData;
172 for ( int i = 0; i < nPoints; ++i )
173 {
174 for ( const QgsPointCloudAttribute &attr : attributes )
175 {
176 if ( request.attributes().indexOf( attr.name() ) >= 0 )
177 {
178 outData.append( ptr, attr.size() );
179 }
180 ptr += attr.size();
181 }
182 }
183
184 //
185 Q_ASSERT( nPoints == outData.size() / request.attributes().pointRecordSize() );
186
187 return outData;
188}
189
191{
192 const QString name = attribute.name().toUpper();
193
194 if ( name == QLatin1String( "INTENSITY" ) )
195 return value >= 0 && value <= 65535;
196 if ( name == QLatin1String( "RETURNNUMBER" ) )
197 return value >= 0 && value <= 15;
198 if ( name == QLatin1String( "NUMBEROFRETURNS" ) )
199 return value >= 0 && value <= 15;
200 if ( name == QLatin1String( "SCANNERCHANNEL" ) )
201 return value >= 0 && value <= 3;
202 if ( name == QLatin1String( "SCANDIRECTIONFLAG" ) )
203 return value >= 0 && value <= 1;
204 if ( name == QLatin1String( "EDGEOFFLIGHTLINE" ) )
205 return value >= 0 && value <= 1;
206 if ( name == QLatin1String( "CLASSIFICATION" ) )
207 return value >= 0 && value <= 255;
208 if ( name == QLatin1String( "USERDATA" ) )
209 return value >= 0 && value <= 255;
210 if ( name == QLatin1String( "SCANANGLERANK" ) )
211 return value >= -30'000 && value <= 30'000;
212 if ( name == QLatin1String( "POINTSOURCEID" ) )
213 return value >= 0 && value <= 65535;
214 if ( name == QLatin1String( "GPSTIME" ) )
215 return value >= 0;
216 if ( name == QLatin1String( "SYNTHETIC" ) )
217 return value >= 0 && value <= 1;
218 if ( name == QLatin1String( "KEYPOINT" ) )
219 return value >= 0 && value <= 1;
220 if ( name == QLatin1String( "WITHHELD" ) )
221 return value >= 0 && value <= 1;
222 if ( name == QLatin1String( "OVERLAP" ) )
223 return value >= 0 && value <= 1;
224 if ( name == QLatin1String( "RED" ) )
225 return value >= 0 && value <= 65535;
226 if ( name == QLatin1String( "GREEN" ) )
227 return value >= 0 && value <= 65535;
228 if ( name == QLatin1String( "BLUE" ) )
229 return value >= 0 && value <= 65535;
230 if ( name == QLatin1String( "INFRARED" ) )
231 return value >= 0 && value <= 65535;
232
233 return true;
234}
Collection of point cloud attributes.
int pointRecordSize() const
Returns total size of record.
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.
QString name() const
Returns name of the attribute.
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 QByteArray updateChunkValues(QgsCopcPointCloudIndex *copcIndex, const QByteArray &chunkData, const QgsPointCloudAttribute &attribute, const QgsPointCloudNodeId &n, const QHash< int, double > &pointValues, std::optional< double > newValue=std::nullopt)
Sets new classification value for the given points in voxel and return updated chunk data.
static bool isAttributeValueValid(const QgsPointCloudAttribute &attribute, double value)
Check if value is within proper range for the attribute.
Represents a indexed point cloud node's position in octree.
Point cloud data request.
QgsPointCloudAttributeCollection attributes() const
Returns attributes.