QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 <QElapsedTimer>
19 
21 #include "qgsfeedback.h"
22 #include "qgslogger.h"
23 
25 #include "qgsvectortilelayer.h"
26 #include "qgsvectortileloader.h"
27 #include "qgsvectortileutils.h"
28 
29 #include "qgslabelingengine.h"
30 #include "qgsvectortilelabeling.h"
31 #include "qgsmapclippingutils.h"
32 
34  : QgsMapLayerRenderer( layer->id(), &context )
35  , mSourceType( layer->sourceType() )
36  , mSourcePath( layer->sourcePath() )
37  , mSourceMinZoom( layer->sourceMinZoom() )
38  , mSourceMaxZoom( layer->sourceMaxZoom() )
39  , mRenderer( layer->renderer()->clone() )
40  , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
41  , mFeedback( new QgsFeedback )
42  , mLayerOpacity( layer->opacity() )
43 {
44 
45  QgsDataSourceUri dsUri;
46  dsUri.setEncodedUri( layer->source() );
47  mAuthCfg = dsUri.authConfigId();
48  mReferer = dsUri.param( QStringLiteral( "referer" ) );
49 
50  if ( QgsLabelingEngine *engine = context.labelingEngine() )
51  {
52  if ( layer->labeling() )
53  {
54  mLabelProvider = layer->labeling()->provider( layer );
55  if ( mLabelProvider )
56  {
57  engine->addProvider( mLabelProvider );
58  }
59  }
60  }
61 
63 }
64 
66 {
68 
69  if ( ctx.renderingStopped() )
70  return false;
71 
72  QgsScopedQPainterState painterState( ctx.painter() );
73 
74  if ( !mClippingRegions.empty() )
75  {
76  bool needsPainterClipPath = false;
77  const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), QgsMapLayerType::VectorTileLayer, needsPainterClipPath );
78  if ( needsPainterClipPath )
79  renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
80  }
81 
82  QElapsedTimer tTotal;
83  tTotal.start();
84 
85  QgsDebugMsgLevel( QStringLiteral( "Vector tiles rendering extent: " ) + ctx.extent().toString( -1 ), 2 );
86  QgsDebugMsgLevel( QStringLiteral( "Vector tiles map scale 1 : %1" ).arg( ctx.rendererScale() ), 2 );
87 
88  mTileZoom = QgsVectorTileUtils::scaleToZoomLevel( ctx.rendererScale(), mSourceMinZoom, mSourceMaxZoom );
89  QgsDebugMsgLevel( QStringLiteral( "Vector tiles zoom level: %1" ).arg( mTileZoom ), 2 );
90 
91  mTileMatrix = QgsTileMatrix::fromWebMercator( mTileZoom );
92 
93  mTileRange = mTileMatrix.tileRangeFromExtent( ctx.extent() );
94  QgsDebugMsgLevel( QStringLiteral( "Vector tiles range X: %1 - %2 Y: %3 - %4" )
95  .arg( mTileRange.startColumn() ).arg( mTileRange.endColumn() )
96  .arg( mTileRange.startRow() ).arg( mTileRange.endRow() ), 2 );
97 
98  // view center is used to sort the order of tiles for fetching and rendering
99  QPointF viewCenter = mTileMatrix.mapToTileCoordinates( ctx.extent().center() );
100 
101  if ( !mTileRange.isValid() )
102  {
103  QgsDebugMsgLevel( QStringLiteral( "Vector tiles - outside of range" ), 2 );
104  return true; // nothing to do
105  }
106 
107  bool isAsync = ( mSourceType == QLatin1String( "xyz" ) );
108 
109  std::unique_ptr<QgsVectorTileLoader> asyncLoader;
110  QList<QgsVectorTileRawData> rawTiles;
111  if ( !isAsync )
112  {
113  QElapsedTimer tFetch;
114  tFetch.start();
115  rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, mTileMatrix, viewCenter, mTileRange, mAuthCfg, mReferer );
116  QgsDebugMsgLevel( QStringLiteral( "Tile fetching time: %1" ).arg( tFetch.elapsed() / 1000. ), 2 );
117  QgsDebugMsgLevel( QStringLiteral( "Fetched tiles: %1" ).arg( rawTiles.count() ), 2 );
118  }
119  else
120  {
121  asyncLoader.reset( new QgsVectorTileLoader( mSourcePath, mTileMatrix, mTileRange, viewCenter, mAuthCfg, mReferer, mFeedback.get() ) );
122  QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, asyncLoader.get(), [this]( const QgsVectorTileRawData & rawTile )
123  {
124  QgsDebugMsgLevel( QStringLiteral( "Got tile asynchronously: " ) + rawTile.id.toString(), 2 );
125  if ( !rawTile.data.isEmpty() )
126  decodeAndDrawTile( rawTile );
127  } );
128  }
129 
130  if ( ctx.renderingStopped() )
131  return false;
132 
133  // add @zoom_level variable which can be used in styling
134  QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
135  scope->setVariable( QStringLiteral( "zoom_level" ), mTileZoom, true );
136  scope->setVariable( QStringLiteral( "vector_tile_zoom" ), QgsVectorTileUtils::scaleToZoom( ctx.rendererScale() ), true );
137  QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
138 
139  mRenderer->startRender( *renderContext(), mTileZoom, mTileRange );
140 
141  QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( ctx );
142 
143  if ( mLabelProvider )
144  {
145  const QMap<QString, QSet<QString> > requiredFieldsLabeling = mLabelProvider->usedAttributes( ctx, mTileZoom );
146  for ( auto it = requiredFieldsLabeling.begin(); it != requiredFieldsLabeling.end(); ++it )
147  {
148  requiredFields[it.key()].unite( it.value() );
149  }
150  }
151 
152  for ( auto it = requiredFields.constBegin(); it != requiredFields.constEnd(); ++it )
153  mPerLayerFields[it.key()] = QgsVectorTileUtils::makeQgisFields( it.value() );
154 
155  mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoom );
156 
157  if ( mLabelProvider )
158  {
159  mLabelProvider->setFields( mPerLayerFields );
160  QSet<QString> attributeNames; // we don't need this - already got referenced columns in provider constructor
161  if ( !mLabelProvider->prepare( ctx, attributeNames ) )
162  {
163  ctx.labelingEngine()->removeProvider( mLabelProvider );
164  mLabelProvider = nullptr; // provider is deleted by the engine
165  }
166 
167  mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoom ) );
168  }
169 
170  if ( !isAsync )
171  {
172  for ( QgsVectorTileRawData &rawTile : rawTiles )
173  {
174  if ( ctx.renderingStopped() )
175  break;
176 
177  decodeAndDrawTile( rawTile );
178  }
179  }
180  else
181  {
182  // Block until tiles are fetched and rendered. If the rendering gets canceled at some point,
183  // the async loader will catch the signal, abort requests and return from downloadBlocking()
184  asyncLoader->downloadBlocking();
185  }
186 
187  mRenderer->stopRender( ctx );
188 
189  QgsDebugMsgLevel( QStringLiteral( "Total time for decoding: %1" ).arg( mTotalDecodeTime / 1000. ), 2 );
190  QgsDebugMsgLevel( QStringLiteral( "Drawing time: %1" ).arg( mTotalDrawTime / 1000. ), 2 );
191  QgsDebugMsgLevel( QStringLiteral( "Total time: %1" ).arg( tTotal.elapsed() / 1000. ), 2 );
192 
193  return !ctx.renderingStopped();
194 }
195 
197 {
198  return renderContext()->testFlag( QgsRenderContext::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) );
199 }
200 
201 void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
202 {
204 
205  QgsDebugMsgLevel( QStringLiteral( "Drawing tile " ) + rawTile.id.toString(), 2 );
206 
207  QElapsedTimer tLoad;
208  tLoad.start();
209 
210  // currently only MVT encoding supported
211  QgsVectorTileMVTDecoder decoder;
212  if ( !decoder.decode( rawTile.id, rawTile.data ) )
213  {
214  QgsDebugMsgLevel( QStringLiteral( "Failed to parse raw tile data! " ) + rawTile.id.toString(), 2 );
215  return;
216  }
217 
218  if ( ctx.renderingStopped() )
219  return;
220 
222 
223  QgsVectorTileRendererData tile( rawTile.id );
224  tile.setFields( mPerLayerFields );
225  tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
226 
227  try
228  {
229  tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrix, ctx.mapToPixel() ) );
230  }
231  catch ( QgsCsException & )
232  {
233  QgsDebugMsgLevel( QStringLiteral( "Failed to generate tile polygon " ) + rawTile.id.toString(), 2 );
234  return;
235  }
236 
237  mTotalDecodeTime += tLoad.elapsed();
238 
239  // calculate tile polygon in screen coordinates
240 
241  if ( ctx.renderingStopped() )
242  return;
243 
244  // set up clipping so that rendering does not go behind tile's extent
245  QgsScopedQPainterState savePainterState( ctx.painter() );
246  // we have to intersect with any existing painter clip regions, or we risk overwriting valid clip
247  // regions setup outside of the vector tile renderer (e.g. layout map clip region)
248  ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
249 
250  QElapsedTimer tDraw;
251  tDraw.start();
252 
253  mRenderer->renderTile( tile, ctx );
254  mTotalDrawTime += tDraw.elapsed();
255 
256  if ( mLabelProvider )
257  mLabelProvider->registerTileFeatures( tile, ctx );
258 
259  if ( mDrawTileBoundaries )
260  {
261  QgsScopedQPainterState savePainterState( ctx.painter() );
262  ctx.painter()->setClipping( false );
263 
264  QPen pen( Qt::red );
265  pen.setWidth( 3 );
266  ctx.painter()->setPen( pen );
267  ctx.painter()->drawPolygon( tile.tilePolygon() );
268  }
269 }
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
Class for storing the component parts of a RDBMS data source URI (e.g.
void setEncodedUri(const QByteArray &uri)
Sets the complete encoded uri.
QString param(const QString &key) const
Returns a generic parameter value corresponding to the specified key.
QString authConfigId() const
Returns any associated authentication configuration ID stored in the URI.
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 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.
static QPainterPath calculatePainterClipRegion(const QList< QgsMapClippingRegion > &regions, const QgsRenderContext &context, QgsMapLayerType layerType, bool &shouldClip)
Returns a QPainterPath representing the intersection of clipping regions from context which should be...
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 source() const
Returns the source for the layer.
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.
double rendererScale() const
Returns the renderer map scale.
QgsExpressionContext & expressionContext()
Gets the expression context.
const QgsMapToPixel & mapToPixel() const
Returns the context's map to pixel transform, which transforms between map coordinates and device coo...
QgsLabelingEngine * labelingEngine() const
Gets access to new labeling engine (may be nullptr)
@ UseAdvancedEffects
Enable layer opacity and blending effects.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
bool testFlag(Flag flag) const
Check whether a particular flag is enabled.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context.
const QgsRectangle & extent() const
When rendering a map layer, calling this method returns the "clipping" extent for the layer (in the l...
Scoped object for saving and restoring a QPainter object's state.
QPointF mapToTileCoordinates(const QgsPointXY &mapPoint) const
Returns row/column coordinates (floating point number) from the given point in map coordinates.
Definition: qgstiles.cpp:78
QgsTileRange tileRangeFromExtent(const QgsRectangle &mExtent)
Returns tile range that fully covers the given extent.
Definition: qgstiles.cpp:54
static QgsTileMatrix fromWebMercator(int mZoomLevel)
Returns a tile matrix for the usual web mercator.
Definition: qgstiles.cpp:20
int endColumn() const
Returns index of the last column in the range.
Definition: qgstiles.h:78
int endRow() const
Returns index of the last row in the range.
Definition: qgstiles.h:82
int startRow() const
Returns index of the first row in the range.
Definition: qgstiles.h:80
int startColumn() const
Returns index of the first column in the range.
Definition: qgstiles.h:76
bool isValid() const
Returns whether the range is valid (when all row/column numbers are not negative)
Definition: qgstiles.h:73
QString toString() const
Returns tile coordinates in a formatted string.
Definition: qgstiles.h:49
virtual bool prepare(QgsRenderContext &context, QSet< QString > &attributeNames)
Prepare for registration of features.
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 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 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(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 QString &sourceType, const QString &sourcePath, const QgsTileMatrix &tileMatrix, const QPointF &viewCenter, const QgsTileRange &range, const QString &authid, const QString &referer)
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.
QgsVectorTileFeatures layerFeatures(const QMap< QString, QgsFields > &perLayerFields, const QgsCoordinateTransform &ct, const QSet< QString > *layerSubset=nullptr) const
Returns decoded features grouped by sub-layers.
bool decode(QgsTileXYZ tileID, const QByteArray &rawTileData)
Tries to decode raw tile data, returns true on success.
Keeps track of raw tile data that need to be decoded.
QByteArray data
Raw tile data.
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 int scaleToZoomLevel(double mapScale, int sourceMinZoom, int sourceMaxZoom)
Finds best fitting zoom level (assuming GoogleCRS84Quad tile matrix set) given map scale denominator ...
static double scaleToZoom(double mapScale)
Finds zoom level (assuming GoogleCRS84Quad tile matrix set) given map scale denominator.
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(QSet< QString > flds)
Returns QgsFields instance based on the set of field names.
@ VectorTileLayer
Added in 3.14.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39