QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgseptdecoder.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointcloudrenderer.cpp
3 --------------------
4 begin : October 2020
5 copyright : (C) 2020 by Peter Petrik
6 email : zilolv at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgseptdecoder.h"
19#include "qgslazdecoder.h"
21#include "qgsvector3d.h"
22#include "qgspointcloudexpression.h"
23#include "qgsrectangle.h"
24
25#include <QFile>
26
27#include <zstd.h>
28
29
31
32std::unique_ptr<QgsPointCloudBlock> decompressBinary_( const QByteArray &dataUncompressed, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
33{
34 const std::size_t pointRecordSize = attributes.pointRecordSize( );
35 const std::size_t requestedPointRecordSize = requestedAttributes.pointRecordSize();
36 const int count = dataUncompressed.size() / pointRecordSize;
37 QByteArray data;
38 data.resize( requestedPointRecordSize * count );
39 char *destinationBuffer = data.data();
40 const char *s = dataUncompressed.data();
41
42 const QVector<QgsPointCloudAttribute> requestedAttributesVector = requestedAttributes.attributes();
43
44 // calculate input attributes and offsets once in advance
45
46 struct AttributeData
47 {
48 AttributeData( int inputOffset, int inputSize, QgsPointCloudAttribute::DataType inputType, int requestedSize, QgsPointCloudAttribute::DataType requestedType )
49 : inputOffset( inputOffset )
50 , inputSize( inputSize )
51 , inputType( inputType )
52 , requestedSize( requestedSize )
53 , requestedType( requestedType )
54 {}
55
56 int inputOffset;
57 int inputSize;
59 int requestedSize;
61 };
62
63 std::vector< AttributeData > attributeData;
64 attributeData.reserve( requestedAttributesVector.size() );
65 for ( const QgsPointCloudAttribute &requestedAttribute : requestedAttributesVector )
66 {
67 int inputAttributeOffset;
68 const QgsPointCloudAttribute *inputAttribute = attributes.find( requestedAttribute.name(), inputAttributeOffset );
69 if ( !inputAttribute )
70 {
71 return nullptr;
72 }
73 attributeData.emplace_back( AttributeData( inputAttributeOffset, inputAttribute->size(), inputAttribute->type(),
74 requestedAttribute.size(), requestedAttribute.type() ) );
75 }
76
77 int skippedPoints = 0;
78 std::unique_ptr< QgsPointCloudBlock > block = std::make_unique< QgsPointCloudBlock >(
79 count,
80 requestedAttributes,
81 data, scale, offset
82 );
83
84 const bool filterIsValid = filterExpression.isValid();
85 if ( !filterExpression.prepare( block.get() ) && filterIsValid )
86 {
87 // skip processing if the expression cannot be prepared
88 block->setPointCount( 0 );
89 return block;
90 }
91
92 int xAttributeOffset, yAttributeOffset;
93 const QgsPointCloudAttribute *attributeX = nullptr;
94 const QgsPointCloudAttribute *attributeY = nullptr;
95 const bool hasFilterRect = !filterRect.isEmpty();
96 if ( hasFilterRect )
97 {
98 attributeX = requestedAttributes.find( QLatin1String( "X" ), xAttributeOffset );
99 attributeY = requestedAttributes.find( QLatin1String( "Y" ), yAttributeOffset );
100 filterRect.setXMinimum( ( filterRect.xMinimum() - offset.x() ) / scale.x() );
101 filterRect.setXMaximum( ( filterRect.xMaximum() - offset.x() ) / scale.x() );
102 filterRect.setYMinimum( ( filterRect.yMinimum() - offset.y() ) / scale.y() );
103 filterRect.setYMaximum( ( filterRect.yMaximum() - offset.y() ) / scale.y() );
104 }
105
106 // now loop through points
107 size_t outputOffset = 0;
108 for ( int i = 0; i < count; ++i )
109 {
110 for ( const AttributeData &attribute : attributeData )
111 {
112 lazSerialize_( destinationBuffer, outputOffset,
113 attribute.requestedType, s,
114 attribute.inputType, attribute.inputSize, i * pointRecordSize + attribute.inputOffset );
115
116 outputOffset += attribute.requestedSize;
117 }
118
119 // check if point needs to be filtered out
120 bool skipThisPoint = false;
121 if ( hasFilterRect && attributeX && attributeY )
122 {
123 const double x = attributeX->convertValueToDouble( destinationBuffer + outputOffset - requestedPointRecordSize + xAttributeOffset );
124 const double y = attributeY->convertValueToDouble( destinationBuffer + outputOffset - requestedPointRecordSize + yAttributeOffset );
125 if ( !filterRect.contains( x, y ) )
126 skipThisPoint = true;
127 }
128 if ( !skipThisPoint && filterIsValid )
129 {
130 // we're always evaluating the last written point in the buffer
131 double eval = filterExpression.evaluate( i - skippedPoints );
132 if ( !eval || std::isnan( eval ) )
133 skipThisPoint = true;
134 }
135 if ( skipThisPoint )
136 {
137 // if the point is filtered out, rewind the offset so the next point is written over it
138 outputOffset -= requestedPointRecordSize;
139 ++skippedPoints;
140 }
141 }
142 block->setPointCount( count - skippedPoints );
143 return block;
144}
145
146std::unique_ptr<QgsPointCloudBlock> QgsEptDecoder::decompressBinary( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
147{
148 if ( ! QFile::exists( filename ) )
149 return nullptr;
150
151 QFile f( filename );
152 const bool r = f.open( QIODevice::ReadOnly );
153 if ( !r )
154 return nullptr;
155
156 const QByteArray dataUncompressed = f.read( f.size() );
157 return decompressBinary_( dataUncompressed, attributes, requestedAttributes, scale, offset, filterExpression, filterRect );
158}
159
160std::unique_ptr<QgsPointCloudBlock> QgsEptDecoder::decompressBinary( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
161{
162 return decompressBinary_( data, attributes, requestedAttributes, scale, offset, filterExpression, filterRect );
163}
164
165/* *************************************************************************************** */
166
167QByteArray decompressZtdStream( const QByteArray &dataCompressed )
168{
169 // NOTE: this is very primitive implementation because we expect the uncompressed
170 // data will be always less than 10 MB
171
172 const int MAXSIZE = 10000000;
173 QByteArray dataUncompressed;
174 dataUncompressed.resize( MAXSIZE );
175
176 ZSTD_DStream *strm = ZSTD_createDStream();
177 ZSTD_initDStream( strm );
178
179 ZSTD_inBuffer m_inBuf;
180 m_inBuf.src = reinterpret_cast<const void *>( dataCompressed.constData() );
181 m_inBuf.size = dataCompressed.size();
182 m_inBuf.pos = 0;
183
184 ZSTD_outBuffer outBuf { reinterpret_cast<void *>( dataUncompressed.data() ), MAXSIZE, 0 };
185 const size_t ret = ZSTD_decompressStream( strm, &outBuf, &m_inBuf );
186 Q_ASSERT( !ZSTD_isError( ret ) );
187 Q_ASSERT( outBuf.pos );
188 Q_ASSERT( outBuf.pos < outBuf.size );
189
190 ZSTD_freeDStream( strm );
191 dataUncompressed.resize( outBuf.pos );
192 return dataUncompressed;
193}
194
195std::unique_ptr<QgsPointCloudBlock> QgsEptDecoder::decompressZStandard( const QString &filename, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
196{
197 if ( ! QFile::exists( filename ) )
198 return nullptr;
199
200 QFile f( filename );
201 const bool r = f.open( QIODevice::ReadOnly );
202 if ( !r )
203 return nullptr;
204
205 const QByteArray dataCompressed = f.readAll();
206 const QByteArray dataUncompressed = decompressZtdStream( dataCompressed );
207 return decompressBinary_( dataUncompressed, attributes, requestedAttributes, scale, offset, filterExpression, filterRect );
208}
209
210std::unique_ptr<QgsPointCloudBlock> QgsEptDecoder::decompressZStandard( const QByteArray &data, const QgsPointCloudAttributeCollection &attributes, const QgsPointCloudAttributeCollection &requestedAttributes, const QgsVector3D &scale, const QgsVector3D &offset, QgsPointCloudExpression &filterExpression, QgsRectangle &filterRect )
211{
212 const QByteArray dataUncompressed = decompressZtdStream( data );
213 return decompressBinary_( dataUncompressed, attributes, requestedAttributes, scale, offset, filterExpression, filterRect );
214}
215
216/* *************************************************************************************** */
217
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.
Attribute for point cloud data pair of name and size in bytes.
DataType
Systems of unit measurement.
int size() const
Returns size of the attribute in bytes.
DataType type() const
Returns the data type.
double convertValueToDouble(const char *ptr) const
Returns the attribute's value as a double for data pointed to by ptr.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:385
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:159
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:149
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:164
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:154
bool isEmpty() const
Returns true if the rectangle has no area.
Definition: qgsrectangle.h:492
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition: qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:50
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:48