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