QGIS API Documentation 3.41.0-Master (1deb1daf037)
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>( std::round( newValue / 0.006 ) ); // copc stores angle in 0.006deg increments
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
165{
166 const QString name = attribute.name().toUpper();
167
168 if ( name == QLatin1String( "INTENSITY" ) )
169 return value >= 0 && value <= 65535;
170 if ( name == QLatin1String( "RETURNNUMBER" ) )
171 return value >= 0 && value <= 15;
172 if ( name == QLatin1String( "NUMBEROFRETURNS" ) )
173 return value >= 0 && value <= 15;
174 if ( name == QLatin1String( "SCANNERCHANNEL" ) )
175 return value >= 0 && value <= 3;
176 if ( name == QLatin1String( "SCANDIRECTIONFLAG" ) )
177 return value >= 0 && value <= 1;
178 if ( name == QLatin1String( "EDGEOFFLIGHTLINE" ) )
179 return value >= 0 && value <= 1;
180 if ( name == QLatin1String( "CLASSIFICATION" ) )
181 return value >= 0 && value <= 255;
182 if ( name == QLatin1String( "USERDATA" ) )
183 return value >= 0 && value <= 255;
184 if ( name == QLatin1String( "SCANANGLERANK" ) )
185 return value >= -180 && value <= 180;
186 if ( name == QLatin1String( "POINTSOURCEID" ) )
187 return value >= 0 && value <= 65535;
188 if ( name == QLatin1String( "GPSTIME" ) )
189 return value >= 0;
190 if ( name == QLatin1String( "SYNTHETIC" ) )
191 return value >= 0 && value <= 1;
192 if ( name == QLatin1String( "KEYPOINT" ) )
193 return value >= 0 && value <= 1;
194 if ( name == QLatin1String( "WITHHELD" ) )
195 return value >= 0 && value <= 1;
196 if ( name == QLatin1String( "OVERLAP" ) )
197 return value >= 0 && value <= 1;
198 if ( name == QLatin1String( "RED" ) )
199 return value >= 0 && value <= 65535;
200 if ( name == QLatin1String( "GREEN" ) )
201 return value >= 0 && value <= 65535;
202 if ( name == QLatin1String( "BLUE" ) )
203 return value >= 0 && value <= 65535;
204 if ( name == QLatin1String( "INFRARED" ) )
205 return value >= 0 && value <= 65535;
206
207 return true;
208}
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 a indexed point cloud node's position in octree.