QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgspointclouddataprovider.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspointclouddataprovider.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
19
20#include <mutex>
21
22#include "qgis.h"
24#include "qgsgeometry.h"
25#include "qgsgeos.h"
26#include "qgspointcloudindex.h"
29#include "qgsthreadingutils.h"
30
31#include <QDebug>
32#include <QString>
33#include <QtConcurrent/QtConcurrentMap>
34#include <QtMath>
35
36#include "moc_qgspointclouddataprovider.cpp"
37
38using namespace Qt::StringLiterals;
39
47
49
56
58{
60
61 QgsPointCloudIndex lIndex = index();
62 return lIndex.isValid();
63}
64
71
73{
75
76 return QVariantMap();
77}
78
80{
82
83 return nullptr;
84}
85
87{
88 static QMap< int, QString > sCodes
89 {
90 {0, u"Created, Never Classified"_s},
91 {1, u"Unclassified"_s},
92 {2, u"Ground"_s},
93 {3, u"Low Vegetation"_s},
94 {4, u"Medium Vegetation"_s},
95 {5, u"High Vegetation"_s},
96 {6, u"Building"_s},
97 {7, u"Low Point (Low Noise)"_s},
98 {8, u"Reserved"_s},
99 {9, u"Water"_s},
100 {10, u"Rail"_s},
101 {11, u"Road Surface"_s},
102 {12, u"Reserved"_s},
103 {13, u"Wire - Guard (Shield)"_s},
104 {14, u"Wire - Conductor (Phase)"_s},
105 {15, u"Transmission Tower"_s},
106 {16, u"Wire-Structure Connector (Insulator)"_s},
107 {17, u"Bridge Deck"_s},
108 {18, u"High Noise"_s},
109 };
110
111 static std::once_flag initialized;
112 std::call_once( initialized, []( )
113 {
114 for ( int i = 19; i <= 63; ++i )
115 sCodes.insert( i, u"Reserved"_s );
116 for ( int i = 64; i <= 255; ++i )
117 sCodes.insert( i, u"User Definable"_s );
118 } );
119
120 return sCodes;
121}
122
124{
125 static QMap< int, QString > sCodes
126 {
127 {0, QObject::tr( "Created, Never Classified" )},
128 {1, QObject::tr( "Unclassified" )},
129 {2, QObject::tr( "Ground" )},
130 {3, QObject::tr( "Low Vegetation" )},
131 {4, QObject::tr( "Medium Vegetation" )},
132 {5, QObject::tr( "High Vegetation" )},
133 {6, QObject::tr( "Building" )},
134 {7, QObject::tr( "Low Point (Noise)" )},
135 {8, QObject::tr( "Reserved" )},
136 {9, QObject::tr( "Water" )},
137 {10, QObject::tr( "Rail" )},
138 {11, QObject::tr( "Road Surface" )},
139 {12, QObject::tr( "Reserved" )},
140 {13, QObject::tr( "Wire - Guard (Shield)" )},
141 {14, QObject::tr( "Wire - Conductor (Phase)" )},
142 {15, QObject::tr( "Transmission Tower" )},
143 {16, QObject::tr( "Wire-Structure Connector (Insulator)" )},
144 {17, QObject::tr( "Bridge Deck" )},
145 {18, QObject::tr( "High Noise" )},
146 };
147
148 static std::once_flag initialized;
149 std::call_once( initialized, []( )
150 {
151 for ( int i = 19; i <= 63; ++i )
152 sCodes.insert( i, QObject::tr( "Reserved" ) );
153 for ( int i = 64; i <= 255; ++i )
154 sCodes.insert( i, QObject::tr( "User Definable" ) );
155 } );
156
157 return sCodes;
158}
159
161{
162 static const QMap< int, QString > sCodes
163 {
164 {0, u"No color or time stored"_s},
165 {1, u"Time is stored"_s},
166 {2, u"Color is stored"_s},
167 {3, u"Color and time are stored"_s},
168 {6, u"Time is stored"_s},
169 {7, u"Time and color are stored)"_s},
170 {8, u"Time, color and near infrared are stored"_s},
171 };
172
173 return sCodes;
174}
175
177{
178 static const QMap< int, QString > sCodes
179 {
180 {0, QObject::tr( "No color or time stored" )},
181 {1, QObject::tr( "Time is stored" )},
182 {2, QObject::tr( "Color is stored" )},
183 {3, QObject::tr( "Color and time are stored" )},
184 {6, QObject::tr( "Time is stored" )},
185 {7, QObject::tr( "Time and color are stored)" )},
186 {8, QObject::tr( "Time, color and near infrared are stored" )},
187 };
188
189 return sCodes;
190}
191
193{
195
196 QgsPointCloudIndex pcIndex = index();
197 if ( pcIndex )
198 {
199 return pcIndex.metadataStatistics();
200 }
202}
203
205{
206 return true;
207}
208
210{
211 return tr( "QGIS expression" );
212}
213
215{
216 // unfortunately we can't access QgsHelp here, that's a GUI class!
217 return QString();
218}
219
221{
222 typedef QVector<QMap<QString, QVariant>> result_type;
223
224 MapIndexedPointCloudNode( QgsPointCloudRequest &request, const QgsVector3D &indexScale, const QgsVector3D &indexOffset,
225 const QgsGeometry &extentGeometry, const QgsDoubleRange &zRange, QgsPointCloudIndex index, int pointsLimit )
226 : mRequest( request ), mIndexScale( indexScale ), mIndexOffset( indexOffset ), mExtentGeometry( extentGeometry ), mZRange( zRange ), mIndex( std::move( index ) ), mPointsLimit( pointsLimit )
227 { }
228
229 QVector<QVariantMap> operator()( QgsPointCloudNodeId n )
230 {
231 QVector<QVariantMap> acceptedPoints;
232 std::unique_ptr<QgsPointCloudBlock> block( mIndex.nodeData( n, mRequest ) );
233
234 if ( !block || pointsCount == mPointsLimit )
235 return acceptedPoints;
236
237 const char *ptr = block->data();
238 const QgsPointCloudAttributeCollection blockAttributes = block->attributes();
239 const std::size_t recordSize = blockAttributes.pointRecordSize();
240 int xOffset = 0, yOffset = 0, zOffset = 0;
241 const QgsPointCloudAttribute::DataType xType = blockAttributes.find( u"X"_s, xOffset )->type();
242 const QgsPointCloudAttribute::DataType yType = blockAttributes.find( u"Y"_s, yOffset )->type();
243 const QgsPointCloudAttribute::DataType zType = blockAttributes.find( u"Z"_s, zOffset )->type();
244 auto extentEngine = std::make_unique< QgsGeos >( mExtentGeometry.constGet() );
245 extentEngine->prepareGeometry();
246
247 std::optional<bool> copcTimeFlag = std::nullopt;
248 QVariantMap extraMetadata = mIndex.extraMetadata();
249 if ( extraMetadata.contains( u"CopcGpsTimeFlag"_s ) )
250 copcTimeFlag = extraMetadata[ u"CopcGpsTimeFlag"_s ].toBool();
251
252 for ( int i = 0; i < block->pointCount() && pointsCount < mPointsLimit; ++i )
253 {
254 double x, y, z;
255 QgsPointCloudAttribute::getPointXYZ( ptr, i, recordSize, xOffset, xType, yOffset, yType, zOffset, zType, block->scale(), block->offset(), x, y, z );
256
257 if ( mZRange.contains( z ) && extentEngine->contains( x, y ) )
258 {
259 QVariantMap pointAttr = QgsPointCloudAttribute::getAttributeMap( ptr, i * recordSize, blockAttributes );
260 pointAttr[ u"X"_s ] = x;
261 pointAttr[ u"Y"_s ] = y;
262 pointAttr[ u"Z"_s ] = z;
263
264
265 if ( copcTimeFlag.has_value() )
266 {
267 const QDateTime gpsBaseTime = QDateTime::fromSecsSinceEpoch( 315964809, Qt::UTC );
268 constexpr int numberOfSecsInWeek = 3600 * 24 * 7;
269 // here we check the flag set in header to determine if we need to
270 // parse the time as GPS week time or GPS adjusted standard time
271 // however often times the flag is set wrong, so we determine if the value is bigger than the maximum amount of seconds in week then it has to be adjusted standard time
272 if ( *copcTimeFlag || pointAttr[u"GpsTime"_s].toDouble() > numberOfSecsInWeek )
273 {
274 const QString utcTime = gpsBaseTime.addSecs( static_cast<qint64>( pointAttr[u"GpsTime"_s].toDouble() + 1e9 ) ).toString( Qt::ISODate );
275 pointAttr[ u"GpsTime (raw)"_s] = pointAttr[u"GpsTime"_s];
276 pointAttr[ u"GpsTime"_s] = utcTime;
277 }
278 else
279 {
280 const QString weekTime = gpsBaseTime.addSecs( pointAttr[u"GpsTime"_s].toLongLong() ).toString( "ddd hh:mm:ss" );
281 pointAttr[ u"GpsTime (raw)"_s] = pointAttr[u"GpsTime"_s];
282 pointAttr[ u"GpsTime"_s] = weekTime;
283 }
284 }
285 pointsCount++;
286 acceptedPoints.push_back( pointAttr );
287 }
288 }
289 return acceptedPoints;
290 }
291
299 int pointsCount = 0;
300};
301
303 double maxError,
304 const QgsGeometry &extentGeometry,
305 const QgsDoubleRange &extentZRange, int pointsLimit )
306{
307 QVector<QVariantMap> acceptedPoints;
308
309 // Try sub-indexes first
310 for ( QgsPointCloudSubIndex &subidx : subIndexes() )
311 {
312 // Check if the sub-index is relevant and if it is loaded. We shouldn't
313 // need to identify points in unloaded indices.
314 QgsPointCloudIndex index = subidx.index();
315 if ( !index
316 || ( !subidx.zRange().overlaps( extentZRange ) )
317 || !subidx.polygonBounds().intersects( extentGeometry ) )
318 continue;
319 acceptedPoints.append( identify( index, maxError, extentGeometry, extentZRange, pointsLimit ) );
320 }
321
322 // Then look at main index
323 QgsPointCloudIndex mainIndex = index();
324 acceptedPoints.append( identify( mainIndex, maxError, extentGeometry, extentZRange, pointsLimit ) );
325
326 return acceptedPoints;
327}
328
330 QgsPointCloudIndex &index, double maxError,
331 const QgsGeometry &extentGeometry,
332 const QgsDoubleRange &extentZRange, int pointsLimit )
333{
335
336 QVector<QVariantMap> acceptedPoints;
337
338 if ( !index.isValid() )
339 return acceptedPoints;
340
341 const QgsPointCloudNode root = index.getNode( index.root() );
342 const QVector<QgsPointCloudNodeId> nodes = traverseTree( index, root, maxError, root.error(), extentGeometry, extentZRange );
343
344 const QgsPointCloudAttributeCollection attributeCollection = index.attributes();
345 QgsPointCloudRequest request;
346 request.setAttributes( attributeCollection );
347
348 acceptedPoints = QtConcurrent::blockingMappedReduced( nodes,
349 MapIndexedPointCloudNode( request, index.scale(), index.offset(), extentGeometry, extentZRange, index, pointsLimit ),
350 qOverload<const QVector<QMap<QString, QVariant>>&>( &QVector<QMap<QString, QVariant>>::append ),
351 QtConcurrent::UnorderedReduce );
352
353 return acceptedPoints;
354}
355
356QVector<QgsPointCloudNodeId> QgsPointCloudDataProvider::traverseTree(
357 const QgsPointCloudIndex &pc,
359 double maxError,
360 double nodeError,
361 const QgsGeometry &extentGeometry,
362 const QgsDoubleRange &extentZRange )
363{
365
366 QVector<QgsPointCloudNodeId> nodes;
367
368 const QgsBox3D nodeBounds = node.bounds();
369 const QgsDoubleRange nodeZRange( nodeBounds.zMinimum(), nodeBounds.zMaximum() );
370 if ( !extentZRange.overlaps( nodeZRange ) )
371 return nodes;
372
373 if ( !extentGeometry.intersects( nodeBounds.toRectangle() ) )
374 return nodes;
375
376 nodes.append( node.id() );
377
378 const double childrenError = nodeError / 2.0;
379 if ( childrenError < maxError )
380 return nodes;
381
382 for ( const QgsPointCloudNodeId &nn : node.children() )
383 {
384 const QgsPointCloudNode childNode = pc.getNode( nn );
385 if ( extentGeometry.intersects( childNode.bounds().toRectangle() ) )
386 nodes += traverseTree( pc, childNode, maxError, childrenError, extentGeometry, extentZRange );
387 }
388
389 return nodes;
390}
391
392bool QgsPointCloudDataProvider::setSubsetString( const QString &subset, bool updateFeatureCount )
393{
395
396 Q_UNUSED( updateFeatureCount )
398 if ( !i )
399 return false;
400
401 if ( !i.setSubsetString( subset ) )
402 return false;
403 mSubsetString = subset;
404 emit dataChanged();
405 return true;
406}
407
414
QFlags< DataProviderReadFlag > DataProviderReadFlags
Flags which control data provider construction.
Definition qgis.h:505
A 3-dimensional box composed of x, y, z coordinates.
Definition qgsbox3d.h:45
double zMaximum() const
Returns the maximum z value.
Definition qgsbox3d.h:261
QgsRectangle toRectangle() const
Converts the box to a 2D rectangle.
Definition qgsbox3d.h:381
double zMinimum() const
Returns the minimum z value.
Definition qgsbox3d.h:254
virtual Qgis::DataProviderFlags flags() const
Returns the generic data provider flags.
void dataChanged()
Emitted whenever a change is made to the data provider which may have caused changes in the provider'...
QgsDataProvider(const QString &uri=QString(), const QgsDataProvider::ProviderOptions &providerOptions=QgsDataProvider::ProviderOptions(), Qgis::DataProviderReadFlags flags=Qgis::DataProviderReadFlags())
Create a new dataprovider with the specified in the uri.
QgsDataSourceUri uri() const
Gets the data source specification.
virtual QgsRectangle extent() const =0
Returns the extent of the layer.
QgsRange which stores a range of double values.
Definition qgsrange.h:236
A geometry is the spatial representation of a feature.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
bool intersects(const QgsRectangle &rectangle) const
Returns true if this geometry exactly intersects with a rectangle.
A 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.
DataType
Systems of unit measurement.
static void getPointXYZ(const char *ptr, int i, std::size_t pointRecordSize, int xOffset, QgsPointCloudAttribute::DataType xType, int yOffset, QgsPointCloudAttribute::DataType yType, int zOffset, QgsPointCloudAttribute::DataType zType, const QgsVector3D &indexScale, const QgsVector3D &indexOffset, double &x, double &y, double &z)
Retrieves the x, y, z values for the point at index i.
static QVariantMap getAttributeMap(const char *data, std::size_t recordOffset, const QgsPointCloudAttributeCollection &attributeCollection)
Retrieves all the attributes of a point.
DataType type() const
Returns the data type.
QString subsetStringDialect() const override
Returns a user-friendly string describing the dialect which is supported for subset strings by the pr...
@ NoCapabilities
Provider has no capabilities.
bool setSubsetString(const QString &subset, bool updateFeatureCount=false) override
Set the subset string used to create a subset of features in the layer.
~QgsPointCloudDataProvider() override
QgsPointCloudDataProvider(const QString &uri, const QgsDataProvider::ProviderOptions &providerOptions, Qgis::DataProviderReadFlags flags=Qgis::DataProviderReadFlags())
Ctor.
static QMap< int, QString > dataFormatIds()
Returns the map of LAS data format ID to untranslated string value.
bool supportsSubsetString() const override
Returns true if the provider supports setting of subset strings.
virtual QVector< QgsPointCloudSubIndex > subIndexes()
Returns a list of sub indexes available if the provider supports multiple indexes,...
QVector< QVariantMap > identify(double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange=QgsDoubleRange(), int pointsLimit=1000)
Returns the list of points of the point cloud according to a zoom level defined by maxError (in layer...
QgsPointCloudStatistics metadataStatistics()
Returns the object containing the statistics metadata extracted from the dataset.
virtual QgsPointCloudIndex index() const
Returns the point cloud index associated with the provider.
QString subsetStringHelpUrl() const override
Returns a URL pointing to documentation describing the dialect which is supported for subset strings ...
QString subsetString() const override
Returns the subset definition string currently in use by the layer and used by the provider to limit ...
static QMap< int, QString > translatedDataFormatIds()
Returns the map of LAS data format ID to translated string value.
virtual QgsPointCloudDataProvider::Capabilities capabilities() const
Returns flags containing the supported capabilities for the data provider.
static QMap< int, QString > translatedLasClassificationCodes()
Returns the map of LAS classification code to translated string value, corresponding to the ASPRS Sta...
QString mSubsetString
String used to define a subset of the layer.
static QMap< int, QString > lasClassificationCodes()
Returns the map of LAS classification code to untranslated string value, corresponding to the ASPRS S...
virtual QgsPointCloudRenderer * createRenderer(const QVariantMap &configuration=QVariantMap()) const
Creates a new 2D point cloud renderer, using provider backend specific information.
bool hasValidIndex() const
Returns whether provider has index which is valid.
virtual QVariantMap originalMetadata() const
Returns a representation of the original metadata included in a point cloud dataset.
virtual QgsGeometry polygonBounds() const
Returns the polygon bounds of the layer.
Smart pointer for QgsAbstractPointCloudIndex.
QgsPointCloudNode getNode(const QgsPointCloudNodeId &id) const
Returns object for a given node.
Represents an indexed point cloud node's position in octree.
Keeps metadata for an indexed point cloud node.
QList< QgsPointCloudNodeId > children() const
Returns IDs of child nodes.
QgsPointCloudNodeId id() const
Returns node's ID (unique in index).
float error() const
Returns node's error in map units (used to determine in whether the node has enough detail for the cu...
QgsBox3D bounds() const
Returns node's bounding cube in CRS coords.
Abstract base class for 2d point cloud renderers.
Point cloud data request.
void setAttributes(const QgsPointCloudAttributeCollection &attributes)
Set attributes filter in the request.
Used to store statistics of a point cloud dataset.
bool overlaps(const QgsRange< T > &other) const
Returns true if this range overlaps another range.
Definition qgsrange.h:179
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
#define QGIS_PROTECT_QOBJECT_THREAD_ACCESS
QVector< QMap< QString, QVariant > > result_type
QVector< QVariantMap > operator()(QgsPointCloudNodeId n)
MapIndexedPointCloudNode(QgsPointCloudRequest &request, const QgsVector3D &indexScale, const QgsVector3D &indexOffset, const QgsGeometry &extentGeometry, const QgsDoubleRange &zRange, QgsPointCloudIndex index, int pointsLimit)
Setting options for creating vector data providers.