QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 "qgsrendercontext.h"
27#include "qgsruntimeprofiler.h"
28#include "qgstextrenderer.h"
29#include "qgsthreadingutils.h"
32#include "qgsvectortilelayer.h"
33#include "qgsvectortileloader.h"
35#include "qgsvectortileutils.h"
36
37#include <QElapsedTimer>
38#include <QThread>
39
41 : QgsMapLayerRenderer( layer->id(), &context )
42 , mLayerName( layer->name() )
43 , mDataProvider( qgis::down_cast< const QgsVectorTileDataProvider* >( layer->dataProvider() )->clone() )
44 , mRenderer( layer->renderer()->clone() )
45 , mLayerBlendMode( layer->blendMode() )
46 , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
47 , mLabelsEnabled( layer->labelsEnabled() )
48 , mFeedback( new QgsFeedback )
49 , mSelectedFeatures( layer->selectedFeatures() )
50 , mLayerOpacity( layer->opacity() )
51 , mTileMatrixSet( layer->tileMatrixSet() )
52 , mEnableProfile( context.flags() & Qgis::RenderContextFlag::RecordProfile )
53{
54 QElapsedTimer timer;
55 timer.start();
56
57 if ( QgsLabelingEngine *engine = context.labelingEngine() )
58 {
59 if ( layer->labelsEnabled() )
60 {
61 mLabelProvider = layer->labeling()->provider( layer );
62 if ( mLabelProvider )
63 {
64 engine->addProvider( mLabelProvider );
65 }
66 }
67 }
68
69 mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer );
70
71 mDataProvider->moveToThread( nullptr );
72
73 mPreparationTime = timer.elapsed();
74}
75
77
79{
80 QgsScopedThreadName threadName( QStringLiteral( "render:%1" ).arg( mLayerName ) );
81
82 std::unique_ptr< QgsScopedRuntimeProfile > profile;
83 if ( mEnableProfile )
84 {
85 profile = std::make_unique< QgsScopedRuntimeProfile >( mLayerName, QStringLiteral( "rendering" ), layerId() );
86 if ( mPreparationTime > 0 )
87 QgsApplication::profiler()->record( QObject::tr( "Create renderer" ), mPreparationTime / 1000.0, QStringLiteral( "rendering" ) );
88 }
89
91
92 if ( ctx.renderingStopped() )
93 return false;
94
95 std::unique_ptr< QgsScopedRuntimeProfile > preparingProfile;
96 if ( mEnableProfile )
97 {
98 preparingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Preparing render" ), QStringLiteral( "rendering" ) );
99 }
100
101 mDataProvider->moveToThread( QThread::currentThread() );
102
103 const QgsScopedQPainterState painterState( ctx.painter() );
104
105 if ( !mClippingRegions.empty() )
106 {
107 bool needsPainterClipPath = false;
108 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::VectorTile, needsPainterClipPath );
109 if ( needsPainterClipPath )
110 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
111 }
112
113 QElapsedTimer tTotal;
114 tTotal.start();
115
116 const double tileRenderScale = mTileMatrixSet.scaleForRenderContext( ctx );
117
118 QgsRectangle extent = ctx.extent();
119 if ( extent.isMaximal() )
120 {
121 // invalid extent. We don't want to fetch ALL tiles at the zoom level!
122 // This has occurred because the map renderer is being pessimistic and thinks a worst-case
123 // scenario is acceptable when it failed to transform the map extent to the layer's crs.
124 // But while that's permissible eg for a local raster layer, it's entirely inappropriate
125 // for vector tiles!
126 // So let's try and determine a MINIMAL extent for the layer, avoiding the pessimism used
127 // in the generic map renderer code
128 QgsCoordinateTransform layerToMapTransform = ctx.coordinateTransform();
129 layerToMapTransform.setAllowFallbackTransforms( true );
130 layerToMapTransform.setBallparkTransformsAreAppropriate( true );
131
132 QgsRectangle extentInMapCrs = ctx.mapExtent();
133 try
134 {
135 extent = layerToMapTransform.transformBoundingBox( extentInMapCrs, Qgis::TransformDirection::Reverse );
136 }
137 catch ( QgsCsException &cs )
138 {
139 QgsDebugError( QStringLiteral( "Could not transform map extent to layer extent -- cannot calculate valid extent for vector tiles, aborting: %1" ).arg( cs.what() ) );
140 return false;
141 }
142 }
143
144 QgsDebugMsgLevel( QStringLiteral( "Vector tiles rendering extent: " ) + extent.toString( -1 ), 2 );
145 QgsDebugMsgLevel( QStringLiteral( "Vector tiles map scale 1 : %1" ).arg( tileRenderScale ), 2 );
146
147 mTileZoomToFetch = mTileMatrixSet.scaleToZoomLevel( tileRenderScale );
148 QgsDebugMsgLevel( QStringLiteral( "Vector tiles zoom level: %1" ).arg( mTileZoomToFetch ), 2 );
149 mTileZoomToRender = mTileMatrixSet.scaleToZoomLevel( tileRenderScale, false );
150 QgsDebugMsgLevel( QStringLiteral( "Render zoom level: %1" ).arg( mTileZoomToRender ), 2 );
151
152 mTileMatrix = mTileMatrixSet.tileMatrix( mTileZoomToFetch );
153
154 mTileRange = mTileMatrix.tileRangeFromExtent( extent );
155 QgsDebugMsgLevel( QStringLiteral( "Vector tiles range X: %1 - %2 Y: %3 - %4 (%5 tiles total)" )
156 .arg( mTileRange.startColumn() ).arg( mTileRange.endColumn() )
157 .arg( mTileRange.startRow() ).arg( mTileRange.endRow() ).arg( mTileRange.count() ), 2 );
158
159 // view center is used to sort the order of tiles for fetching and rendering
160 const QPointF viewCenter = mTileMatrix.mapToTileCoordinates( extent.center() );
161
162 if ( !mTileRange.isValid() )
163 {
164 QgsDebugMsgLevel( QStringLiteral( "Vector tiles - outside of range" ), 2 );
165 return true; // nothing to do
166 }
167
168 preparingProfile.reset();
169 std::unique_ptr< QgsScopedRuntimeProfile > renderingProfile;
170 if ( mEnableProfile )
171 {
172 renderingProfile = std::make_unique< QgsScopedRuntimeProfile >( QObject::tr( "Rendering" ), QStringLiteral( "rendering" ) );
173 }
174
175 std::unique_ptr<QgsVectorTileLoader> asyncLoader;
176 QList<QgsVectorTileRawData> rawTiles;
177 if ( !mDataProvider->supportsAsync() )
178 {
179 QElapsedTimer tFetch;
180 tFetch.start();
181 rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mDataProvider.get(), mTileMatrixSet, viewCenter, mTileRange, mTileZoomToFetch, mFeedback.get() );
182 QgsDebugMsgLevel( QStringLiteral( "Tile fetching time: %1" ).arg( tFetch.elapsed() / 1000. ), 2 );
183 QgsDebugMsgLevel( QStringLiteral( "Fetched tiles: %1" ).arg( rawTiles.count() ), 2 );
184 }
185 else
186 {
187 asyncLoader = std::make_unique<QgsVectorTileLoader>( mDataProvider.get(), mTileMatrixSet, mTileRange, mTileZoomToFetch, viewCenter, mFeedback.get(), renderContext()->rendererUsage() );
188 QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, asyncLoader.get(), [this]( const QgsVectorTileRawData & rawTile )
189 {
190 QgsDebugMsgLevel( QStringLiteral( "Got tile asynchronously: " ) + rawTile.id.toString(), 2 );
191 if ( !rawTile.data.isEmpty() )
192 decodeAndDrawTile( rawTile );
193 } );
194 }
195
196 if ( ctx.renderingStopped() )
197 return false;
198
199 // add @zoom_level and @vector_tile_zoom variables which can be used in styling
200 QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
201 scope->setVariable( QStringLiteral( "zoom_level" ), mTileZoomToRender, true );
202 scope->setVariable( QStringLiteral( "vector_tile_zoom" ), mTileMatrixSet.scaleToZoom( tileRenderScale ), true );
203 const QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
204
205 mRenderer->startRender( *renderContext(), mTileZoomToRender, mTileRange );
206
207 // Draw background style if present
208 mRenderer->renderBackground( ctx );
209
210 QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( ctx );
211
212 if ( mLabelProvider )
213 {
214 const QMap<QString, QSet<QString> > requiredFieldsLabeling = mLabelProvider->usedAttributes( ctx, mTileZoomToRender );
215 for ( auto it = requiredFieldsLabeling.begin(); it != requiredFieldsLabeling.end(); ++it )
216 {
217 requiredFields[it.key()].unite( it.value() );
218 }
219 }
220
221 for ( auto it = requiredFields.constBegin(); it != requiredFields.constEnd(); ++it )
222 mPerLayerFields[it.key()] = QgsVectorTileUtils::makeQgisFields( it.value() );
223
224 mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoomToRender );
225
226 if ( mLabelProvider )
227 {
228 mLabelProvider->setFields( mPerLayerFields );
229 QSet<QString> attributeNames; // we don't need this - already got referenced columns in provider constructor
230 if ( !mLabelProvider->prepare( ctx, attributeNames ) )
231 {
232 ctx.labelingEngine()->removeProvider( mLabelProvider );
233 mLabelProvider = nullptr; // provider is deleted by the engine
234 }
235 else
236 {
237 mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoomToRender ) );
238 }
239 }
240
241 if ( !mDataProvider->supportsAsync() )
242 {
243 for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
244 {
245 if ( ctx.renderingStopped() )
246 break;
247
248 decodeAndDrawTile( rawTile );
249 }
250 }
251 else
252 {
253 // Block until tiles are fetched and rendered. If the rendering gets canceled at some point,
254 // the async loader will catch the signal, abort requests and return from downloadBlocking()
255 asyncLoader->downloadBlocking();
256 if ( !asyncLoader->error().isEmpty() )
257 mErrors.append( asyncLoader->error() );
258 }
259
260 // Register labels features when all tiles are fetched to ensure consistent labeling
261 if ( mLabelProvider )
262 {
263 for ( const auto &tile : mTileDataMap )
264 {
265 mLabelProvider->registerTileFeatures( tile, ctx );
266 }
267 }
268
270 mRenderer->renderSelectedFeatures( mSelectedFeatures, ctx );
271
272 mRenderer->stopRender( ctx );
273
274 QgsDebugMsgLevel( QStringLiteral( "Total time for decoding: %1" ).arg( mTotalDecodeTime / 1000. ), 2 );
275 QgsDebugMsgLevel( QStringLiteral( "Drawing time: %1" ).arg( mTotalDrawTime / 1000. ), 2 );
276 QgsDebugMsgLevel( QStringLiteral( "Total time: %1" ).arg( tTotal.elapsed() / 1000. ), 2 );
277
278 return !ctx.renderingStopped();
279}
280
282{
283 switch ( renderContext()->rasterizedRenderingPolicy() )
284 {
287 break;
288
290 return false;
291 }
292
293 if ( !qgsDoubleNear( mLayerOpacity, 1.0 ) )
294 return true;
295
296 if ( mLayerBlendMode != QPainter::CompositionMode_SourceOver )
297 return true;
298
299 return false;
300}
301
302void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
303{
305
306 QgsDebugMsgLevel( QStringLiteral( "Drawing tile " ) + rawTile.id.toString(), 2 );
307
308 QElapsedTimer tLoad;
309 tLoad.start();
310
311 // currently only MVT encoding supported
312 QgsVectorTileMVTDecoder decoder( mTileMatrixSet );
313 if ( !decoder.decode( rawTile ) )
314 {
315 QgsDebugMsgLevel( QStringLiteral( "Failed to parse raw tile data! " ) + rawTile.id.toString(), 2 );
316 return;
317 }
318
319 if ( ctx.renderingStopped() )
320 return;
321
322 const QgsCoordinateTransform ct = ctx.coordinateTransform();
323
324 QgsVectorTileRendererData tile( rawTile.id );
325 tile.setRenderZoomLevel( mTileZoomToRender );
326
327 tile.setFields( mPerLayerFields );
328 tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
329
330 try
331 {
332 tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrixSet.tileMatrix( rawTile.id.zoomLevel() ), ctx.mapToPixel() ) );
333 }
334 catch ( QgsCsException & )
335 {
336 QgsDebugMsgLevel( QStringLiteral( "Failed to generate tile polygon " ) + rawTile.id.toString(), 2 );
337 return;
338 }
339
340 mTotalDecodeTime += tLoad.elapsed();
341
342 // calculate tile polygon in screen coordinates
343
344 if ( ctx.renderingStopped() )
345 return;
346
347 {
348 // set up clipping so that rendering does not go behind tile's extent
349 const QgsScopedQPainterState savePainterState( ctx.painter() );
350 // we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
351 // regions setup outside of the vector tile renderer (e.g. layout map clip region)
352 ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
353
354 QElapsedTimer tDraw;
355 tDraw.start();
356
357 mRenderer->renderTile( tile, ctx );
358 mTotalDrawTime += tDraw.elapsed();
359 }
360
361 // Store tile for later use
362 if ( mLabelProvider )
363 mTileDataMap.insert( tile.id().toString(), tile );
364
365 if ( mDrawTileBoundaries )
366 {
367 const QgsScopedQPainterState savePainterState( ctx.painter() );
368 ctx.painter()->setClipping( false );
369
370 QPen pen( Qt::red );
371 pen.setWidth( 3 );
372 QBrush brush( QColor( 255, 0, 0, 40 ), Qt::BrushStyle::Dense3Pattern );
373
374 ctx.painter()->setPen( pen );
375 ctx.painter()->setBrush( brush );
376 ctx.painter()->drawPolygon( tile.tilePolygon() );
377#if 1
378 QgsTextFormat format;
379 format.setColor( QColor( 255, 0, 0 ) );
380 format.buffer().setEnabled( true );
381
382 QgsTextRenderer::drawText( QRectF( QPoint( 0, 0 ), ctx.outputSize() ).intersected( tile.tilePolygon().boundingRect() ),
383 0, Qgis::TextHorizontalAlignment::Center, { tile.id().toString() },
385#endif
386 }
387}
Provides global constants and enumerations for use throughout the application.
Definition qgis.h:56
@ Default
Allow raster-based rendering in situations where it is required for correct rendering or where it wil...
Definition qgis.h:2704
@ PreferVector
Prefer vector-based rendering, when the result will still be visually near-identical to a raster-base...
Definition qgis.h:2705
@ ForceVector
Always force vector-based rendering, even when the result will be visually different to a raster-base...
Definition qgis.h:2706
@ VectorTile
Vector tile layer. Added in QGIS 3.14.
Definition qgis.h:195
@ DrawSelection
Whether vector selections should be shown in the rendered map.
Definition qgis.h:2753
@ VerticalCenter
Center align.
Definition qgis.h:2963
@ Center
Center align.
Definition qgis.h:2944
@ Reverse
Reverse/inverse transform (from destination to source).
Definition qgis.h:2673
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 truncated to the sp...
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:55
int zoomLevel() const
Returns tile's zoom level (Z).
Definition qgstiles.h:52
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:6607
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:61
#define QgsDebugError(str)
Definition qgslogger.h:57