Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsrasterlayerrenderer.cpp
3 : : --------------------------------------
4 : : Date : December 2013
5 : : Copyright : (C) 2013 by Martin Dobias
6 : : Email : wonder dot sk at gmail dot com
7 : : ***************************************************************************
8 : : * *
9 : : * This program is free software; you can redistribute it and/or modify *
10 : : * it under the terms of the GNU General Public License as published by *
11 : : * the Free Software Foundation; either version 2 of the License, or *
12 : : * (at your option) any later version. *
13 : : * *
14 : : ***************************************************************************/
15 : :
16 : : #include "qgsrasterlayerrenderer.h"
17 : :
18 : : #include "qgsmessagelog.h"
19 : : #include "qgsrasterdataprovider.h"
20 : : #include "qgsrasterdrawer.h"
21 : : #include "qgsrasteriterator.h"
22 : : #include "qgsrasterlayer.h"
23 : : #include "qgsrasterprojector.h"
24 : : #include "qgsrendercontext.h"
25 : : #include "qgsproject.h"
26 : : #include "qgsexception.h"
27 : : #include "qgsrasterlayertemporalproperties.h"
28 : : #include "qgsmapclippingutils.h"
29 : :
30 : : #include <QElapsedTimer>
31 : : #include <QPointer>
32 : :
33 : : ///@cond PRIVATE
34 : :
35 : 0 : QgsRasterLayerRendererFeedback::QgsRasterLayerRendererFeedback( QgsRasterLayerRenderer *r )
36 : 0 : : mR( r )
37 : 0 : , mMinimalPreviewInterval( 250 )
38 : 0 : {
39 : 0 : setRenderPartialOutput( r->renderContext()->testFlag( QgsRenderContext::RenderPartialOutput ) );
40 : 0 : }
41 : :
42 : 0 : void QgsRasterLayerRendererFeedback::onNewData()
43 : : {
44 : 0 : if ( !renderPartialOutput() )
45 : 0 : return; // we were not asked for partial renders and we may not have a temporary image for overwriting...
46 : :
47 : : // update only once upon a time
48 : : // (preview itself takes some time)
49 : 0 : if ( mLastPreview.isValid() && mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval )
50 : 0 : return;
51 : :
52 : : // TODO: update only the area that got new data
53 : :
54 : 0 : QgsDebugMsgLevel( QStringLiteral( "new raster preview! %1" ).arg( mLastPreview.msecsTo( QTime::currentTime() ) ), 3 );
55 : 0 : QElapsedTimer t;
56 : 0 : t.start();
57 : 0 : QgsRasterBlockFeedback feedback;
58 : 0 : feedback.setPreviewOnly( true );
59 : 0 : feedback.setRenderPartialOutput( true );
60 : 0 : QgsRasterIterator iterator( mR->mPipe->last() );
61 : 0 : QgsRasterDrawer drawer( &iterator );
62 : 0 : drawer.draw( mR->renderContext()->painter(), mR->mRasterViewPort, &mR->renderContext()->mapToPixel(), &feedback );
63 : 0 : mR->mReadyToCompose = true;
64 : 0 : QgsDebugMsgLevel( QStringLiteral( "total raster preview time: %1 ms" ).arg( t.elapsed() ), 3 );
65 : 0 : mLastPreview = QTime::currentTime();
66 : 0 : }
67 : :
68 : : ///@endcond
69 : : ///
70 : 0 : QgsRasterLayerRenderer::QgsRasterLayerRenderer( QgsRasterLayer *layer, QgsRenderContext &rendererContext )
71 : 0 : : QgsMapLayerRenderer( layer->id(), &rendererContext )
72 : 0 : , mProviderCapabilities( static_cast<QgsRasterDataProvider::Capability>( layer->dataProvider()->capabilities() ) )
73 : 0 : , mFeedback( new QgsRasterLayerRendererFeedback( this ) )
74 : 0 : {
75 : 0 : mReadyToCompose = false;
76 : 0 : QgsMapToPixel mapToPixel = rendererContext.mapToPixel();
77 : 0 : if ( rendererContext.mapToPixel().mapRotation() )
78 : : {
79 : : // unset rotation for the sake of local computations.
80 : : // Rotation will be handled by QPainter later
81 : : // TODO: provide a method of QgsMapToPixel to fetch map center
82 : 0 : // in geographical units
83 : 0 : QgsPointXY center = mapToPixel.toMapCoordinates(
84 : 0 : static_cast<int>( mapToPixel.mapWidth() / 2.0 ),
85 : 0 : static_cast<int>( mapToPixel.mapHeight() / 2.0 )
86 : : );
87 : 0 : mapToPixel.setMapRotation( 0, center.x(), center.y() );
88 : 0 : }
89 : :
90 : 0 : QgsRectangle myProjectedViewExtent;
91 : 0 : QgsRectangle myProjectedLayerExtent;
92 : :
93 : 0 : if ( rendererContext.coordinateTransform().isValid() )
94 : : {
95 : 0 : QgsDebugMsgLevel( QStringLiteral( "coordinateTransform set -> project extents." ), 4 );
96 : 0 : if ( rendererContext.extent().xMinimum() == std::numeric_limits<double>::lowest() &&
97 : 0 : rendererContext.extent().yMinimum() == std::numeric_limits<double>::lowest() &&
98 : 0 : rendererContext.extent().xMaximum() == std::numeric_limits<double>::max() &&
99 : 0 : rendererContext.extent().yMaximum() == std::numeric_limits<double>::max() )
100 : : {
101 : : // We get in this situation if the view CRS is geographical and the
102 : : // extent goes beyond -180,-90,180,90. To avoid reprojection issues to the
103 : : // layer CRS, then this dummy extent is returned by QgsMapRendererJob::reprojectToLayerExtent()
104 : : // Don't try to reproject it now to view extent as this would return
105 : : // a null rectangle.
106 : 0 : myProjectedViewExtent = rendererContext.extent();
107 : 0 : }
108 : : else
109 : : {
110 : : try
111 : : {
112 : 0 : QgsCoordinateTransform ct = rendererContext.coordinateTransform();
113 : 0 : ct.setBallparkTransformsAreAppropriate( true );
114 : 0 : myProjectedViewExtent = ct.transformBoundingBox( rendererContext.extent() );
115 : 0 : }
116 : : catch ( QgsCsException &cs )
117 : : {
118 : 0 : QgsMessageLog::logMessage( QObject::tr( "Could not reproject view extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) );
119 : 0 : myProjectedViewExtent.setMinimal();
120 : 0 : }
121 : : }
122 : :
123 : : try
124 : : {
125 : 0 : QgsCoordinateTransform ct = rendererContext.coordinateTransform();
126 : 0 : ct.setBallparkTransformsAreAppropriate( true );
127 : 0 : myProjectedLayerExtent = ct.transformBoundingBox( layer->extent() );
128 : 0 : }
129 : : catch ( QgsCsException &cs )
130 : : {
131 : 0 : QgsMessageLog::logMessage( QObject::tr( "Could not reproject layer extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) );
132 : 0 : myProjectedLayerExtent.setMinimal();
133 : 0 : }
134 : 0 : }
135 : : else
136 : : {
137 : 0 : QgsDebugMsgLevel( QStringLiteral( "coordinateTransform not set" ), 4 );
138 : 0 : myProjectedViewExtent = rendererContext.extent();
139 : 0 : myProjectedLayerExtent = layer->extent();
140 : : }
141 : :
142 : : // clip raster extent to view extent
143 : 0 : QgsRectangle myRasterExtent = layer->ignoreExtents() ? myProjectedViewExtent : myProjectedViewExtent.intersect( myProjectedLayerExtent );
144 : 0 : if ( myRasterExtent.isEmpty() )
145 : : {
146 : 0 : QgsDebugMsgLevel( QStringLiteral( "draw request outside view extent." ), 2 );
147 : : // nothing to do
148 : 0 : return;
149 : : }
150 : :
151 : 0 : QgsDebugMsgLevel( "theViewExtent is " + rendererContext.extent().toString(), 4 );
152 : 0 : QgsDebugMsgLevel( "myProjectedViewExtent is " + myProjectedViewExtent.toString(), 4 );
153 : 0 : QgsDebugMsgLevel( "myProjectedLayerExtent is " + myProjectedLayerExtent.toString(), 4 );
154 : 0 : QgsDebugMsgLevel( "myRasterExtent is " + myRasterExtent.toString(), 4 );
155 : :
156 : : //
157 : : // The first thing we do is set up the QgsRasterViewPort. This struct stores all the settings
158 : : // relating to the size (in pixels and coordinate system units) of the raster part that is
159 : : // in view in the map window. It also stores the origin.
160 : : //
161 : : //this is not a class level member because every time the user pans or zooms
162 : : //the contents of the rasterViewPort will change
163 : 0 : mRasterViewPort = new QgsRasterViewPort();
164 : :
165 : 0 : mRasterViewPort->mDrawnExtent = myRasterExtent;
166 : 0 : if ( rendererContext.coordinateTransform().isValid() )
167 : : {
168 : 0 : mRasterViewPort->mSrcCRS = layer->crs();
169 : 0 : mRasterViewPort->mDestCRS = rendererContext.coordinateTransform().destinationCrs();
170 : 0 : mRasterViewPort->mTransformContext = rendererContext.transformContext();
171 : 0 : }
172 : : else
173 : : {
174 : 0 : mRasterViewPort->mSrcCRS = QgsCoordinateReferenceSystem(); // will be invalid
175 : 0 : mRasterViewPort->mDestCRS = QgsCoordinateReferenceSystem(); // will be invalid
176 : : }
177 : :
178 : : // get dimensions of clipped raster image in device coordinate space (this is the size of the viewport)
179 : 0 : mRasterViewPort->mTopLeftPoint = mapToPixel.transform( myRasterExtent.xMinimum(), myRasterExtent.yMaximum() );
180 : 0 : mRasterViewPort->mBottomRightPoint = mapToPixel.transform( myRasterExtent.xMaximum(), myRasterExtent.yMinimum() );
181 : :
182 : : // align to output device grid, i.e. std::floor/ceil to integers
183 : : // TODO: this should only be done if paint device is raster - screen, image
184 : : // for other devices (pdf) it can have floating point origin
185 : : // we could use floating point for raster devices as well, but respecting the
186 : : // output device grid should make it more effective as the resampling is done in
187 : : // the provider anyway
188 : 0 : mRasterViewPort->mTopLeftPoint.setX( std::floor( mRasterViewPort->mTopLeftPoint.x() ) );
189 : 0 : mRasterViewPort->mTopLeftPoint.setY( std::floor( mRasterViewPort->mTopLeftPoint.y() ) );
190 : 0 : mRasterViewPort->mBottomRightPoint.setX( std::ceil( mRasterViewPort->mBottomRightPoint.x() ) );
191 : 0 : mRasterViewPort->mBottomRightPoint.setY( std::ceil( mRasterViewPort->mBottomRightPoint.y() ) );
192 : : // recalc myRasterExtent to aligned values
193 : 0 : myRasterExtent.set(
194 : 0 : mapToPixel.toMapCoordinates( mRasterViewPort->mTopLeftPoint.x(),
195 : 0 : mRasterViewPort->mBottomRightPoint.y() ),
196 : 0 : mapToPixel.toMapCoordinates( mRasterViewPort->mBottomRightPoint.x(),
197 : 0 : mRasterViewPort->mTopLeftPoint.y() )
198 : : );
199 : :
200 : : //raster viewport top left / bottom right are already rounded to int
201 : 0 : mRasterViewPort->mWidth = static_cast<qgssize>( std::abs( mRasterViewPort->mBottomRightPoint.x() - mRasterViewPort->mTopLeftPoint.x() ) );
202 : 0 : mRasterViewPort->mHeight = static_cast<qgssize>( std::abs( mRasterViewPort->mBottomRightPoint.y() - mRasterViewPort->mTopLeftPoint.y() ) );
203 : :
204 : : //the drawable area can start to get very very large when you get down displaying 2x2 or smaller, this is because
205 : : //mapToPixel.mapUnitsPerPixel() is less then 1,
206 : : //so we will just get the pixel data and then render these special cases differently in paintImageToCanvas()
207 : :
208 : 0 : QgsDebugMsgLevel( QStringLiteral( "mapUnitsPerPixel = %1" ).arg( mapToPixel.mapUnitsPerPixel() ), 3 );
209 : 0 : QgsDebugMsgLevel( QStringLiteral( "mWidth = %1" ).arg( layer->width() ), 3 );
210 : 0 : QgsDebugMsgLevel( QStringLiteral( "mHeight = %1" ).arg( layer->height() ), 3 );
211 : 0 : QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.xMinimum() = %1" ).arg( myRasterExtent.xMinimum() ), 3 );
212 : 0 : QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.xMaximum() = %1" ).arg( myRasterExtent.xMaximum() ), 3 );
213 : 0 : QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.yMinimum() = %1" ).arg( myRasterExtent.yMinimum() ), 3 );
214 : 0 : QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.yMaximum() = %1" ).arg( myRasterExtent.yMaximum() ), 3 );
215 : :
216 : 0 : QgsDebugMsgLevel( QStringLiteral( "mTopLeftPoint.x() = %1" ).arg( mRasterViewPort->mTopLeftPoint.x() ), 3 );
217 : 0 : QgsDebugMsgLevel( QStringLiteral( "mBottomRightPoint.x() = %1" ).arg( mRasterViewPort->mBottomRightPoint.x() ), 3 );
218 : 0 : QgsDebugMsgLevel( QStringLiteral( "mTopLeftPoint.y() = %1" ).arg( mRasterViewPort->mTopLeftPoint.y() ), 3 );
219 : 0 : QgsDebugMsgLevel( QStringLiteral( "mBottomRightPoint.y() = %1" ).arg( mRasterViewPort->mBottomRightPoint.y() ), 3 );
220 : :
221 : 0 : QgsDebugMsgLevel( QStringLiteral( "mWidth = %1" ).arg( mRasterViewPort->mWidth ), 3 );
222 : 0 : QgsDebugMsgLevel( QStringLiteral( "mHeight = %1" ).arg( mRasterViewPort->mHeight ), 3 );
223 : :
224 : : // /\/\/\ - added to handle zoomed-in rasters
225 : :
226 : : // TODO R->mLastViewPort = *mRasterViewPort;
227 : :
228 : : // TODO: is it necessary? Probably WMS only?
229 : 0 : layer->dataProvider()->setDpi( 25.4 * rendererContext.scaleFactor() );
230 : :
231 : :
232 : : // copy the whole raster pipe!
233 : 0 : mPipe = new QgsRasterPipe( *layer->pipe() );
234 : 0 : QObject::connect( mPipe->provider(), &QgsRasterDataProvider::statusChanged, layer, &QgsRasterLayer::statusChanged );
235 : 0 : QgsRasterRenderer *rasterRenderer = mPipe->renderer();
236 : 0 : if ( rasterRenderer
237 : 0 : && !( rendererContext.flags() & QgsRenderContext::RenderPreviewJob )
238 : 0 : && !( rendererContext.flags() & QgsRenderContext::Render3DMap ) )
239 : : {
240 : 0 : layer->refreshRendererIfNeeded( rasterRenderer, rendererContext.extent() );
241 : 0 : }
242 : :
243 : 0 : const QgsRasterLayerTemporalProperties *temporalProperties = qobject_cast< const QgsRasterLayerTemporalProperties * >( layer->temporalProperties() );
244 : 0 : if ( temporalProperties->isActive() && renderContext()->isTemporal() )
245 : : {
246 : 0 : switch ( temporalProperties->mode() )
247 : : {
248 : : case QgsRasterLayerTemporalProperties::ModeFixedTemporalRange:
249 : 0 : break;
250 : :
251 : : case QgsRasterLayerTemporalProperties::ModeTemporalRangeFromDataProvider:
252 : : // in this mode we need to pass on the desired render temporal range to the data provider
253 : 0 : if ( mPipe->provider()->temporalCapabilities() )
254 : : {
255 : 0 : mPipe->provider()->temporalCapabilities()->setRequestedTemporalRange( rendererContext.temporalRange() );
256 : 0 : mPipe->provider()->temporalCapabilities()->setIntervalHandlingMethod( temporalProperties->intervalHandlingMethod() );
257 : 0 : }
258 : 0 : break;
259 : : }
260 : 0 : }
261 : 0 : else if ( mPipe->provider()->temporalCapabilities() )
262 : : {
263 : 0 : mPipe->provider()->temporalCapabilities()->setRequestedTemporalRange( QgsDateTimeRange() );
264 : 0 : mPipe->provider()->temporalCapabilities()->setIntervalHandlingMethod( temporalProperties->intervalHandlingMethod() );
265 : 0 : }
266 : :
267 : 0 : mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer );
268 : 0 : }
269 : :
270 : 0 : QgsRasterLayerRenderer::~QgsRasterLayerRenderer()
271 : 0 : {
272 : 0 : delete mFeedback;
273 : :
274 : 0 : delete mRasterViewPort;
275 : 0 : delete mPipe;
276 : 0 : }
277 : :
278 : 0 : bool QgsRasterLayerRenderer::render()
279 : : {
280 : : // Skip rendering of out of view tiles (xyz)
281 : 0 : if ( !mRasterViewPort || ( renderContext()->testFlag( QgsRenderContext::Flag::RenderPreviewJob ) &&
282 : 0 : !( mProviderCapabilities &
283 : : QgsRasterInterface::Capability::Prefetch ) ) )
284 : 0 : return true;
285 : :
286 : 0 : QElapsedTimer time;
287 : 0 : time.start();
288 : : //
289 : : //
290 : : // The goal here is to make as many decisions as possible early on (outside of the rendering loop)
291 : : // so that we can maximise performance of the rendering process. So now we check which drawing
292 : : // procedure to use :
293 : : //
294 : :
295 : 0 : QgsScopedQPainterState painterSate( renderContext()->painter() );
296 : 0 : if ( !mClippingRegions.empty() )
297 : : {
298 : 0 : bool needsPainterClipPath = false;
299 : 0 : const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), QgsMapLayerType::RasterLayer, needsPainterClipPath );
300 : 0 : if ( needsPainterClipPath )
301 : 0 : renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
302 : 0 : }
303 : :
304 : 0 : QgsRasterProjector *projector = mPipe->projector();
305 : 0 : bool restoreOldResamplingStage = false;
306 : 0 : QgsRasterPipe::ResamplingStage oldResamplingState = mPipe->resamplingStage();
307 : :
308 : : // TODO add a method to interface to get provider and get provider
309 : : // params in QgsRasterProjector
310 : 0 : if ( projector )
311 : : {
312 : : // Force provider resampling if reprojection is needed
313 : 0 : if ( ( mPipe->provider()->providerCapabilities() & QgsRasterDataProvider::ProviderHintCanPerformProviderResampling ) &&
314 : 0 : mRasterViewPort->mSrcCRS != mRasterViewPort->mDestCRS &&
315 : 0 : oldResamplingState != QgsRasterPipe::ResamplingStage::Provider )
316 : : {
317 : 0 : restoreOldResamplingStage = true;
318 : 0 : mPipe->setResamplingStage( QgsRasterPipe::ResamplingStage::Provider );
319 : 0 : }
320 : 0 : projector->setCrs( mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS, mRasterViewPort->mTransformContext );
321 : 0 : }
322 : :
323 : : // Drawer to pipe?
324 : 0 : QgsRasterIterator iterator( mPipe->last() );
325 : 0 : QgsRasterDrawer drawer( &iterator );
326 : 0 : drawer.draw( renderContext()->painter(), mRasterViewPort, &renderContext()->mapToPixel(), mFeedback );
327 : :
328 : 0 : if ( restoreOldResamplingStage )
329 : : {
330 : 0 : mPipe->setResamplingStage( oldResamplingState );
331 : 0 : }
332 : :
333 : 0 : const QStringList errors = mFeedback->errors();
334 : 0 : for ( const QString &error : errors )
335 : : {
336 : 0 : mErrors.append( error );
337 : : }
338 : :
339 : 0 : QgsDebugMsgLevel( QStringLiteral( "total raster draw time (ms): %1" ).arg( time.elapsed(), 5 ), 4 );
340 : 0 : mReadyToCompose = true;
341 : :
342 : 0 : return !mFeedback->isCanceled();
343 : 0 : }
344 : :
345 : 0 : QgsFeedback *QgsRasterLayerRenderer::feedback() const
346 : : {
347 : 0 : return mFeedback;
348 : : }
349 : :
350 : 0 : bool QgsRasterLayerRenderer::forceRasterRender() const
351 : : {
352 : : // preview of intermediate raster rendering results requires a temporary output image
353 : 0 : return renderContext()->testFlag( QgsRenderContext::RenderPartialOutput );
354 : : }
355 : :
|