Branch data Line data Source code
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 "qgspointcloudrenderer.h"
19 : : #include "qgspointcloudrendererregistry.h"
20 : : #include "qgsapplication.h"
21 : : #include "qgssymbollayerutils.h"
22 : : #include "qgspointcloudlayer.h"
23 : : #include "qgspointcloudindex.h"
24 : : #include "qgspointcloudlayerelevationproperties.h"
25 : : #include "qgslogger.h"
26 : : #include "qgscircle.h"
27 : : #include <QThread>
28 : : #include <QPointer>
29 : :
30 : 0 : QgsPointCloudRenderContext::QgsPointCloudRenderContext( QgsRenderContext &context, const QgsVector3D &scale, const QgsVector3D &offset, double zValueScale, double zValueFixedOffset, QgsFeedback *feedback )
31 : 0 : : mRenderContext( context )
32 : 0 : , mScale( scale )
33 : 0 : , mOffset( offset )
34 : 0 : , mZValueScale( zValueScale )
35 : 0 : , mZValueFixedOffset( zValueFixedOffset )
36 : 0 : , mFeedback( feedback )
37 : : {
38 : :
39 : 0 : }
40 : :
41 : 0 : long QgsPointCloudRenderContext::pointsRendered() const
42 : : {
43 : 0 : return mPointsRendered;
44 : : }
45 : :
46 : 0 : void QgsPointCloudRenderContext::incrementPointsRendered( long count )
47 : : {
48 : 0 : mPointsRendered += count;
49 : 0 : }
50 : :
51 : 0 : void QgsPointCloudRenderContext::setAttributes( const QgsPointCloudAttributeCollection &attributes )
52 : : {
53 : 0 : mAttributes = attributes;
54 : 0 : mPointRecordSize = mAttributes.pointRecordSize();
55 : :
56 : : // fetch offset for x/y/z attributes
57 : 0 : attributes.find( QStringLiteral( "X" ), mXOffset );
58 : 0 : attributes.find( QStringLiteral( "Y" ), mYOffset );
59 : 0 : attributes.find( QStringLiteral( "Z" ), mZOffset );
60 : 0 : }
61 : :
62 : 0 : QgsPointCloudRenderer *QgsPointCloudRenderer::load( QDomElement &element, const QgsReadWriteContext &context )
63 : : {
64 : 0 : if ( element.isNull() )
65 : 0 : return nullptr;
66 : :
67 : : // load renderer
68 : 0 : const QString rendererType = element.attribute( QStringLiteral( "type" ) );
69 : :
70 : 0 : QgsPointCloudRendererAbstractMetadata *m = QgsApplication::pointCloudRendererRegistry()->rendererMetadata( rendererType );
71 : 0 : if ( !m )
72 : 0 : return nullptr;
73 : :
74 : 0 : std::unique_ptr< QgsPointCloudRenderer > r( m->createRenderer( element, context ) );
75 : 0 : return r.release();
76 : 0 : }
77 : :
78 : 0 : QSet<QString> QgsPointCloudRenderer::usedAttributes( const QgsPointCloudRenderContext & ) const
79 : : {
80 : 0 : return QSet< QString >();
81 : : }
82 : :
83 : 0 : void QgsPointCloudRenderer::startRender( QgsPointCloudRenderContext &context )
84 : : {
85 : : #ifdef QGISDEBUG
86 : : if ( !mThread )
87 : : {
88 : : mThread = QThread::currentThread();
89 : : }
90 : : else
91 : : {
92 : : Q_ASSERT_X( mThread == QThread::currentThread(), "QgsPointCloudRenderer::startRender", "startRender called in a different thread - use a cloned renderer instead" );
93 : : }
94 : : #endif
95 : :
96 : 0 : mPainterPenWidth = context.renderContext().convertToPainterUnits( pointSize(), pointSizeUnit(), pointSizeMapUnitScale() );
97 : :
98 : 0 : switch ( mPointSymbol )
99 : : {
100 : : case Square:
101 : : // for square point we always disable antialiasing -- it's not critical here and we benefit from the performance boost disabling it gives
102 : 0 : context.renderContext().painter()->setRenderHint( QPainter::Antialiasing, false );
103 : 0 : break;
104 : :
105 : : case Circle:
106 : 0 : break;
107 : : }
108 : 0 : }
109 : :
110 : 0 : void QgsPointCloudRenderer::stopRender( QgsPointCloudRenderContext & )
111 : : {
112 : : #ifdef QGISDEBUG
113 : : Q_ASSERT_X( mThread == QThread::currentThread(), "QgsPointCloudRenderer::stopRender", "stopRender called in a different thread - use a cloned renderer instead" );
114 : : #endif
115 : 0 : }
116 : :
117 : 0 : bool QgsPointCloudRenderer::legendItemChecked( const QString & )
118 : : {
119 : 0 : return false;
120 : : }
121 : :
122 : 0 : void QgsPointCloudRenderer::checkLegendItem( const QString &, bool )
123 : : {
124 : :
125 : 0 : }
126 : :
127 : 0 : double QgsPointCloudRenderer::maximumScreenError() const
128 : : {
129 : 0 : return mMaximumScreenError;
130 : : }
131 : :
132 : 0 : void QgsPointCloudRenderer::setMaximumScreenError( double error )
133 : : {
134 : 0 : mMaximumScreenError = error;
135 : 0 : }
136 : :
137 : 0 : QgsUnitTypes::RenderUnit QgsPointCloudRenderer::maximumScreenErrorUnit() const
138 : : {
139 : 0 : return mMaximumScreenErrorUnit;
140 : : }
141 : :
142 : 0 : void QgsPointCloudRenderer::setMaximumScreenErrorUnit( QgsUnitTypes::RenderUnit unit )
143 : : {
144 : 0 : mMaximumScreenErrorUnit = unit;
145 : 0 : }
146 : :
147 : 0 : QList<QgsLayerTreeModelLegendNode *> QgsPointCloudRenderer::createLegendNodes( QgsLayerTreeLayer * )
148 : : {
149 : 0 : return QList<QgsLayerTreeModelLegendNode *>();
150 : : }
151 : :
152 : 0 : QStringList QgsPointCloudRenderer::legendRuleKeys() const
153 : : {
154 : 0 : return QStringList();
155 : : }
156 : :
157 : 0 : void QgsPointCloudRenderer::copyCommonProperties( QgsPointCloudRenderer *destination ) const
158 : : {
159 : 0 : destination->setPointSize( mPointSize );
160 : 0 : destination->setPointSizeUnit( mPointSizeUnit );
161 : 0 : destination->setPointSizeMapUnitScale( mPointSizeMapUnitScale );
162 : 0 : destination->setMaximumScreenError( mMaximumScreenError );
163 : 0 : destination->setMaximumScreenErrorUnit( mMaximumScreenErrorUnit );
164 : 0 : destination->setPointSymbol( mPointSymbol );
165 : 0 : }
166 : :
167 : 0 : void QgsPointCloudRenderer::restoreCommonProperties( const QDomElement &element, const QgsReadWriteContext & )
168 : : {
169 : 0 : mPointSize = element.attribute( QStringLiteral( "pointSize" ), QStringLiteral( "1" ) ).toDouble();
170 : 0 : mPointSizeUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "pointSizeUnit" ), QStringLiteral( "MM" ) ) );
171 : 0 : mPointSizeMapUnitScale = QgsSymbolLayerUtils::decodeMapUnitScale( element.attribute( QStringLiteral( "pointSizeMapUnitScale" ), QString() ) );
172 : :
173 : 0 : mMaximumScreenError = element.attribute( QStringLiteral( "maximumScreenError" ), QStringLiteral( "0.3" ) ).toDouble();
174 : 0 : mMaximumScreenErrorUnit = QgsUnitTypes::decodeRenderUnit( element.attribute( QStringLiteral( "maximumScreenErrorUnit" ), QStringLiteral( "MM" ) ) );
175 : 0 : mPointSymbol = static_cast< PointSymbol >( element.attribute( QStringLiteral( "pointSymbol" ), QStringLiteral( "0" ) ).toInt() );
176 : 0 : }
177 : :
178 : 0 : void QgsPointCloudRenderer::saveCommonProperties( QDomElement &element, const QgsReadWriteContext & ) const
179 : : {
180 : 0 : element.setAttribute( QStringLiteral( "pointSize" ), qgsDoubleToString( mPointSize ) );
181 : 0 : element.setAttribute( QStringLiteral( "pointSizeUnit" ), QgsUnitTypes::encodeUnit( mPointSizeUnit ) );
182 : 0 : element.setAttribute( QStringLiteral( "pointSizeMapUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mPointSizeMapUnitScale ) );
183 : :
184 : 0 : element.setAttribute( QStringLiteral( "maximumScreenError" ), qgsDoubleToString( mMaximumScreenError ) );
185 : 0 : element.setAttribute( QStringLiteral( "maximumScreenErrorUnit" ), QgsUnitTypes::encodeUnit( mMaximumScreenErrorUnit ) );
186 : 0 : element.setAttribute( QStringLiteral( "pointSymbol" ), QString::number( mPointSymbol ) );
187 : 0 : }
188 : :
189 : 0 : QgsPointCloudRenderer::PointSymbol QgsPointCloudRenderer::pointSymbol() const
190 : : {
191 : 0 : return mPointSymbol;
192 : : }
193 : :
194 : 0 : void QgsPointCloudRenderer::setPointSymbol( PointSymbol symbol )
195 : : {
196 : 0 : mPointSymbol = symbol;
197 : 0 : }
198 : :
199 : 0 : QVector<QVariantMap> QgsPointCloudRenderer::identify( QgsPointCloudLayer *layer, const QgsRenderContext &renderContext, const QgsGeometry &geometry, double toleranceForPointIdentification )
200 : : {
201 : 0 : QVector<QVariantMap> selectedPoints;
202 : :
203 : 0 : QgsPointCloudIndex *index = layer->dataProvider()->index();
204 : 0 : const IndexedPointCloudNode root = index->root();
205 : :
206 : 0 : const double maxErrorPixels = renderContext.convertToPainterUnits( maximumScreenError(), maximumScreenErrorUnit() );// in pixels
207 : :
208 : 0 : const QgsRectangle rootNodeExtentLayerCoords = index->nodeMapExtent( root );
209 : 0 : QgsRectangle rootNodeExtentMapCoords;
210 : 0 : try
211 : : {
212 : 0 : rootNodeExtentMapCoords = renderContext.coordinateTransform().transformBoundingBox( rootNodeExtentLayerCoords );
213 : 0 : }
214 : 0 : catch ( QgsCsException & )
215 : 0 : {
216 : 0 : QgsDebugMsg( QStringLiteral( "Could not transform node extent to map CRS" ) );
217 : 0 : rootNodeExtentMapCoords = rootNodeExtentLayerCoords;
218 : 0 : }
219 : :
220 : 0 : const double rootErrorInMapCoordinates = rootNodeExtentMapCoords.width() / index->span();
221 : 0 : const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / index->span();
222 : :
223 : 0 : double mapUnitsPerPixel = renderContext.mapToPixel().mapUnitsPerPixel();
224 : 0 : if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maxErrorPixels < 0.0 ) )
225 : : {
226 : 0 : QgsDebugMsg( QStringLiteral( "invalid screen error" ) );
227 : 0 : return selectedPoints;
228 : : }
229 : :
230 : 0 : const double maxErrorInMapCoordinates = maxErrorPixels * mapUnitsPerPixel;
231 : 0 : const double maxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates;
232 : :
233 : 0 : QgsGeometry selectionGeometry = geometry;
234 : 0 : if ( geometry.type() == QgsWkbTypes::PointGeometry )
235 : : {
236 : 0 : double x = geometry.asPoint().x();
237 : 0 : double y = geometry.asPoint().y();
238 : 0 : const double toleranceInPixels = toleranceForPointIdentification / renderContext.mapToPixel().mapUnitsPerPixel();
239 : 0 : const double pointSizePixels = renderContext.convertToPainterUnits( mPointSize, mPointSizeUnit, mPointSizeMapUnitScale );
240 : 0 : switch ( pointSymbol() )
241 : : {
242 : : case QgsPointCloudRenderer::PointSymbol::Square:
243 : : {
244 : 0 : QgsPointXY deviceCoords = renderContext.mapToPixel().transform( QgsPointXY( x, y ) );
245 : 0 : QgsPointXY point1( deviceCoords.x() - std::max( toleranceInPixels, pointSizePixels / 2.0 ), deviceCoords.y() - std::max( toleranceInPixels, pointSizePixels / 2.0 ) );
246 : 0 : QgsPointXY point2( deviceCoords.x() + std::max( toleranceInPixels, pointSizePixels / 2.0 ), deviceCoords.y() + std::max( toleranceInPixels, pointSizePixels / 2.0 ) );
247 : 0 : QgsPointXY point1MapCoords = renderContext.mapToPixel().toMapCoordinates( point1.x(), point1.y() );
248 : 0 : QgsPointXY point2MapCoords = renderContext.mapToPixel().toMapCoordinates( point2.x(), point2.y() );
249 : 0 : QgsRectangle pointRect( point1MapCoords, point2MapCoords );
250 : 0 : selectionGeometry = QgsGeometry::fromRect( pointRect );
251 : 0 : break;
252 : : }
253 : : case QgsPointCloudRenderer::PointSymbol::Circle:
254 : : {
255 : 0 : QgsPoint centerMapCoords( x, y );
256 : 0 : QgsPointXY deviceCoords = renderContext.mapToPixel().transform( centerMapCoords );
257 : 0 : QgsPoint point1( deviceCoords.x(), deviceCoords.y() - std::max( toleranceInPixels, pointSizePixels / 2.0 ) );
258 : 0 : QgsPoint point2( deviceCoords.x(), deviceCoords.y() + std::max( toleranceInPixels, pointSizePixels / 2.0 ) );
259 : 0 : QgsPointXY point1MapCoords = renderContext.mapToPixel().toMapCoordinates( point1.x(), point1.y() );
260 : 0 : QgsPointXY point2MapCoords = renderContext.mapToPixel().toMapCoordinates( point2.x(), point2.y() );
261 : 0 : QgsCircle circle = QgsCircle::from2Points( QgsPoint( point1MapCoords ), QgsPoint( point2MapCoords ) );
262 : 0 : std::unique_ptr<QgsPolygon> polygon( circle.toPolygon( 6 ) );
263 : 0 : QgsGeometry circleGeometry( std::move( polygon ) );
264 : 0 : selectionGeometry = circleGeometry;
265 : : break;
266 : 0 : }
267 : : }
268 : 0 : }
269 : :
270 : : // selection geometry must be in layer CRS for QgsPointCloudDataProvider::identify
271 : : try
272 : : {
273 : 0 : selectionGeometry.transform( renderContext.coordinateTransform(), QgsCoordinateTransform::ReverseTransform );
274 : 0 : }
275 : : catch ( QgsCsException & )
276 : : {
277 : 0 : QgsDebugMsg( QStringLiteral( "Could not transform geometry to layer CRS" ) );
278 : 0 : return selectedPoints;
279 : 0 : }
280 : :
281 : 0 : selectedPoints = layer->dataProvider()->identify( maxErrorInLayerCoordinates, selectionGeometry, renderContext.zRange() );
282 : :
283 : 0 : selectedPoints.erase( std::remove_if( selectedPoints.begin(), selectedPoints.end(), [this]( const QMap<QString, QVariant> &point ) { return !this->willRenderPoint( point ); } ), selectedPoints.end() );
284 : :
285 : 0 : return selectedPoints;
286 : 0 : }
|