QGIS API Documentation 4.3.0-Master (16eddb2670e)
Loading...
Searching...
No Matches
qgsvectortilelayerrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvectortilelayerrenderer.cpp
3 --------------------------------------
4 Date : March 2020
5 Copyright : (C) 2020 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 <memory>
19
20#include "qgsapplication.h"
22#include "qgsfeedback.h"
23#include "qgslabelingengine.h"
24#include "qgslogger.h"
25#include "qgsmapclippingutils.h"
26#include "qgspainting.h"
27#include "qgsrendercontext.h"
28#include "qgsruntimeprofiler.h"
29#include "qgstextrenderer.h"
30#include "qgsthreadingutils.h"
33#include "qgsvectortilelayer.h"
34#include "qgsvectortileloader.h"
36#include "qgsvectortileutils.h"
37
38#include <QElapsedTimer>
39#include <QString>
40#include <QThread>
41
42using namespace Qt::StringLiterals;
43
45 : QgsMapLayerRenderer( layer->id(), &context )
46 , mLayerName( layer->name() )
47 , mDataProvider( qgis::down_cast< const QgsVectorTileDataProvider * >( layer->dataProvider() )->clone() )
48 , mRenderer( layer->renderer()->clone() )
49 , mLayerBlendMode( layer->blendMode() )
50 , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
51 , mLabelsEnabled( layer->labelsEnabled() )
52 , mFeedback( new QgsFeedback )
53 , mSelectedFeatures( layer->selectedFeatures() )
54 , mLayerOpacity( layer->opacity() )
55 , mTileMatrixSet( layer->tileMatrixSet() )
56 , mEnableProfile( context.flags() & Qgis::RenderContextFlag::RecordProfile )
57{
58 QElapsedTimer timer;
59 timer.start();
60
61 if ( QgsLabelingEngine *engine = context.labelingEngine() )
62 {
63 if ( layer->labelsEnabled() )
64 {
65 mLabelProvider = layer->labeling()->provider( layer );
66 if ( mLabelProvider )
67 {
68 engine->addProvider( mLabelProvider );
69 }
70 }
71 }
72
73 mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer );
74
75 mDataProvider->moveToThread( nullptr );
76
77 mPreparationTime = timer.elapsed();
78}
79
81
83{
84 QgsScopedThreadName threadName( u"render:%1"_s.arg( mLayerName ) );
85
86 std::unique_ptr< QgsScopedRuntimeProfile > profile;
87 if ( mEnableProfile )
88 {
89 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, u"rendering"_s, layerId() );
90 if ( mPreparationTime > 0 )
91 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, u"rendering"_s );
92 }
93
95
96 if ( ctx.renderingStopped() )
97 return false;
98
99 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
100 if ( mEnableProfile )
101 {
102 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), u"rendering"_s );
103 }
104
105 mDataProvider->moveToThread( QThread::currentThread() );
106
107 const QgsScopedQPainterState painterState( ctx.painter() );
108
109 if ( !mClippingRegions.empty() )
110 {
111 bool needsPainterClipPath = false;
112 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::VectorTile, needsPainterClipPath );
113 if ( needsPainterClipPath )
114 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
115 }
116
117 QElapsedTimer tTotal;
118 tTotal.start();
119
120 const double tileRenderScale = mTileMatrixSet.scaleForRenderContext( ctx );
121
122 QgsRectangle extent = ctx.extent();
123 if ( extent.isMaximal() )
124 {
125 // invalid extent. We don't want to fetch ALL tiles at the zoom level!
126 // This has occurred because the map renderer is being pessimistic and thinks a worst-case
127 // scenario is acceptable when it failed to transform the map extent to the layer's crs.
128 // But while that's permissible eg for a local raster layer, it's entirely inappropriate
129 // for vector tiles!
130 // So let's try and determine a MINIMAL extent for the layer, avoiding the pessimism used
131 // in the generic map renderer code
132 QgsCoordinateTransform layerToMapTransform = ctx.coordinateTransform();
133 layerToMapTransform.setAllowFallbackTransforms( true );
134 layerToMapTransform.setBallparkTransformsAreAppropriate( true );
135
136 QgsRectangle extentInMapCrs = ctx.mapExtent();
137 try
138 {
139 extent = layerToMapTransform.transformBoundingBox( extentInMapCrs, Qgis::TransformDirection::Reverse );
140 }
141 catch ( QgsCsException &cs )
142 {
143 QgsDebugError( u"Could not transform map extent to layer extent -- cannot calculate valid extent for vector tiles, aborting: %1"_s.arg( cs.what() ) );
144 return false;
145 }
146 }
147
148 QgsDebugMsgLevel( u"Vector tiles rendering extent: "_s + extent.toString( -1 ), 2 );
149 QgsDebugMsgLevel( u"Vector tiles map scale 1 : %1"_s.arg( tileRenderScale ), 2 );
150
151 mTileZoomToFetch = mTileMatrixSet.scaleToZoomLevel( tileRenderScale );
152 QgsDebugMsgLevel( u"Vector tiles zoom level: %1"_s.arg( mTileZoomToFetch ), 2 );
153 mTileZoomToRender = mTileMatrixSet.scaleToZoomLevel( tileRenderScale, false );
154 QgsDebugMsgLevel( u"Render zoom level: %1"_s.arg( mTileZoomToRender ), 2 );
155
156 mTileMatrix = mTileMatrixSet.tileMatrix( mTileZoomToFetch );
157
158 mTileRange = mTileMatrix.tileRangeFromExtent( extent );
160 u"Vector tiles range X: %1 - %2 Y: %3 - %4 (%5 tiles total)"_s.arg( mTileRange.startColumn() )
161 .arg( mTileRange.endColumn() )
162 .arg( mTileRange.startRow() )
163 .arg( mTileRange.endRow() )
164 .arg( mTileRange.count() ),
165 2
166 );
167
168 // view center is used to sort the order of tiles for fetching and rendering
169 const QPointF viewCenter = mTileMatrix.mapToTileCoordinates( extent.center() );
170
171 if ( !mTileRange.isValid() )
172 {
173 QgsDebugMsgLevel( u"Vector tiles - outside of range"_s, 2 );
174 return true; // nothing to do
175 }
176
177 preparingProfile.reset();
178 std::unique_ptr< QgsScopedRuntimeProfile > renderingProfile;
179 if ( mEnableProfile )
180 {
181 renderingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering" ), u"rendering"_s );
182 }
183
184 std::unique_ptr<QgsVectorTileLoader> asyncLoader;
185 QList<QgsVectorTileRawData> rawTiles;
186 if ( !mDataProvider->supportsAsync() )
187 {
188 QElapsedTimer tFetch;
189 tFetch.start();
190 rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mDataProvider.get(), mTileMatrixSet, viewCenter, mTileRange, mTileZoomToFetch, mFeedback.get() );
191 QgsDebugMsgLevel( u"Tile fetching time: %1"_s.arg( tFetch.elapsed() / 1000. ), 2 );
192 QgsDebugMsgLevel( u"Fetched tiles: %1"_s.arg( rawTiles.count() ), 2 );
193 }
194 else
195 {
196 asyncLoader = std::make_unique<QgsVectorTileLoader>( mDataProvider.get(), mTileMatrixSet, mTileRange, mTileZoomToFetch, viewCenter, mFeedback.get(), renderContext()->rendererUsage() );
197 QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, asyncLoader.get(), [this]( const QgsVectorTileRawData &rawTile ) {
198 QgsDebugMsgLevel( u"Got tile asynchronously: "_s + rawTile.id.toString(), 2 );
199 if ( !rawTile.data.isEmpty() )
200 decodeAndDrawTile( rawTile );
201 } );
202 }
203
204 if ( ctx.renderingStopped() )
205 return false;
206
207 // add @zoom_level and @vector_tile_zoom variables which can be used in styling
208 QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
209 scope->setVariable( u"zoom_level"_s, mTileZoomToRender, true );
210 scope->setVariable( u"vector_tile_zoom"_s, mTileMatrixSet.scaleToZoom( tileRenderScale ), true );
211 const QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
212
213 mRenderer->startRender( *renderContext(), mTileZoomToRender, mTileRange );
214
215 // Draw background style if present
216 mRenderer->renderBackground( ctx );
217
218 QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( ctx );
219
220 if ( mLabelProvider )
221 {
222 const QMap<QString, QSet<QString> > requiredFieldsLabeling = mLabelProvider->usedAttributes( ctx, mTileZoomToRender );
223 for ( auto it = requiredFieldsLabeling.begin(); it != requiredFieldsLabeling.end(); ++it )
224 {
225 requiredFields[it.key()].unite( it.value() );
226 }
227 }
228
229 for ( auto it = requiredFields.constBegin(); it != requiredFields.constEnd(); ++it )
230 mPerLayerFields[it.key()] = QgsVectorTileUtils::makeQgisFields( it.value() );
231
232 mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoomToRender );
233
234 if ( mLabelProvider )
235 {
236 mLabelProvider->setFields( mPerLayerFields );
237 QSet<QString> attributeNames; // we don't need this - already got referenced columns in provider constructor
238 if ( !mLabelProvider->prepare( ctx, attributeNames ) )
239 {
240 ctx.labelingEngine()->removeProvider( mLabelProvider );
241 mLabelProvider = nullptr; // provider is deleted by the engine
242 }
243 else
244 {
245 mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoomToRender ) );
246 }
247 }
248
249 if ( !mDataProvider->supportsAsync() )
250 {
251 for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
252 {
253 if ( ctx.renderingStopped() )
254 break;
255
256 decodeAndDrawTile( rawTile );
257 }
258 }
259 else
260 {
261 // Block until tiles are fetched and rendered. If the rendering gets canceled at some point,
262 // the async loader will catch the signal, abort requests and return from downloadBlocking()
263 asyncLoader->downloadBlocking();
264 if ( !asyncLoader->error().isEmpty() )
265 mErrors.append( asyncLoader->error() );
266 }
267
268 // Register labels features when all tiles are fetched to ensure consistent labeling
269 if ( mLabelProvider )
270 {
271 for ( const auto &tile : mTileDataMap )
272 {
273 mLabelProvider->registerTileFeatures( tile, ctx );
274 }
275 }
276
278 mRenderer->renderSelectedFeatures( mSelectedFeatures, ctx );
279
280 mRenderer->stopRender( ctx );
281
282 QgsDebugMsgLevel( u"Total time for decoding: %1"_s.arg( mTotalDecodeTime / 1000. ), 2 );
283 QgsDebugMsgLevel( u"Drawing time: %1"_s.arg( mTotalDrawTime / 1000. ), 2 );
284 QgsDebugMsgLevel( u"Total time: %1"_s.arg( tTotal.elapsed() / 1000. ), 2 );
285
286 return !ctx.renderingStopped();
287}
288
290{
291 switch ( renderContext()->rasterizedRenderingPolicy() )
292 {
295 break;
296
298 return false;
299 }
300
301 if ( !qgsDoubleNear( mLayerOpacity, 1.0 ) )
302 return true;
303
304 if ( mLayerBlendMode != QPainter::CompositionMode_SourceOver )
305 return true;
306
307 return false;
308}
309
310void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
311{
313
314 QgsDebugMsgLevel( u"Drawing tile "_s + rawTile.id.toString(), 2 );
315
316 QElapsedTimer tLoad;
317 tLoad.start();
318
319 // currently only MVT encoding supported
320 QgsVectorTileMVTDecoder decoder( mTileMatrixSet );
321 if ( !decoder.decode( rawTile ) )
322 {
323 QgsDebugMsgLevel( u"Failed to parse raw tile data! "_s + rawTile.id.toString(), 2 );
324 return;
325 }
326
327 if ( ctx.renderingStopped() )
328 return;
329
330 const QgsCoordinateTransform ct = ctx.coordinateTransform();
331
332 QgsVectorTileRendererData tile( rawTile.id );
333 tile.setRenderZoomLevel( mTileZoomToRender );
334
335 tile.setFields( mPerLayerFields );
336 tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
337
338 try
339 {
340 tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrixSet.tileMatrix( rawTile.id.zoomLevel() ), ctx.mapToPixel() ) );
341 }
342 catch ( QgsCsException & )
343 {
344 QgsDebugMsgLevel( u"Failed to generate tile polygon "_s + rawTile.id.toString(), 2 );
345 return;
346 }
347
348 mTotalDecodeTime += tLoad.elapsed();
349
350 // calculate tile polygon in screen coordinates
351
352 if ( ctx.renderingStopped() )
353 return;
354
355 {
356 // set up clipping so that rendering does not go behind tile's extent
357 const QgsScopedQPainterState savePainterState( ctx.painter() );
358 // we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
359 // regions setup outside of the vector tile renderer (e.g. layout map clip region)
360 ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
361
362 QElapsedTimer tDraw;
363 tDraw.start();
364
365 mRenderer->renderTile( tile, ctx );
366 mTotalDrawTime += tDraw.elapsed();
367 }
368
369 // Store tile for later use
370 if ( mLabelProvider )
371 mTileDataMap.insert( tile.id().toString(), tile );
372
373 if ( mDrawTileBoundaries )
374 {
375 const QgsScopedQPainterState savePainterState( ctx.painter() );
376 ctx.painter()->setClipping( false );
377
378 QPen pen( Qt::red );
379 pen.setWidth( 3 );
380 QBrush brush( QColor( 255, 0, 0, 40 ), Qt::BrushStyle::Dense3Pattern );
381
382 ctx.painter()->setPen( pen );
383 ctx.painter()->setBrush( brush );
384 ctx.painter()->drawPolygon( tile.tilePolygon() );
385#if 1
386 QgsTextFormat format;
387 format.setColor( QColor( 255, 0, 0 ) );
388 format.buffer().setEnabled( true );
389
391 drawText( QRectF( QPoint( 0, 0 ), ctx.outputSize() ).intersected( tile.tilePolygon().boundingRect() ), 0, Qgis::TextHorizontalAlignment::Center, { tile.id().toString() }, ctx, format, true, Qgis::TextVerticalAlignment::VerticalCenter );
392#endif
393 }
394}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:62
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2917
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2918
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
Definition qgis.h:2919
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:211
@ DrawSelection
Whether vector selections should be shown in the rendered map.
Definition qgis.h:2969
@ VerticalCenter
Center align.
Definition qgis.h:3188
@ Center
Center align.
Definition qgis.h:3169
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2884
static QgsRuntimeProfiler * profiler()
Returns the application runtime profiler.
Handles coordinate transforms between two 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.
void setAllowFallbackTransforms(bool allowed)
Sets whether "ballpark" fallback transformations can be used in the case that the specified coordinat...
Custom exception class for Coordinate Reference System related exceptions.
QString what() const
RAII class to pop scope from an expression context on destruction.
Single scope for storing variables and functions for use within a QgsExpressionContext.
void setVariable(const QString &name, const QVariant &value, bool isStatic=false)
Convenience method for setting a variable in the context scope by name name and value.
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
Provides map labeling functionality.
void removeProvider(QgsAbstractLabelProvider *provider)
Remove provider if the provider's initialization failed. Provider instance is deleted.
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.
QString layerId() const
Gets access to the ID of the layer rendered by this class.
virtual Qgis::MapLayerRendererFlags flags() const
Returns flags which control how the map layer rendering behaves.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
QgsMapLayerRenderer(const QString &layerID, QgsRenderContext *context=nullptr)
Constructor for QgsMapLayerRenderer, with the associated layerID and render context.
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be rounded to the spec...
bool isMaximal() const
Test if the rectangle is the maximal possible rectangle.
QgsPointXY center
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
QSize outputSize() const
Returns the size of the resulting rendered image, in pixels.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
QgsRectangle mapExtent() const
Returns the original extent of the map being rendered.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QgsLabelingEngine * labelingEngine() const
Gets access to new labeling engine (may be nullptr).
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.
Scoped object for setting the current thread name.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
QgsTextBufferSettings & buffer()
Returns a reference to the text buffer settings.
static void drawText(const QRectF &rect, double rotation, Qgis::TextHorizontalAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, Qgis::TextVerticalAlignment vAlignment=Qgis::TextVerticalAlignment::Top, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), Qgis::TextLayoutMode mode=Qgis::TextLayoutMode::Rectangle)
Draws text within a rectangle using the specified settings.
QString toString() const
Returns tile coordinates in a formatted string.
Definition qgstiles.h:60
int zoomLevel() const
Returns tile's zoom level (Z).
Definition qgstiles.h:57
Base class for vector tile layer data providers.
bool forceRasterRender() const override
Returns true if the renderer must be rendered to a raster paint device (e.g.
~QgsVectorTileLayerRenderer() override
QgsVectorTileLayerRenderer(QgsVectorTileLayer *layer, QgsRenderContext &context)
Creates the renderer. Always called from main thread, should copy whatever necessary from the layer.
bool render() override
Do the rendering (based on data stored in the class).
Implements a map layer that is dedicated to rendering of vector tiles.
void tileRequestFinished(const QgsVectorTileRawData &rawTile)
Emitted when a tile request has finished. If a tile request has failed, the returned raw tile byte ar...
static QList< QgsVectorTileRawData > blockingFetchTileRawData(const QgsVectorTileDataProvider *provider, const QgsTileMatrixSet &tileMatrixSet, const QPointF &viewCenter, const QgsTileRange &range, int zoomLevel, QgsFeedback *feedback=nullptr, Qgis::RendererUsage usage=Qgis::RendererUsage::Unknown)
Returns raw tile data for the specified range of tiles. Blocks the caller until all tiles are fetched...
Responsible for decoding raw tile data written with Mapbox Vector Tiles encoding.
Keeps track of raw tile data from one or more sources that need to be decoded.
QgsTileXYZ id
Tile position in tile matrix set.
static QPolygon tilePolygon(QgsTileXYZ id, const QgsCoordinateTransform &ct, const QgsTileMatrix &tm, const QgsMapToPixel &mtp)
Returns polygon (made by four corners of the tile) in screen coordinates.
static QgsFields makeQgisFields(const QSet< QString > &flds)
Returns QgsFields instance based on the set of field names.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7743
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71