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