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