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