QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsrasterlayerrenderer.cpp
Go to the documentation of this file.
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
17
18#include "qgsmessagelog.h"
20#include "qgsrasterdrawer.h"
21#include "qgsrasteriterator.h"
22#include "qgsrasterlayer.h"
23#include "qgsrasterprojector.h"
24#include "qgsrendercontext.h"
25#include "qgsrasterrenderer.h"
26#include "qgsexception.h"
28#include "qgsmapclippingutils.h"
29#include "qgsrasterpipe.h"
30#include "qgselevationmap.h"
31#include "qgsgdalutils.h"
34#include "qgsruntimeprofiler.h"
35#include "qgsapplication.h"
37
38#include <QElapsedTimer>
39#include <QPointer>
40#include <QThread>
41
43
44QgsRasterLayerRendererFeedback::QgsRasterLayerRendererFeedback( QgsRasterLayerRenderer *r )
45 : mR( r )
46 , mMinimalPreviewInterval( 250 )
47{
49}
50
51void QgsRasterLayerRendererFeedback::onNewData()
52{
53 if ( !renderPartialOutput() )
54 return; // we were not asked for partial renders and we may not have a temporary image for overwriting...
55
56 // update only once upon a time
57 // (preview itself takes some time)
58 if ( mLastPreview.isValid() && mLastPreview.msecsTo( QTime::currentTime() ) < mMinimalPreviewInterval )
59 return;
60
61 // TODO: update only the area that got new data
62
63 QgsDebugMsgLevel( QStringLiteral( "new raster preview! %1" ).arg( mLastPreview.msecsTo( QTime::currentTime() ) ), 3 );
64 QElapsedTimer t;
65 t.start();
67 feedback.setPreviewOnly( true );
68 feedback.setRenderPartialOutput( true );
69 QgsRasterIterator iterator( mR->mPipe->last() );
70 QgsRasterDrawer drawer( &iterator );
71 drawer.draw( *( mR->renderContext() ), mR->mRasterViewPort, &feedback );
72 mR->mReadyToCompose = true;
73 QgsDebugMsgLevel( QStringLiteral( "total raster preview time: %1 ms" ).arg( t.elapsed() ), 3 );
74 mLastPreview = QTime::currentTime();
75}
76
80 : QgsMapLayerRenderer( layer->id(), &rendererContext )
81 , mLayerName( layer->name() )
82 , mLayerOpacity( layer->opacity() )
83 , mProviderCapabilities( layer->dataProvider()->providerCapabilities() )
84 , mFeedback( new QgsRasterLayerRendererFeedback( this ) )
85 , mEnableProfile( rendererContext.flags() & Qgis::RenderContextFlag::RecordProfile )
86{
87 mReadyToCompose = false;
88
89 QElapsedTimer timer;
90 timer.start();
91
92 QgsMapToPixel mapToPixel = rendererContext.mapToPixel();
93 if ( rendererContext.mapToPixel().mapRotation() )
94 {
95 // unset rotation for the sake of local computations.
96 // Rotation will be handled by QPainter later
97 // TODO: provide a method of QgsMapToPixel to fetch map center
98 // in geographical units
99 const QgsPointXY center = mapToPixel.toMapCoordinates(
100 static_cast<int>( mapToPixel.mapWidth() / 2.0 ),
101 static_cast<int>( mapToPixel.mapHeight() / 2.0 )
102 );
103 mapToPixel.setMapRotation( 0, center.x(), center.y() );
104 }
105
106 QgsRectangle myProjectedViewExtent;
107 QgsRectangle myProjectedLayerExtent;
108
109 if ( rendererContext.coordinateTransform().isValid() )
110 {
111 QgsDebugMsgLevel( QStringLiteral( "coordinateTransform set -> project extents." ), 4 );
112 if ( rendererContext.extent().xMinimum() == std::numeric_limits<double>::lowest() &&
113 rendererContext.extent().yMinimum() == std::numeric_limits<double>::lowest() &&
114 rendererContext.extent().xMaximum() == std::numeric_limits<double>::max() &&
115 rendererContext.extent().yMaximum() == std::numeric_limits<double>::max() )
116 {
117 // We get in this situation if the view CRS is geographical and the
118 // extent goes beyond -180,-90,180,90. To avoid reprojection issues to the
119 // layer CRS, then this dummy extent is returned by QgsMapRendererJob::reprojectToLayerExtent()
120 // Don't try to reproject it now to view extent as this would return
121 // a null rectangle.
122 myProjectedViewExtent = rendererContext.extent();
123 }
124 else
125 {
126 try
127 {
128 QgsCoordinateTransform ct = rendererContext.coordinateTransform();
130 myProjectedViewExtent = ct.transformBoundingBox( rendererContext.extent() );
131 }
132 catch ( QgsCsException &cs )
133 {
134 QgsMessageLog::logMessage( QObject::tr( "Could not reproject view extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) );
135 myProjectedViewExtent.setNull();
136 }
137 }
138
139 try
140 {
141 QgsCoordinateTransform ct = rendererContext.coordinateTransform();
143 myProjectedLayerExtent = ct.transformBoundingBox( layer->extent() );
144 }
145 catch ( QgsCsException &cs )
146 {
147 QgsMessageLog::logMessage( QObject::tr( "Could not reproject layer extent: %1" ).arg( cs.what() ), QObject::tr( "Raster" ) );
148 myProjectedLayerExtent.setNull();
149 }
150 }
151 else
152 {
153 QgsDebugMsgLevel( QStringLiteral( "coordinateTransform not set" ), 4 );
154 myProjectedViewExtent = rendererContext.extent();
155 myProjectedLayerExtent = layer->extent();
156 }
157
158 // clip raster extent to view extent
159 QgsRectangle myRasterExtent = layer->ignoreExtents() ? myProjectedViewExtent : myProjectedViewExtent.intersect( myProjectedLayerExtent );
160 if ( myRasterExtent.isEmpty() )
161 {
162 QgsDebugMsgLevel( QStringLiteral( "draw request outside view extent." ), 2 );
163 // nothing to do
164 return;
165 }
166
167 QgsDebugMsgLevel( "theViewExtent is " + rendererContext.extent().toString(), 4 );
168 QgsDebugMsgLevel( "myProjectedViewExtent is " + myProjectedViewExtent.toString(), 4 );
169 QgsDebugMsgLevel( "myProjectedLayerExtent is " + myProjectedLayerExtent.toString(), 4 );
170 QgsDebugMsgLevel( "myRasterExtent is " + myRasterExtent.toString(), 4 );
171
172 //
173 // The first thing we do is set up the QgsRasterViewPort. This struct stores all the settings
174 // relating to the size (in pixels and coordinate system units) of the raster part that is
175 // in view in the map window. It also stores the origin.
176 //
177 //this is not a class level member because every time the user pans or zooms
178 //the contents of the rasterViewPort will change
179 mRasterViewPort = new QgsRasterViewPort();
180
181 mRasterViewPort->mDrawnExtent = myRasterExtent;
182 if ( rendererContext.coordinateTransform().isValid() )
183 {
184 mRasterViewPort->mSrcCRS = layer->crs();
185 mRasterViewPort->mDestCRS = rendererContext.coordinateTransform().destinationCrs();
186 mRasterViewPort->mTransformContext = rendererContext.transformContext();
187 }
188 else
189 {
190 mRasterViewPort->mSrcCRS = QgsCoordinateReferenceSystem(); // will be invalid
191 mRasterViewPort->mDestCRS = QgsCoordinateReferenceSystem(); // will be invalid
192 }
193
194 // get dimensions of clipped raster image in device coordinate space (this is the size of the viewport)
195 mRasterViewPort->mTopLeftPoint = mapToPixel.transform( myRasterExtent.xMinimum(), myRasterExtent.yMaximum() );
196 mRasterViewPort->mBottomRightPoint = mapToPixel.transform( myRasterExtent.xMaximum(), myRasterExtent.yMinimum() );
197
198 // align to output device grid, i.e. std::floor/ceil to integers
199 // TODO: this should only be done if paint device is raster - screen, image
200 // for other devices (pdf) it can have floating point origin
201 // we could use floating point for raster devices as well, but respecting the
202 // output device grid should make it more effective as the resampling is done in
203 // the provider anyway
204 mRasterViewPort->mTopLeftPoint.setX( std::floor( mRasterViewPort->mTopLeftPoint.x() ) );
205 mRasterViewPort->mTopLeftPoint.setY( std::floor( mRasterViewPort->mTopLeftPoint.y() ) );
206 mRasterViewPort->mBottomRightPoint.setX( std::ceil( mRasterViewPort->mBottomRightPoint.x() ) );
207 mRasterViewPort->mBottomRightPoint.setY( std::ceil( mRasterViewPort->mBottomRightPoint.y() ) );
208 // recalc myRasterExtent to aligned values
209 myRasterExtent.set(
210 mapToPixel.toMapCoordinates( mRasterViewPort->mTopLeftPoint.x(),
211 mRasterViewPort->mBottomRightPoint.y() ),
212 mapToPixel.toMapCoordinates( mRasterViewPort->mBottomRightPoint.x(),
213 mRasterViewPort->mTopLeftPoint.y() )
214 );
215
216 //raster viewport top left / bottom right are already rounded to int
217 mRasterViewPort->mWidth = static_cast<qgssize>( std::abs( mRasterViewPort->mBottomRightPoint.x() - mRasterViewPort->mTopLeftPoint.x() ) );
218 mRasterViewPort->mHeight = static_cast<qgssize>( std::abs( mRasterViewPort->mBottomRightPoint.y() - mRasterViewPort->mTopLeftPoint.y() ) );
219
220 const double dpi = 25.4 * rendererContext.scaleFactor();
221 if ( mProviderCapabilities & QgsRasterDataProvider::DpiDependentData
222 && rendererContext.dpiTarget() >= 0.0 )
223 {
224 const double dpiScaleFactor = rendererContext.dpiTarget() / dpi;
225 mRasterViewPort->mWidth *= dpiScaleFactor;
226 mRasterViewPort->mHeight *= dpiScaleFactor;
227 }
228 else
229 {
230 rendererContext.setDpiTarget( -1.0 );
231 }
232
233 //the drawable area can start to get very very large when you get down displaying 2x2 or smaller, this is because
234 //mapToPixel.mapUnitsPerPixel() is less then 1,
235 //so we will just get the pixel data and then render these special cases differently in paintImageToCanvas()
236
237 QgsDebugMsgLevel( QStringLiteral( "mapUnitsPerPixel = %1" ).arg( mapToPixel.mapUnitsPerPixel() ), 3 );
238 QgsDebugMsgLevel( QStringLiteral( "mWidth = %1" ).arg( layer->width() ), 3 );
239 QgsDebugMsgLevel( QStringLiteral( "mHeight = %1" ).arg( layer->height() ), 3 );
240 QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.xMinimum() = %1" ).arg( myRasterExtent.xMinimum() ), 3 );
241 QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.xMaximum() = %1" ).arg( myRasterExtent.xMaximum() ), 3 );
242 QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.yMinimum() = %1" ).arg( myRasterExtent.yMinimum() ), 3 );
243 QgsDebugMsgLevel( QStringLiteral( "myRasterExtent.yMaximum() = %1" ).arg( myRasterExtent.yMaximum() ), 3 );
244
245 QgsDebugMsgLevel( QStringLiteral( "mTopLeftPoint.x() = %1" ).arg( mRasterViewPort->mTopLeftPoint.x() ), 3 );
246 QgsDebugMsgLevel( QStringLiteral( "mBottomRightPoint.x() = %1" ).arg( mRasterViewPort->mBottomRightPoint.x() ), 3 );
247 QgsDebugMsgLevel( QStringLiteral( "mTopLeftPoint.y() = %1" ).arg( mRasterViewPort->mTopLeftPoint.y() ), 3 );
248 QgsDebugMsgLevel( QStringLiteral( "mBottomRightPoint.y() = %1" ).arg( mRasterViewPort->mBottomRightPoint.y() ), 3 );
249
250 QgsDebugMsgLevel( QStringLiteral( "mWidth = %1" ).arg( mRasterViewPort->mWidth ), 3 );
251 QgsDebugMsgLevel( QStringLiteral( "mHeight = %1" ).arg( mRasterViewPort->mHeight ), 3 );
252
253 // /\/\/\ - added to handle zoomed-in rasters
254
255 // TODO R->mLastViewPort = *mRasterViewPort;
256
257 // TODO: is it necessary? Probably WMS only?
258 layer->dataProvider()->setDpi( std::floor( dpi * rendererContext.devicePixelRatio() ) );
259
260 // copy the whole raster pipe!
261 mPipe.reset( new QgsRasterPipe( *layer->pipe() ) );
262
263 QObject::connect( mPipe->provider(), &QgsRasterDataProvider::statusChanged, layer, &QgsRasterLayer::statusChanged );
264 QgsRasterRenderer *rasterRenderer = mPipe->renderer();
265 if ( rasterRenderer
266 && !( rendererContext.flags() & Qgis::RenderContextFlag::RenderPreviewJob )
267 && !( rendererContext.flags() & Qgis::RenderContextFlag::Render3DMap ) )
268 {
269 layer->refreshRendererIfNeeded( rasterRenderer, rendererContext.extent() );
270 }
271
272 mPipe->evaluateDataDefinedProperties( rendererContext.expressionContext() );
273
274 const QgsRasterLayerTemporalProperties *temporalProperties = qobject_cast< const QgsRasterLayerTemporalProperties * >( layer->temporalProperties() );
275 if ( temporalProperties->isActive() && renderContext()->isTemporal() )
276 {
277 switch ( temporalProperties->mode() )
278 {
281 break;
282
284 {
285 const int matchingBand = temporalProperties->bandForTemporalRange( layer, rendererContext.temporalRange() );
286
287 // this is guaranteed, as we won't ever be creating a renderer if this condition is not met, but let's be ultra safe!
288 if ( matchingBand > 0 )
289 {
290 mPipe->renderer()->setInputBand( matchingBand );
291 }
292
293 break;
294 }
295
297 // in this mode we need to pass on the desired render temporal range to the data provider
298 if ( QgsRasterDataProviderTemporalCapabilities *temporalCapabilities = mPipe->provider()->temporalCapabilities() )
299 {
300 temporalCapabilities->setRequestedTemporalRange( rendererContext.temporalRange() );
301 temporalCapabilities->setIntervalHandlingMethod( temporalProperties->intervalHandlingMethod() );
302 }
303 break;
304 }
305 }
306 else if ( QgsRasterDataProviderTemporalCapabilities *temporalCapabilities = mPipe->provider()->temporalCapabilities() )
307 {
308 temporalCapabilities->setRequestedTemporalRange( QgsDateTimeRange() );
309 temporalCapabilities->setIntervalHandlingMethod( temporalProperties->intervalHandlingMethod() );
310 }
311
313
314 if ( layer->elevationProperties() && layer->elevationProperties()->hasElevation() )
315 {
316 QgsRasterLayerElevationProperties *elevProp = qobject_cast<QgsRasterLayerElevationProperties *>( layer->elevationProperties() );
317 mDrawElevationMap = true;
318 mElevationScale = elevProp->zScale();
319 mElevationOffset = elevProp->zOffset();
320 mElevationBand = elevProp->bandNumber();
321
322 if ( !rendererContext.zRange().isInfinite() )
323 {
324 switch ( elevProp->mode() )
325 {
327 // don't need to handle anything here -- the layer renderer will never be created if the
328 // render context range doesn't match the layer's fixed elevation range
329 break;
330
333 {
334 const int matchingBand = elevProp->bandForElevationRange( layer, rendererContext.zRange() );
335
336 // this is guaranteed, as we won't ever be creating a renderer if this condition is not met, but let's be ultra safe!
337 if ( matchingBand > 0 )
338 {
339 mPipe->renderer()->setInputBand( matchingBand );
340 }
341 break;
342 }
343
345 {
346 if ( mPipe->renderer()->usesBands().contains( mElevationBand ) )
347 {
348 // if layer has elevation settings and we are only rendering a slice of z values => we need to filter pixels by elevation
349
350 std::unique_ptr< QgsRasterTransparency > transparency;
351 if ( const QgsRasterTransparency *rendererTransparency = mPipe->renderer()->rasterTransparency() )
352 transparency = std::make_unique< QgsRasterTransparency >( *rendererTransparency );
353 else
354 transparency = std::make_unique< QgsRasterTransparency >();
355
356 QVector<QgsRasterTransparency::TransparentSingleValuePixel> transparentPixels = transparency->transparentSingleValuePixelList();
357
358 // account for z offset/zscale by reversing these calculations, so that we get the z range in
359 // raw pixel values
360 const double adjustedLower = ( rendererContext.zRange().lower() - mElevationOffset ) / mElevationScale;
361 const double adjustedUpper = ( rendererContext.zRange().upper() - mElevationOffset ) / mElevationScale;
362 transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( std::numeric_limits<double>::lowest(), adjustedLower, 0, true, !rendererContext.zRange().includeLower() ) );
363 transparentPixels.append( QgsRasterTransparency::TransparentSingleValuePixel( adjustedUpper, std::numeric_limits<double>::max(), 0, !rendererContext.zRange().includeUpper(), true ) );
364
365 transparency->setTransparentSingleValuePixelList( transparentPixels );
366 mPipe->renderer()->setRasterTransparency( transparency.release() );
367 }
368 break;
369 }
370 }
371 }
372 }
373
374 mFeedback->setRenderContext( rendererContext );
375
376 mPipe->moveToThread( nullptr );
377
378 mPreparationTime = timer.elapsed();
379}
380
382{
383 delete mFeedback;
384
385 delete mRasterViewPort;
386}
387
389{
390 std::unique_ptr< QgsScopedRuntimeProfile > profile;
391 if ( mEnableProfile )
392 {
393 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, QStringLiteral( "rendering" ), layerId() );
394 if ( mPreparationTime > 0 )
395 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, QStringLiteral( "rendering" ) );
396 }
397
398 // Skip rendering of out of view tiles (xyz)
399 if ( !mRasterViewPort || ( renderContext()->testFlag( Qgis::RenderContextFlag::RenderPreviewJob ) &&
400 !( mProviderCapabilities &
401 QgsRasterInterface::Capability::Prefetch ) ) )
402 return true;
403
404 mPipe->moveToThread( QThread::currentThread() );
405
406 QElapsedTimer time;
407 time.start();
408
409 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
410 if ( mEnableProfile )
411 {
412 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), QStringLiteral( "rendering" ) );
413 }
414
415 //
416 //
417 // The goal here is to make as many decisions as possible early on (outside of the rendering loop)
418 // so that we can maximise performance of the rendering process. So now we check which drawing
419 // procedure to use :
420 //
421
422 const QgsScopedQPainterState painterSate( renderContext()->painter() );
423 if ( !mClippingRegions.empty() )
424 {
425 bool needsPainterClipPath = false;
426 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::Raster, needsPainterClipPath );
427 if ( needsPainterClipPath )
428 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
429 }
430
431 QgsRasterProjector *projector = mPipe->projector();
432 bool restoreOldResamplingStage = false;
433 const Qgis::RasterResamplingStage oldResamplingState = mPipe->resamplingStage();
434
435 // TODO add a method to interface to get provider and get provider
436 // params in QgsRasterProjector
437 if ( projector )
438 {
439 // Force provider resampling if reprojection is needed
440 if ( ( mPipe->provider()->providerCapabilities() & QgsRasterDataProvider::ProviderHintCanPerformProviderResampling ) &&
441 mRasterViewPort->mSrcCRS != mRasterViewPort->mDestCRS &&
442 oldResamplingState != Qgis::RasterResamplingStage::Provider )
443 {
444 restoreOldResamplingStage = true;
445 mPipe->setResamplingStage( Qgis::RasterResamplingStage::Provider );
446 }
447 projector->setCrs( mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS, mRasterViewPort->mTransformContext );
448 }
449
450 // important -- disable SmoothPixmapTransform for raster layer renders. We want individual pixels to be clearly defined!
451 renderContext()->painter()->setRenderHint( QPainter::SmoothPixmapTransform, false );
452
453 preparingProfile.reset();
454 std::unique_ptr< QgsScopedRuntimeProfile > renderingProfile;
455 if ( mEnableProfile )
456 {
457 renderingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering" ), QStringLiteral( "rendering" ) );
458 }
459
460 // Drawer to pipe?
461 QgsRasterIterator iterator( mPipe->last() );
462 QgsRasterDrawer drawer( &iterator );
463 drawer.draw( *( renderContext() ), mRasterViewPort, mFeedback );
464
465 if ( mDrawElevationMap )
466 drawElevationMap();
467
468 if ( restoreOldResamplingStage )
469 {
470 mPipe->setResamplingStage( oldResamplingState );
471 }
472
473 const QStringList errors = mFeedback->errors();
474 for ( const QString &error : errors )
475 {
476 mErrors.append( error );
477 }
478
479 QgsDebugMsgLevel( QStringLiteral( "total raster draw time (ms): %1" ).arg( time.elapsed(), 5 ), 4 );
480 mReadyToCompose = true;
481
482 mPipe->moveToThread( nullptr );
483
484 return !mFeedback->isCanceled();
485}
486
488{
489 return mFeedback;
490}
491
493{
494 if ( !mRasterViewPort || !mPipe )
495 return false; // this layer is not going to get rendered
496
497 // preview of intermediate raster rendering results requires a temporary output image
499 return true;
500
501 if ( QgsRasterRenderer *renderer = mPipe->renderer() )
502 {
504 && renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) ) )
505 return true;
506 }
507
508 return false;
509}
510
511void QgsRasterLayerRenderer::drawElevationMap()
512{
513 QgsRasterDataProvider *dataProvider = mPipe->provider();
514 if ( renderContext()->elevationMap() && dataProvider )
515 {
516 double dpiScalefactor;
517
518 if ( renderContext()->dpiTarget() >= 0.0 )
519 dpiScalefactor = renderContext()->dpiTarget() / ( renderContext()->scaleFactor() * 25.4 );
520 else
521 dpiScalefactor = 1.0;
522
523 int outputWidth = static_cast<int>( static_cast<double>( mRasterViewPort->mWidth ) / dpiScalefactor * renderContext()->devicePixelRatio() );
524 int outputHeight = static_cast<int>( static_cast<double>( mRasterViewPort->mHeight ) / dpiScalefactor * renderContext()->devicePixelRatio() );
525
526 QSize viewSize = renderContext()->deviceOutputSize();
527 int viewWidth = static_cast<int>( viewSize.width() / dpiScalefactor );
528 int viewHeight = static_cast<int>( viewSize.height() / dpiScalefactor );
529
530 bool canRenderElevation = false;
531 std::unique_ptr<QgsRasterBlock> elevationBlock;
532 if ( mRasterViewPort->mSrcCRS == mRasterViewPort->mDestCRS )
533 {
534 elevationBlock.reset(
535 dataProvider->block(
536 mElevationBand,
537 mRasterViewPort->mDrawnExtent,
538 outputWidth,
539 outputHeight,
540 mFeedback ) );
541 canRenderElevation = true;
542 }
543 else
544 {
545 // Destinaton CRS is different from the source CRS.
546 // Using the raster projector lead to have big artifacts when rendering the elevation map.
547 // To get a smoother elevation map, we use GDAL resampling with coordinates transform
548 QgsRectangle viewExtentInLayerCoordinate = renderContext()->extent();
549
550 // If view extent is infinite, we use the data provider extent
551 if ( viewExtentInLayerCoordinate.xMinimum() == std::numeric_limits<double>::lowest() &&
552 viewExtentInLayerCoordinate.yMinimum() == std::numeric_limits<double>::lowest() &&
553 viewExtentInLayerCoordinate.xMaximum() == std::numeric_limits<double>::max() &&
554 viewExtentInLayerCoordinate.yMaximum() == std::numeric_limits<double>::max() )
555 {
556 viewExtentInLayerCoordinate = dataProvider->extent();
557 }
558
559 double xLayerResol = viewExtentInLayerCoordinate.width() / static_cast<double>( viewWidth );
560 double yLayerResol = viewExtentInLayerCoordinate.height() / static_cast<double>( viewHeight );
561
562 double overSampling = 1;
563 if ( mPipe->resampleFilter() )
564 overSampling = mPipe->resampleFilter()->maxOversampling();
565
566 if ( dataProvider->capabilities() & QgsRasterDataProvider::Size )
567 {
568 // If the dataprovider has size capability, we calculate the requested resolution to provider
569 double providerXResol = dataProvider->extent().width() / dataProvider->xSize();
570 double providerYResol = dataProvider->extent().height() / dataProvider->ySize();
571 overSampling = ( xLayerResol / providerXResol + yLayerResol / providerYResol ) / 2;
572 }
573
574 GDALResampleAlg alg;
575 if ( overSampling > 1 )
577 else
579
580 Qgis::DataType dataType = dataProvider->dataType( mElevationBand );
581
582 if ( dataType != Qgis::DataType::UnknownDataType ) // resampling data by GDAL is not compatible with unknown data type
583 {
584 // we need extra pixels on border to avoid effect border with resampling (at least 2 pixels band for cubic alg)
585 int sourceWidth = viewWidth + 4;
586 int sourceHeight = viewHeight + 4;
587 viewExtentInLayerCoordinate = QgsRectangle(
588 viewExtentInLayerCoordinate.xMinimum() - xLayerResol * 2,
589 viewExtentInLayerCoordinate.yMinimum() - yLayerResol * 2,
590 viewExtentInLayerCoordinate.xMaximum() + xLayerResol * 2,
591 viewExtentInLayerCoordinate.yMaximum() + yLayerResol * 2 );
592
593 // Now we can do the resampling
594 std::unique_ptr<QgsRasterBlock> sourcedata( dataProvider->block( mElevationBand, viewExtentInLayerCoordinate, sourceWidth, sourceHeight, mFeedback ) );
595 gdal::dataset_unique_ptr gdalDsInput =
596 QgsGdalUtils::blockToSingleBandMemoryDataset( viewExtentInLayerCoordinate, sourcedata.get() );
597
598
599 elevationBlock.reset( new QgsRasterBlock( dataType,
600 outputWidth,
601 outputHeight ) );
602
603 elevationBlock->setNoDataValue( dataProvider->sourceNoDataValue( mElevationBand ) );
604
605 gdal::dataset_unique_ptr gdalDsOutput =
606 QgsGdalUtils::blockToSingleBandMemoryDataset( mRasterViewPort->mDrawnExtent, elevationBlock.get() );
607
608 // For coordinate transformation, we try to obtain a coordinate operation string from the transform context.
609 // Depending of the CRS, if we can't we use GDAL transformation directly from the source and destination CRS
610 QString coordinateOperation;
611 const QgsCoordinateTransformContext &transformContext = renderContext()->transformContext();
612 if ( transformContext.mustReverseCoordinateOperation( mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS ) )
613 coordinateOperation = transformContext.calculateCoordinateOperation( mRasterViewPort->mDestCRS, mRasterViewPort->mSrcCRS );
614 else
615 coordinateOperation = transformContext.calculateCoordinateOperation( mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS );
616
617 if ( coordinateOperation.isEmpty() )
618 canRenderElevation = QgsGdalUtils::resampleSingleBandRaster( gdalDsInput.get(), gdalDsOutput.get(), alg,
619 mRasterViewPort->mSrcCRS, mRasterViewPort->mDestCRS );
620 else
621 canRenderElevation = QgsGdalUtils::resampleSingleBandRaster( gdalDsInput.get(), gdalDsOutput.get(), alg,
622 coordinateOperation.toUtf8().constData() );
623 }
624 }
625
626 if ( canRenderElevation )
627 {
628 QPoint topLeft;
629 if ( renderContext()->mapToPixel().mapRotation() != 0 )
630 {
631 // Now rendering elevation on the elevation map, we need to take care of rotation:
632 // again a resampling but this time with a geotransform.
633 const QgsMapToPixel &mtp = renderContext()->mapToPixel();
635
636 int elevMapWidth = elevMap->rawElevationImage().width();
637 int elevMapHeight = elevMap->rawElevationImage().height();
638
639 int bottom = 0;
640 int top = elevMapHeight;
641 int left = elevMapWidth;
642 int right = 0;
643
644 QList<QgsPointXY> corners;
645 corners << QgsPointXY( mRasterViewPort->mDrawnExtent.xMinimum(), mRasterViewPort->mDrawnExtent.yMinimum() )
646 << QgsPointXY( mRasterViewPort->mDrawnExtent.xMaximum(), mRasterViewPort->mDrawnExtent.yMaximum() )
647 << QgsPointXY( mRasterViewPort->mDrawnExtent.xMinimum(), mRasterViewPort->mDrawnExtent.yMaximum() )
648 << QgsPointXY( mRasterViewPort->mDrawnExtent.xMaximum(), mRasterViewPort->mDrawnExtent.yMinimum() );
649
650 for ( const QgsPointXY &corner : std::as_const( corners ) )
651 {
652 const QgsPointXY dpt = mtp.transform( corner );
653 int x = static_cast<int>( std::round( dpt.x() ) );
654 int y = static_cast<int>( std::round( dpt.y() ) );
655
656 if ( x < left )
657 left = x;
658 if ( x > right )
659 right = x;
660 if ( y < top )
661 top = y;
662 if ( y > bottom )
663 bottom = y;
664 }
665
666 const QgsPointXY origin = mtp.toMapCoordinates( left, top );
667 double gridXSize = mtp.toMapCoordinates( right, top ).distance( origin );
668 double gridYSize = mtp.toMapCoordinates( left, bottom ).distance( origin );
669 double angleRad = renderContext()->mapToPixel().mapRotation() / 180 * M_PI;
670
671 gdal::dataset_unique_ptr gdalDsInput =
672 QgsGdalUtils::blockToSingleBandMemoryDataset( mRasterViewPort->mDrawnExtent, elevationBlock.get() );
673
674 std::unique_ptr<QgsRasterBlock> rotatedElevationBlock =
675 std::make_unique<QgsRasterBlock>( elevationBlock->dataType(),
676 ( right - left ) * renderContext()->devicePixelRatio() + 1,
677 ( bottom - top ) * renderContext()->devicePixelRatio() + 1 );
678
679 rotatedElevationBlock->setNoDataValue( elevationBlock->noDataValue() );
680
681 gdal::dataset_unique_ptr gdalDsOutput =
682 QgsGdalUtils::blockToSingleBandMemoryDataset( angleRad, origin, gridXSize, gridYSize, rotatedElevationBlock.get() );
683
685 gdalDsInput.get(),
686 gdalDsOutput.get(),
688 {
689 elevationBlock.reset( rotatedElevationBlock.release() );
690 }
691
692 topLeft = QPoint( left, top );
693 }
694 else
695 {
696 topLeft = mRasterViewPort->mTopLeftPoint.toQPointF().toPoint();
697 }
698
700 elevationBlock.get(),
701 topLeft.y() * renderContext()->devicePixelRatio(),
702 topLeft.x() * renderContext()->devicePixelRatio(),
703 mElevationScale,
704 mElevationOffset );
705 }
706 }
707}
The Qgis class provides global constants for use throughout the application.
Definition: qgis.h:54
RasterResamplingStage
Stage at which raster resampling occurs.
Definition: qgis.h:1138
@ Provider
Resampling occurs in Provider.
@ InternalLayerOpacityHandling
The renderer internally handles the raster layer's opacity, so the default layer level opacity handli...
@ FixedRangePerBand
Layer has a fixed (manually specified) elevation range per band.
@ FixedElevationRange
Layer has a fixed elevation range.
@ RepresentsElevationSurface
Pixel values represent an elevation surface.
@ DynamicRangePerBand
Layer has a elevation range per band, calculated dynamically from an expression.
@ RedrawLayerOnly
Redraw the layer when temporal range changes, but don't apply any filtering. Useful when raster symbo...
@ FixedRangePerBand
Layer has a fixed temporal range per band (since QGIS 3.38)
@ TemporalRangeFromDataProvider
Mode when raster layer delegates temporal range handling to the dataprovider.
@ FixedTemporalRange
Mode when temporal properties have fixed start and end datetimes.
DataType
Raster data types.
Definition: qgis.h:269
@ UnknownDataType
Unknown or unspecified type.
@ Raster
Raster layer.
@ RenderPreviewJob
Render is a 'canvas preview' render, and shortcuts should be taken to ensure fast rendering.
@ RenderPartialOutput
Whether to make extra effort to update map image with partially rendered layers (better for interacti...
@ Render3DMap
Render is for a 3D map.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
QString calculateCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the Proj coordinate operation string to use when transforming from the specified source CRS t...
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
bool isInfinite() const
Returns true if the range consists of all possible values.
Definition: qgsrange.h:285
Stores digital elevation model in a raster image which may get updated as a part of map layer renderi...
QImage rawElevationImage() const
Returns raw elevation image with elevations encoded as color values.
void fillWithRasterBlock(QgsRasterBlock *block, int top, int left, double zScale=1.0, double offset=0.0)
Fills the elevation map with values contains in a raster block starting from position defined by top ...
QString what() const
Definition: qgsexception.h:49
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition: qgsfeedback.h:44
static GDALResampleAlg gdalResamplingAlgorithm(QgsRasterDataProvider::ResamplingMethod method)
Returns the GDAL resampling method corresponding to the QGIS resampling method.
static bool resampleSingleBandRaster(GDALDatasetH hSrcDS, GDALDatasetH hDstDS, GDALResampleAlg resampleAlg, const char *pszCoordinateOperation)
Resamples a single band raster to the destination dataset with different resolution (and possibly wit...
static gdal::dataset_unique_ptr blockToSingleBandMemoryDataset(int pixelWidth, int pixelHeight, const QgsRectangle &extent, void *block, GDALDataType dataType)
Converts a data block to a single band GDAL memory dataset.
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, Qgis::LayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
static QList< QgsMapClippingRegion > collectClippingRegionsForLayer(const QgsRenderContext &context, const QgsMapLayer *layer)
Collects the list of map clipping regions from a context which apply to a map layer.
double zScale() const
Returns the z scale, which is a scaling factor which should be applied to z values from the layer.
virtual bool hasElevation() const
Returns true if the layer has an elevation or z component.
double zOffset() const
Returns the z offset, which is a fixed offset amount which should be added to z values from the layer...
Base class for utility classes that encapsulate information necessary for rendering of map layers.
bool mReadyToCompose
The flag must be set to false in renderer's constructor if wants to use the smarter map redraws funct...
QString layerId() const
Gets access to the ID of the layer rendered by this class.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
QStringList errors() const
Returns list of errors (problems) that happened during the rendering.
virtual QgsRectangle extent() const
Returns the extent of the layer.
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:81
void statusChanged(const QString &status)
Emit a signal with status (e.g. to be caught by QgisApp and display a msg on status bar)
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:39
int mapHeight() const
Returns current map height in pixels.
void setMapRotation(double degrees, double cx, double cy)
Sets map rotation in degrees (clockwise).
double mapUnitsPerPixel() const
Returns the current map units per pixel.
QgsPointXY toMapCoordinates(int x, int y) const
Transforms device coordinates to map (world) coordinates.
int mapWidth() const
Returns the current map width in pixels.
QgsPointXY transform(const QgsPointXY &p) const
Transforms a point p from map (world) coordinates to device coordinates.
Definition: qgsmaptopixel.h:88
double mapRotation() const
Returns the current map rotation in degrees (clockwise).
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A class to represent a 2D point.
Definition: qgspointxy.h:60
double distance(double x, double y) const
Returns the distance between this point and a specified x, y coordinate.
Definition: qgspointxy.h:207
void setY(double y)
Sets the y value of the point.
Definition: qgspointxy.h:130
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
void setX(double x)
Sets the x value of the point.
Definition: qgspointxy.h:120
QPointF toQPointF() const
Converts a point to a QPointF.
Definition: qgspointxy.h:166
bool includeUpper() const
Returns true if the upper bound is inclusive, or false if the upper bound is exclusive.
Definition: qgsrange.h:101
T lower() const
Returns the lower bound of the range.
Definition: qgsrange.h:78
bool includeLower() const
Returns true if the lower bound is inclusive, or false if the lower bound is exclusive.
Definition: qgsrange.h:93
T upper() const
Returns the upper bound of the range.
Definition: qgsrange.h:85
Feedback object tailored for raster block reading.
void setPreviewOnly(bool preview)
set flag whether the block request is for preview purposes only
void setRenderPartialOutput(bool enable)
Set whether our painter is drawing to a temporary image used just by this layer.
Raster data container.
Implementation of data provider temporal properties for QgsRasterDataProviders.
Base class for raster data providers.
virtual double sourceNoDataValue(int bandNo) const
Value representing no data value.
ResamplingMethod zoomedOutResamplingMethod() const
Returns resampling method for zoomed-out operations.
void setDpi(int dpi)
Sets the output device resolution.
QgsRectangle extent() const override=0
Returns the extent of the layer.
Qgis::DataType dataType(int bandNo) const override=0
Returns data type for the band specified by number.
@ ProviderHintCanPerformProviderResampling
Provider can perform resampling (to be opposed to post rendering resampling) (since QGIS 3....
QgsRasterBlock * block(int bandNo, const QgsRectangle &boundingBox, int width, int height, QgsRasterBlockFeedback *feedback=nullptr) override
Read block of data using given extent and size.
void statusChanged(const QString &) const
Emit a message to be displayed on status bar, usually used by network providers (WMS,...
ResamplingMethod zoomedInResamplingMethod() const
Returns resampling method for zoomed-in operations.
The drawing pipe for raster layers.
void draw(QPainter *p, QgsRasterViewPort *viewPort, const QgsMapToPixel *qgsMapToPixel, QgsRasterBlockFeedback *feedback=nullptr)
Draws raster data.
@ Size
Original data source size (and thus resolution) is known, it is not always available,...
virtual int xSize() const
Gets raster size.
virtual int capabilities() const
Returns a bitmask containing the supported capabilities.
virtual int ySize() const
Iterator for sequentially processing raster cells.
Raster layer specific subclass of QgsMapLayerElevationProperties.
Qgis::RasterElevationMode mode() const
Returns the elevation mode.
int bandNumber() const
Returns the band number from which the elevation should be taken.
int bandForElevationRange(QgsRasterLayer *layer, const QgsDoubleRange &range) const
Returns the band corresponding to the specified range.
Implementation of threaded rendering for raster layers.
bool render() override
Do the rendering (based on data stored in the class).
QgsRasterLayerRenderer(QgsRasterLayer *layer, QgsRenderContext &rendererContext)
bool forceRasterRender() const override
Returns true if the renderer must be rendered to a raster paint device (e.g.
QgsFeedback * feedback() const override
Access to feedback object of the layer renderer (may be nullptr)
Implementation of map layer temporal properties for raster layers.
Qgis::TemporalIntervalMatchMethod intervalHandlingMethod() const
Returns the desired method to use when resolving a temporal interval to matching layers or bands in t...
Qgis::RasterTemporalMode mode() const
Returns the temporal properties mode.
int bandForTemporalRange(QgsRasterLayer *layer, const QgsDateTimeRange &range) const
Returns the band corresponding to the specified range.
Represents a raster layer.
int height() const
Returns the height of the (unclipped) raster.
void refreshRendererIfNeeded(QgsRasterRenderer *rasterRenderer, const QgsRectangle &extent)
Refresh renderer with new extent, if needed.
QgsMapLayerTemporalProperties * temporalProperties() override
Returns the layer's temporal properties.
QgsRasterPipe * pipe()
Returns the raster pipe.
bool ignoreExtents() const
If the ignoreExtent flag is set, the layer will also render outside the bounding box reported by the ...
QgsRasterDataProvider * dataProvider() override
Returns the source data provider.
int width() const
Returns the width of the (unclipped) raster.
QgsMapLayerElevationProperties * elevationProperties() override
Returns the layer's elevation properties.
Contains a pipeline of raster interfaces for sequential raster processing.
Definition: qgsrasterpipe.h:50
QgsRasterProjector implements approximate projection support for it calculates grid of points in sour...
Q_DECL_DEPRECATED void setCrs(const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS, int srcDatumTransform=-1, int destDatumTransform=-1)
Sets the source and destination CRS.
Raster renderer pipe that applies colors to a raster.
Defines the list of pixel values to be considered as transparent or semi transparent when rendering r...
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:236
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
void set(const QgsPointXY &p1, const QgsPointXY &p2, bool normalize=true)
Sets the rectangle from two QgsPoints.
Definition: qgsrectangle.h:120
bool isEmpty() const
Returns true if the rectangle has no area.
Definition: qgsrectangle.h:492
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:243
void setNull()
Mark a rectangle as being null (holding no spatial information).
Definition: qgsrectangle.h:176
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:355
Contains information about the context of a rendering operation.
double scaleFactor() const
Returns the scaling factor for the render to convert painter units to physical sizes.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
QgsCoordinateTransformContext transformContext() const
Returns the context's coordinate transform context, which stores various information regarding which ...
QgsElevationMap * elevationMap() const
Returns the destination elevation map for the render operation.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
void setDpiTarget(double dpi)
Sets the targeted dpi for rendering.
float devicePixelRatio() const
Returns the device pixel ratio.
double dpiTarget() const
Returns the targeted DPI for rendering.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QgsDoubleRange zRange() const
Returns the range of z-values which should be rendered.
QSize deviceOutputSize() const
Returns the device output size of the render.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
Qgis::RenderContextFlags flags() const
Returns combination of flags used for rendering.
void record(const QString &name, double time, const QString &group="startup", const QString &id=QString())
Manually adds a profile event with the given name and total time (in seconds).
Scoped object for saving and restoring a QPainter object's state.
bool isActive() const
Returns true if the temporal property is active.
const QgsDateTimeRange & temporalRange() const
Returns the datetime range for the object.
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
Definition: qgsogrutils.h:157
unsigned long long qgssize
Qgssize is used instead of size_t, because size_t is stdlib type, unknown by SIP, and it would be har...
Definition: qgis.h:5747
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsTemporalRange< QDateTime > QgsDateTimeRange
QgsRange which stores a range of date times.
Definition: qgsrange.h:742
Defines the transparency for a range of single-band pixel values.
This class provides details of the viewable area that a raster will be rendered into.
qgssize mHeight
Height, number of rows to be rendered.
QgsCoordinateReferenceSystem mDestCRS
Target coordinate system.
QgsPointXY mBottomRightPoint
Coordinate (in output device coordinate system) of bottom right corner of the part of the raster that...
QgsPointXY mTopLeftPoint
Coordinate (in output device coordinate system) of top left corner of the part of the raster that is ...
QgsCoordinateReferenceSystem mSrcCRS
Source coordinate system.
QgsRectangle mDrawnExtent
Intersection of current map extent and layer extent.
QgsCoordinateTransformContext mTransformContext
Coordinate transform context.
qgssize mWidth
Width, number of columns to be rendered.