1 /***************************************************************************
2                          qgspointclouddataprovider.h
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 #ifndef QGSPOINTCLOUDDATAPROVIDER_H
19 #define QGSPOINTCLOUDDATAPROVIDER_H
20 
21 #include "qgis_core.h"
22 #include "qgsdataprovider.h"
23 #include "qgspointcloudattribute.h"
24 #include "qgsstatisticalsummary.h"
25 #include "qgspointcloudindex.h"
26 #include "qgspoint.h"
27 #include "qgsray3d.h"
28 #include <memory>
29 
30 class IndexedPointCloudNode;
31 class QgsPointCloudIndex;
32 class QgsPointCloudRenderer;
33 class QgsGeometry;
34 
35 /**
36  * \ingroup core
37  * \brief Base class for providing data for QgsPointCloudLayer
38  *
39  * Responsible for reading native point cloud data and returning the indexed data.
40  *
41  * \note The API is considered EXPERIMENTAL and can be changed without a notice
42  *
43  * \since QGIS 3.18
44  */
45 class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider
46 {
47     Q_OBJECT
48   public:
49 
50     /**
51      * Capabilities that providers may implement.
52      */
53     enum Capability
54     {
55       NoCapabilities = 0,       //!< Provider has no capabilities
56       ReadLayerMetadata = 1 << 0, //!< Provider can read layer metadata from data store.
57       WriteLayerMetadata = 1 << 1, //!< Provider can write layer metadata to the data store. See QgsDataProvider::writeLayerMetadata()
58       CreateRenderer = 1 << 2, //!< Provider can create 2D renderers using backend-specific formatting information. See QgsPointCloudDataProvider::createRenderer().
59     };
60 
61     Q_DECLARE_FLAGS( Capabilities, Capability )
62 
63     /**
64      * Point cloud index state
65      */
66     enum PointCloudIndexGenerationState
67     {
68       NotIndexed = 0, //!< Provider has no index available
69       Indexing = 1 << 0, //!< Provider try to index the source data
70       Indexed = 1 << 1 //!< The index is ready to be used
71     };
72 
73     //! Ctor
74     QgsPointCloudDataProvider( const QString &uri,
75                                const QgsDataProvider::ProviderOptions &providerOptions,
76                                QgsDataProvider::ReadFlags flags = QgsDataProvider::ReadFlags() );
77 
78     ~QgsPointCloudDataProvider() override;
79 
80 #ifndef SIP_RUN
81 
82     /**
83      * Returns the list of points of the point cloud according to a zoom level
84      * defined by \a maxError (in layer coordinates), an extent \a geometry in the 2D plane
85      * and a range \a extentZRange for z values. The function will try to limit
86      * the number of points returned to \a pointsLimit points
87      *
88      * \a maxErrorPixels : maximum accepted error factor in pixels
89      *
90      * \note this function does not handle elevation properties and you need to
91      * change elevation coordinates yourself after returning from the function
92      */
93     QVector<QVariantMap> identify( double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange = QgsDoubleRange(), int pointsLimit = 1000 );
94 #else
95 
96     /**
97      * Returns the list of points of the point cloud according to a zoom level
98      * defined by \a maxError (in layer coordinates), an extent \a geometry in the 2D plane
99      * and a range \a extentZRange for z values. The function will try to limit
100      * the number of points returned to \a pointsLimit points
101      *
102      * \a maxErrorPixels : maximum accepted error factor in pixels
103      *
104      * \note this function does not handle elevation properties and you need to
105      * change elevation coordinates yourself after returning from the function
106      */
107     SIP_PYLIST identify( float maxErrorInMapCoords, QgsGeometry extentGeometry, const QgsDoubleRange extentZRange = QgsDoubleRange(), int pointsLimit = 1000 );
108     % MethodCode
109     {
110       QVector<QMap<QString, QVariant>> res = sipCpp->identify( a0, *a1, *a2, a3 );
111       sipRes = PyList_New( res.size() );
112       for ( int i = 0; i < res.size(); ++i )
113       {
114         PyObject *dict = PyDict_New();
115         for ( QString key : res[i].keys() )
116         {
117           PyObject *keyObj = sipConvertFromNewType( new QString( key ), sipType_QString, Py_None );
118           PyObject *valObj = sipConvertFromNewType( new QVariant( res[i][key] ), sipType_QVariant, Py_None );
119           PyDict_SetItem( dict, keyObj, valObj );
120         }
121         PyList_SET_ITEM( sipRes, i, dict );
122       }
123     }
124     % End
125 #endif
126 
127     /**
128      * Returns flags containing the supported capabilities for the data provider.
129      */
130     virtual QgsPointCloudDataProvider::Capabilities capabilities() const;
131 
132     /**
133      * Returns the attributes available from this data provider.
134      * May return empty collection until pointCloudIndexLoaded() is emitted
135      */
136     virtual QgsPointCloudAttributeCollection attributes() const = 0;
137 
138     /**
139      * Triggers loading of the point cloud index
140      *
141      * \sa index()
142      */
143     virtual void loadIndex( ) = 0;
144 
145     /**
146      * Triggers generation of the point cloud index
147      *
148      * emits indexGenerationStateChanged()
149      *
150      * \sa index()
151      */
152     virtual void generateIndex( ) = 0;
153 
154 
155     /**
156      * Gets the current index generation state
157      */
158     virtual PointCloudIndexGenerationState indexingState( ) = 0;
159 
160     /**
161      * Returns the point cloud index associated with the provider.
162      *
163      * Can be nullptr (e.g. the index is being created)
164      *
165      * \note Not available in Python bindings
166      */
index()167     virtual QgsPointCloudIndex *index() const SIP_SKIP {return nullptr;}
168 
169     /**
170      * Returns whether provider has index which is valid
171      */
172     bool hasValidIndex() const;
173 
174     /**
175      * Returns the total number of points available in the dataset.
176      */
177     virtual qint64 pointCount() const = 0;
178 
179     /**
180      * Returns the polygon bounds of the layer. The CRS of the returned geometry will match the provider's crs().
181      *
182      * This method will return the best approximation for the actual bounds of points contained in the
183      * dataset available from the provider's metadata. This may match the bounding box rectangle returned
184      * by extent(), or for some datasets a "convex hull" style polygon representing a more precise bounds
185      * will be returned.
186      *
187      * This method will not attempt to calculate the data bounds, rather it will return only whatever precomputed bounds
188      * are included in the data source's metadata.
189      */
190     virtual QgsGeometry polygonBounds() const;
191 
192     /**
193      * Returns a representation of the original metadata included in a point cloud dataset.
194      *
195      * This is a free-form dictionary of values, the contents and structure of which will vary by provider and
196      * dataset.
197      */
198     virtual QVariantMap originalMetadata() const;
199 
200     /**
201      * Creates a new 2D point cloud renderer, using provider backend specific information.
202      *
203      * The \a configuration map can be used to pass provider-specific configuration maps to the provider to
204      * allow customization of the returned renderer. Support and format of \a configuration varies by provider.
205      *
206      * When called with an empty \a configuration map the provider's default renderer will be returned.
207      *
208      * This method returns a new renderer and the caller takes ownership of the returned object.
209      *
210      * Only providers which report the CreateRenderer capability will return a 2D renderer. Other
211      * providers will return NULLPTR.
212      */
213     virtual QgsPointCloudRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const SIP_FACTORY;
214 #ifndef SIP_RUN
215 
216     /**
217      * Returns a statistic for the specified \a attribute, taken only from the metadata of the point cloud
218      * data source.
219      *
220      * This method will not perform any statistical calculations, rather it will return only precomputed attribute
221      * statistics which are included in the data source's metadata. Not all data sources include this information
222      * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
223      *
224      * If no matching precalculated statistic is available then an invalid variant will be returned.
225      */
226     virtual QVariant metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const;
227 #else
228 
229     /**
230      * Returns a statistic for the specified \a attribute, taken only from the metadata of the point cloud
231      * data source.
232      *
233      * This method will not perform any statistical calculations, rather it will return only precomputed attribute
234      * statistics which are included in the data source's metadata. Not all data sources include this information
235      * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
236      *
237      * \throws ValueError if no matching precalculated statistic is available for the attribute.
238      */
239     SIP_PYOBJECT metadataStatistic( const QString &attribute, QgsStatisticalSummary::Statistic statistic ) const;
240     % MethodCode
241     {
242       const QVariant res = sipCpp->metadataStatistic( *a0, a1 );
243       if ( !res.isValid() )
244       {
245         PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() );
246         sipIsErr = 1;
247       }
248       else
249       {
250         QVariant *v = new QVariant( res );
251         sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None );
252       }
253     }
254     % End
255 #endif
256 
257     /**
258      * Returns a list of existing classes which are present for the specified \a attribute, taken only from the
259      * metadata of the point cloud data source.
260      *
261      * This method will not perform any classification or scan for available classes, rather it will return only
262      * precomputed classes which are included in the data source's metadata. Not all data sources include this information
263      * in the metadata.
264      */
265     virtual QVariantList metadataClasses( const QString &attribute ) const;
266 
267 
268 #ifndef SIP_RUN
269 
270     /**
271      * Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud
272      * data source.
273      *
274      * This method will not perform any statistical calculations, rather it will return only precomputed class
275      * statistics which are included in the data source's metadata. Not all data sources include this information
276      * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
277      *
278      * If no matching precalculated statistic is available then an invalid variant will be returned.
279      */
280     virtual QVariant metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;
281 #else
282 
283     /**
284      * Returns a statistic for one class \a value from the specified \a attribute, taken only from the metadata of the point cloud
285      * data source.
286      * This method will not perform any statistical calculations, rather it will return only precomputed class
287      * statistics which are included in the data source's metadata. Not all data sources include this information
288      * in the metadata, and even for sources with statistical metadata only some \a statistic values may be available.
289      *
290      * \throws ValueError if no matching precalculated statistic is available for the attribute.
291      */
292     SIP_PYOBJECT metadataClassStatistic( const QString &attribute, const QVariant &value, QgsStatisticalSummary::Statistic statistic ) const;
293     % MethodCode
294     {
295       const QVariant res = sipCpp->metadataClassStatistic( *a0, *a1, a2 );
296       if ( !res.isValid() )
297       {
298         PyErr_SetString( PyExc_ValueError, QStringLiteral( "Statistic is not available" ).toUtf8().constData() );
299         sipIsErr = 1;
300       }
301       else
302       {
303         QVariant *v = new QVariant( res );
304         sipRes = sipConvertFromNewType( v, sipType_QVariant, Py_None );
305       }
306     }
307     % End
308 #endif
309 
310     /**
311      * Returns the map of LAS classification code to untranslated string value, corresponding to the ASPRS Standard
312      * Lidar Point Classes.
313      *
314      * \see translatedLasClassificationCodes()
315      */
316     static QMap< int, QString > lasClassificationCodes();
317 
318     /**
319      * Returns the map of LAS classification code to translated string value, corresponding to the ASPRS Standard
320      * Lidar Point Classes.
321      *
322      * \see lasClassificationCodes()
323      */
324     static QMap< int, QString > translatedLasClassificationCodes();
325 
326     /**
327      * Returns the map of LAS data format ID to untranslated string value.
328      *
329      * \see translatedDataFormatIds()
330      */
331     static QMap< int, QString > dataFormatIds();
332 
333     /**
334      * Returns the map of LAS data format ID to translated string value.
335      *
336      * \see dataFormatIds()
337      */
338     static QMap< int, QString > translatedDataFormatIds();
339 
340   signals:
341 
342     /**
343      * Emitted when point cloud generation state is changed
344      */
345     void indexGenerationStateChanged( PointCloudIndexGenerationState state );
346 
347   private:
348     QVector<IndexedPointCloudNode> traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxError, double nodeError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange );
349 };
350 
351 #endif // QGSMESHDATAPROVIDER_H
352