QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
•All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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
18#include "qgsfeedback.h"
19#include "qgslogger.h"
21#include "qgsvectortilelayer.h"
22#include "qgsvectortileloader.h"
23#include "qgsvectortileutils.h"
24#include "qgslabelingengine.h"
26#include "qgsmapclippingutils.h"
27#include "qgsrendercontext.h"
29#include "qgstextrenderer.h"
30
31#include <QElapsedTimer>
32#include <QThread>
33
35 : QgsMapLayerRenderer( layer->id(), &context )
36 , mDataProvider( qgis::down_cast< const QgsVectorTileDataProvider* >( layer->dataProvider() )->clone() )
37 , mRenderer( layer->renderer()->clone() )
38 , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
39 , mFeedback( new QgsFeedback )
40 , mSelectedFeatures( layer->selectedFeatures() )
41 , mLayerOpacity( layer->opacity() )
42 , mTileMatrixSet( layer->tileMatrixSet() )
43{
44 if ( QgsLabelingEngine *engine = context.labelingEngine() )
45 {
46 if ( layer->labeling() )
47 {
48 mLabelProvider = layer->labeling()->provider( layer );
49 if ( mLabelProvider )
50 {
51 engine->addProvider( mLabelProvider );
52 }
53 }
54 }
55
57
58 mDataProvider->moveToThread( nullptr );
59}
60
62
64{
66
67 if ( ctx.renderingStopped() )
68 return false;
69
70 mDataProvider->moveToThread( QThread::currentThread() );
71
72 const QgsScopedQPainterState painterState( ctx.painter() );
73
74 if ( !mClippingRegions.empty() )
75 {
76 bool needsPainterClipPath = false;
77 const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), Qgis::LayerType::VectorTile, needsPainterClipPath );
78 if ( needsPainterClipPath )
79 renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
80 }
81
82 QElapsedTimer tTotal;
83 tTotal.start();
84
85 const double tileRenderScale = mTileMatrixSet.scaleForRenderContext( ctx );
86 QgsDebugMsgLevel( QStringLiteral( "Vector tiles rendering extent: " ) + ctx.extent().toString( -1 ), 2 );
87 QgsDebugMsgLevel( QStringLiteral( "Vector tiles map scale 1 : %1" ).arg( tileRenderScale ), 2 );
88
89 mTileZoomToFetch = mTileMatrixSet.scaleToZoomLevel( tileRenderScale );
90 QgsDebugMsgLevel( QStringLiteral( "Vector tiles zoom level: %1" ).arg( mTileZoomToFetch ), 2 );
91 mTileZoomToRender = mTileMatrixSet.scaleToZoomLevel( tileRenderScale, false );
92 QgsDebugMsgLevel( QStringLiteral( "Render zoom level: %1" ).arg( mTileZoomToRender ), 2 );
93
94 mTileMatrix = mTileMatrixSet.tileMatrix( mTileZoomToFetch );
95
96 mTileRange = mTileMatrix.tileRangeFromExtent( ctx.extent() );
97 QgsDebugMsgLevel( QStringLiteral( "Vector tiles range X: %1 - %2 Y: %3 - %4" )
98 .arg( mTileRange.startColumn() ).arg( mTileRange.endColumn() )
99 .arg( mTileRange.startRow() ).arg( mTileRange.endRow() ), 2 );
100
101 // view center is used to sort the order of tiles for fetching and rendering
102 const QPointF viewCenter = mTileMatrix.mapToTileCoordinates( ctx.extent().center() );
103
104 if ( !mTileRange.isValid() )
105 {
106 QgsDebugMsgLevel( QStringLiteral( "Vector tiles - outside of range" ), 2 );
107 return true; // nothing to do
108 }
109
110 std::unique_ptr<QgsVectorTileLoader> asyncLoader;
111 QList<QgsVectorTileRawData> rawTiles;
112 if ( !mDataProvider->supportsAsync() )
113 {
114 QElapsedTimer tFetch;
115 tFetch.start();
116 rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mDataProvider.get(), mTileMatrixSet, viewCenter, mTileRange, mTileZoomToFetch, mFeedback.get() );
117 QgsDebugMsgLevel( QStringLiteral( "Tile fetching time: %1" ).arg( tFetch.elapsed() / 1000. ), 2 );
118 QgsDebugMsgLevel( QStringLiteral( "Fetched tiles: %1" ).arg( rawTiles.count() ), 2 );
119 }
120 else
121 {
122 asyncLoader.reset( new QgsVectorTileLoader( mDataProvider.get(), mTileMatrixSet, mTileRange, mTileZoomToFetch, viewCenter, mFeedback.get(), renderContext()->rendererUsage() ) );
123 QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, asyncLoader.get(), [this]( const QgsVectorTileRawData & rawTile )
124 {
125 QgsDebugMsgLevel( QStringLiteral( "Got tile asynchronously: " ) + rawTile.id.toString(), 2 );
126 if ( !rawTile.data.isEmpty() )
127 decodeAndDrawTile( rawTile );
128 } );
129 }
130
131 if ( ctx.renderingStopped() )
132 return false;
133
134 // add @zoom_level and @vector_tile_zoom variables which can be used in styling
135 QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
136 scope->setVariable( QStringLiteral( "zoom_level" ), mTileZoomToRender, true );
137 scope->setVariable( QStringLiteral( "vector_tile_zoom" ), mTileMatrixSet.scaleToZoom( tileRenderScale ), true );
138 const QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
139
140 mRenderer->startRender( *renderContext(), mTileZoomToRender, mTileRange );
141
142 // Draw background style if present
143 mRenderer->renderBackground( ctx );
144
145 QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( ctx );
146
147 if ( mLabelProvider )
148 {
149 const QMap<QString, QSet<QString> > requiredFieldsLabeling = mLabelProvider->usedAttributes( ctx, mTileZoomToRender );
150 for ( auto it = requiredFieldsLabeling.begin(); it != requiredFieldsLabeling.end(); ++it )
151 {
152 requiredFields[it.key()].unite( it.value() );
153 }
154 }
155
156 for ( auto it = requiredFields.constBegin(); it != requiredFields.constEnd(); ++it )
157 mPerLayerFields[it.key()] = QgsVectorTileUtils::makeQgisFields( it.value() );
158
159 mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoomToRender );
160
161 if ( mLabelProvider )
162 {
163 mLabelProvider->setFields( mPerLayerFields );
164 QSet<QString> attributeNames; // we don't need this - already got referenced columns in provider constructor
165 if ( !mLabelProvider->prepare( ctx, attributeNames ) )
166 {
167 ctx.labelingEngine()->removeProvider( mLabelProvider );
168 mLabelProvider = nullptr; // provider is deleted by the engine
169 }
170 else
171 {
172 mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoomToRender ) );
173 }
174 }
175
176 if ( !mDataProvider->supportsAsync() )
177 {
178 for ( const QgsVectorTileRawData &rawTile : std::as_const( rawTiles ) )
179 {
180 if ( ctx.renderingStopped() )
181 break;
182
183 decodeAndDrawTile( rawTile );
184 }
185 }
186 else
187 {
188 // Block until tiles are fetched and rendered. If the rendering gets canceled at some point,
189 // the async loader will catch the signal, abort requests and return from downloadBlocking()
190 asyncLoader->downloadBlocking();
191 if ( !asyncLoader->error().isEmpty() )
192 mErrors.append( asyncLoader->error() );
193 }
194
196 mRenderer->renderSelectedFeatures( mSelectedFeatures, ctx );
197
198 mRenderer->stopRender( ctx );
199
200 QgsDebugMsgLevel( QStringLiteral( "Total time for decoding: %1" ).arg( mTotalDecodeTime / 1000. ), 2 );
201 QgsDebugMsgLevel( QStringLiteral( "Drawing time: %1" ).arg( mTotalDrawTime / 1000. ), 2 );
202 QgsDebugMsgLevel( QStringLiteral( "Total time: %1" ).arg( tTotal.elapsed() / 1000. ), 2 );
203
204 return !ctx.renderingStopped();
205}
206
208{
209 return renderContext()->testFlag( Qgis::RenderContextFlag::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) );
210}
211
212void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
213{
215
216 QgsDebugMsgLevel( QStringLiteral( "Drawing tile " ) + rawTile.id.toString(), 2 );
217
218 QElapsedTimer tLoad;
219 tLoad.start();
220
221 // currently only MVT encoding supported
222 QgsVectorTileMVTDecoder decoder( mTileMatrixSet );
223 if ( !decoder.decode( rawTile ) )
224 {
225 QgsDebugMsgLevel( QStringLiteral( "Failed to parse raw tile data! " ) + rawTile.id.toString(), 2 );
226 return;
227 }
228
229 if ( ctx.renderingStopped() )
230 return;
231
233
234 QgsVectorTileRendererData tile( rawTile.id );
235 tile.setRenderZoomLevel( mTileZoomToRender );
236
237 tile.setFields( mPerLayerFields );
238 tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
239
240 try
241 {
242 tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrixSet.tileMatrix( rawTile.id.zoomLevel() ), ctx.mapToPixel() ) );
243 }
244 catch ( QgsCsException & )
245 {
246 QgsDebugMsgLevel( QStringLiteral( "Failed to generate tile polygon " ) + rawTile.id.toString(), 2 );
247 return;
248 }
249
250 mTotalDecodeTime += tLoad.elapsed();
251
252 // calculate tile polygon in screen coordinates
253
254 if ( ctx.renderingStopped() )
255 return;
256
257 {
258 // set up clipping so that rendering does not go behind tile's extent
259 const QgsScopedQPainterState savePainterState( ctx.painter() );
260 // we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
261 // regions setup outside of the vector tile renderer (e.g. layout map clip region)
262 ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
263
264 QElapsedTimer tDraw;
265 tDraw.start();
266
267 mRenderer->renderTile( tile, ctx );
268 mTotalDrawTime += tDraw.elapsed();
269 }
270
271 if ( mLabelProvider )
272 mLabelProvider->registerTileFeatures( tile, ctx );
273
274 if ( mDrawTileBoundaries )
275 {
276 const QgsScopedQPainterState savePainterState( ctx.painter() );
277 ctx.painter()->setClipping( false );
278
279 QPen pen( Qt::red );
280 pen.setWidth( 3 );
281 QBrush brush( QColor( 255, 0, 0, 40 ), Qt::BrushStyle::Dense3Pattern );
282
283 ctx.painter()->setPen( pen );
284 ctx.painter()->setBrush( brush );
285 ctx.painter()->drawPolygon( tile.tilePolygon() );
286#if 1
287 QgsTextFormat format;
288 format.setColor( QColor( 255, 0, 0 ) );
289 format.buffer().setEnabled( true );
290
291 QgsTextRenderer::drawText( QRectF( QPoint( 0, 0 ), ctx.outputSize() ).intersected( tile.tilePolygon().boundingRect() ),
292 0, Qgis::TextHorizontalAlignment::Center, { tile.id().toString() },
293 ctx, format, true, Qgis::TextVerticalAlignment::VerticalCenter );
294#endif
295 }
296}
@ DrawSelection
Whether vector selections should be shown in the rendered map.
@ UseAdvancedEffects
Enable layer opacity and blending effects.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
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:45
The QgsLabelingEngine class 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.
Base class for utility classes that encapsulate information necessary for rendering of map layers.
QgsRenderContext * renderContext()
Returns the render context associated with the renderer.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
QgsPointXY center() const SIP_HOLDGIL
Returns the center point of the rectangle.
Definition: qgsrectangle.h:251
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...
bool testFlag(Qgis::RenderContextFlag flag) const
Check whether a particular flag is enabled.
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.
Scoped object for saving and restoring a QPainter object's state.
void setEnabled(bool enabled)
Sets whether the text buffer will be drawn.
Container for all settings relating to text rendering.
Definition: qgstextformat.h:42
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.
double scaleForRenderContext(const QgsRenderContext &context) const
Calculates the correct scale to use for the tiles when rendered using the specified render context.
Definition: qgstiles.cpp:299
double scaleToZoom(double scale) const
Calculates a fractional zoom level given a map scale denominator.
Definition: qgstiles.cpp:226
QgsTileMatrix tileMatrix(int zoom) const
Returns the tile matrix corresponding to the specified zoom.
Definition: qgstiles.cpp:156
int scaleToZoomLevel(double scale, bool clamp=true) const
Finds the best fitting (integer) zoom level given a map scale denominator.
Definition: qgstiles.cpp:283
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition: qgstiles.cpp:121
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent) const
Returns tile range that fully covers the given extent.
Definition: qgstiles.cpp:97
int endColumn() const
Returns index of the last column in the range.
Definition: qgstiles.h:109
int endRow() const
Returns index of the last row in the range.
Definition: qgstiles.h:113
int startRow() const
Returns index of the first row in the range.
Definition: qgstiles.h:111
int startColumn() const
Returns index of the first column in the range.
Definition: qgstiles.h:107
bool isValid() const
Returns whether the range is valid (when all row/column numbers are not negative)
Definition: qgstiles.h:104
QString toString() const
Returns tile coordinates in a formatted string.
Definition: qgstiles.h:54
int zoomLevel() const
Returns tile's zoom level (Z)
Definition: qgstiles.h:51
virtual bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames)
Prepare for registration of features.
Base class for vector tile layer data providers.
virtual QMap< QString, QSet< QString > > usedAttributes(const QgsRenderContext &context, int tileZoom) const =0
Returns field names for each sub-layer that are required for labeling.
virtual void setFields(const QMap< QString, QgsFields > &perLayerFields)=0
Sets fields for each sub-layer.
virtual QSet< QString > requiredLayers(QgsRenderContext &context, int tileZoom) const
Returns a list of the layers required for labeling.
virtual void registerTileFeatures(const QgsVectorTileRendererData &tile, QgsRenderContext &context)=0
Registers label features for given tile to the labeling engine.
virtual QgsVectorTileLabelProvider * provider(QgsVectorTileLayer *layer) const SIP_SKIP
Factory for label provider implementation.
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.
virtual 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.
QgsVectorTileLabeling * labeling() const
Returns currently assigned labeling.
The loader class takes care of loading raw vector tile data from a tile source.
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...
This class is responsible for decoding raw tile data written with Mapbox Vector Tiles encoding.
Keeps track of raw tile data that need to be decoded.
QgsTileXYZ id
Tile position in tile matrix set.
Contains decoded features of a single vector tile and any other data necessary for rendering of it.
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:3988
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39