Branch data Line data Source code
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 : :
16 : : #include "qgsvectortilelayerrenderer.h"
17 : :
18 : : #include <QElapsedTimer>
19 : :
20 : : #include "qgsexpressioncontextutils.h"
21 : : #include "qgsfeedback.h"
22 : : #include "qgslogger.h"
23 : :
24 : : #include "qgsvectortilemvtdecoder.h"
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 : :
33 : 0 : QgsVectorTileLayerRenderer::QgsVectorTileLayerRenderer( QgsVectorTileLayer *layer, QgsRenderContext &context )
34 : 0 : : QgsMapLayerRenderer( layer->id(), &context )
35 : 0 : , mSourceType( layer->sourceType() )
36 : 0 : , mSourcePath( layer->sourcePath() )
37 : 0 : , mSourceMinZoom( layer->sourceMinZoom() )
38 : 0 : , mSourceMaxZoom( layer->sourceMaxZoom() )
39 : 0 : , mRenderer( layer->renderer()->clone() )
40 : 0 : , mDrawTileBoundaries( layer->isTileBorderRenderingEnabled() )
41 : 0 : , mFeedback( new QgsFeedback )
42 : 0 : , mLayerOpacity( layer->opacity() )
43 : 0 : {
44 : :
45 : 0 : QgsDataSourceUri dsUri;
46 : 0 : dsUri.setEncodedUri( layer->source() );
47 : 0 : mAuthCfg = dsUri.authConfigId();
48 : 0 : mReferer = dsUri.param( QStringLiteral( "referer" ) );
49 : :
50 : 0 : if ( QgsLabelingEngine *engine = context.labelingEngine() )
51 : : {
52 : 0 : if ( layer->labeling() )
53 : : {
54 : 0 : mLabelProvider = layer->labeling()->provider( layer );
55 : 0 : if ( mLabelProvider )
56 : : {
57 : 0 : engine->addProvider( mLabelProvider );
58 : 0 : }
59 : 0 : }
60 : 0 : }
61 : :
62 : 0 : mClippingRegions = QgsMapClippingUtils::collectClippingRegionsForLayer( *renderContext(), layer );
63 : 0 : }
64 : :
65 : 0 : bool QgsVectorTileLayerRenderer::render()
66 : : {
67 : 0 : QgsRenderContext &ctx = *renderContext();
68 : :
69 : 0 : if ( ctx.renderingStopped() )
70 : 0 : return false;
71 : :
72 : 0 : QgsScopedQPainterState painterState( ctx.painter() );
73 : :
74 : 0 : if ( !mClippingRegions.empty() )
75 : 0 : {
76 : 0 : bool needsPainterClipPath = false;
77 : 0 : const QPainterPath path = QgsMapClippingUtils::calculatePainterClipRegion( mClippingRegions, *renderContext(), QgsMapLayerType::VectorTileLayer, needsPainterClipPath );
78 : 0 : if ( needsPainterClipPath )
79 : 0 : renderContext()->painter()->setClipPath( path, Qt::IntersectClip );
80 : 0 : }
81 : :
82 : 0 : QElapsedTimer tTotal;
83 : 0 : tTotal.start();
84 : :
85 : 0 : QgsDebugMsgLevel( QStringLiteral( "Vector tiles rendering extent: " ) + ctx.extent().toString( -1 ), 2 );
86 : 0 : QgsDebugMsgLevel( QStringLiteral( "Vector tiles map scale 1 : %1" ).arg( ctx.rendererScale() ), 2 );
87 : :
88 : 0 : mTileZoom = QgsVectorTileUtils::scaleToZoomLevel( ctx.rendererScale(), mSourceMinZoom, mSourceMaxZoom );
89 : 0 : QgsDebugMsgLevel( QStringLiteral( "Vector tiles zoom level: %1" ).arg( mTileZoom ), 2 );
90 : :
91 : 0 : mTileMatrix = QgsTileMatrix::fromWebMercator( mTileZoom );
92 : :
93 : 0 : mTileRange = mTileMatrix.tileRangeFromExtent( ctx.extent() );
94 : 0 : 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 : 0 :
98 : : // view center is used to sort the order of tiles for fetching and rendering
99 : 0 : QPointF viewCenter = mTileMatrix.mapToTileCoordinates( ctx.extent().center() );
100 : :
101 : 0 : if ( !mTileRange.isValid() )
102 : : {
103 : 0 : QgsDebugMsgLevel( QStringLiteral( "Vector tiles - outside of range" ), 2 );
104 : 0 : return true; // nothing to do
105 : : }
106 : :
107 : 0 : bool isAsync = ( mSourceType == QLatin1String( "xyz" ) );
108 : :
109 : 0 : std::unique_ptr<QgsVectorTileLoader> asyncLoader;
110 : 0 : QList<QgsVectorTileRawData> rawTiles;
111 : 0 : if ( !isAsync )
112 : : {
113 : 0 : QElapsedTimer tFetch;
114 : 0 : tFetch.start();
115 : 0 : rawTiles = QgsVectorTileLoader::blockingFetchTileRawData( mSourceType, mSourcePath, mTileMatrix, viewCenter, mTileRange, mAuthCfg, mReferer );
116 : 0 : QgsDebugMsgLevel( QStringLiteral( "Tile fetching time: %1" ).arg( tFetch.elapsed() / 1000. ), 2 );
117 : 0 : QgsDebugMsgLevel( QStringLiteral( "Fetched tiles: %1" ).arg( rawTiles.count() ), 2 );
118 : 0 : }
119 : : else
120 : : {
121 : 0 : asyncLoader.reset( new QgsVectorTileLoader( mSourcePath, mTileMatrix, mTileRange, viewCenter, mAuthCfg, mReferer, mFeedback.get() ) );
122 : 0 : QObject::connect( asyncLoader.get(), &QgsVectorTileLoader::tileRequestFinished, asyncLoader.get(), [this]( const QgsVectorTileRawData & rawTile )
123 : : {
124 : 0 : QgsDebugMsgLevel( QStringLiteral( "Got tile asynchronously: " ) + rawTile.id.toString(), 2 );
125 : 0 : if ( !rawTile.data.isEmpty() )
126 : 0 : decodeAndDrawTile( rawTile );
127 : 0 : } );
128 : : }
129 : :
130 : 0 : if ( ctx.renderingStopped() )
131 : 0 : return false;
132 : :
133 : : // add @zoom_level variable which can be used in styling
134 : 0 : QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Tiles" ) ); // will be deleted by popper
135 : 0 : scope->setVariable( QStringLiteral( "zoom_level" ), mTileZoom, true );
136 : 0 : scope->setVariable( QStringLiteral( "vector_tile_zoom" ), QgsVectorTileUtils::scaleToZoom( ctx.rendererScale() ), true );
137 : 0 : QgsExpressionContextScopePopper popper( ctx.expressionContext(), scope );
138 : :
139 : 0 : mRenderer->startRender( *renderContext(), mTileZoom, mTileRange );
140 : :
141 : 0 : QMap<QString, QSet<QString> > requiredFields = mRenderer->usedAttributes( ctx );
142 : :
143 : 0 : if ( mLabelProvider )
144 : : {
145 : 0 : const QMap<QString, QSet<QString> > requiredFieldsLabeling = mLabelProvider->usedAttributes( ctx, mTileZoom );
146 : 0 : for ( auto it = requiredFieldsLabeling.begin(); it != requiredFieldsLabeling.end(); ++it )
147 : : {
148 : 0 : requiredFields[it.key()].unite( it.value() );
149 : 0 : }
150 : 0 : }
151 : :
152 : 0 : for ( auto it = requiredFields.constBegin(); it != requiredFields.constEnd(); ++it )
153 : 0 : mPerLayerFields[it.key()] = QgsVectorTileUtils::makeQgisFields( it.value() );
154 : :
155 : 0 : mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoom );
156 : :
157 : 0 : if ( mLabelProvider )
158 : : {
159 : 0 : mLabelProvider->setFields( mPerLayerFields );
160 : 0 : QSet<QString> attributeNames; // we don't need this - already got referenced columns in provider constructor
161 : 0 : if ( !mLabelProvider->prepare( ctx, attributeNames ) )
162 : : {
163 : 0 : ctx.labelingEngine()->removeProvider( mLabelProvider );
164 : 0 : mLabelProvider = nullptr; // provider is deleted by the engine
165 : 0 : }
166 : :
167 : 0 : mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoom ) );
168 : 0 : }
169 : :
170 : 0 : if ( !isAsync )
171 : : {
172 : 0 : for ( QgsVectorTileRawData &rawTile : rawTiles )
173 : : {
174 : 0 : if ( ctx.renderingStopped() )
175 : 0 : break;
176 : :
177 : 0 : decodeAndDrawTile( rawTile );
178 : : }
179 : 0 : }
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 : 0 : asyncLoader->downloadBlocking();
185 : : }
186 : :
187 : 0 : mRenderer->stopRender( ctx );
188 : :
189 : 0 : QgsDebugMsgLevel( QStringLiteral( "Total time for decoding: %1" ).arg( mTotalDecodeTime / 1000. ), 2 );
190 : 0 : QgsDebugMsgLevel( QStringLiteral( "Drawing time: %1" ).arg( mTotalDrawTime / 1000. ), 2 );
191 : 0 : QgsDebugMsgLevel( QStringLiteral( "Total time: %1" ).arg( tTotal.elapsed() / 1000. ), 2 );
192 : :
193 : 0 : return !ctx.renderingStopped();
194 : 0 : }
195 : :
196 : 0 : bool QgsVectorTileLayerRenderer::forceRasterRender() const
197 : : {
198 : 0 : return renderContext()->testFlag( QgsRenderContext::UseAdvancedEffects ) && ( !qgsDoubleNear( mLayerOpacity, 1.0 ) );
199 : : }
200 : :
201 : 0 : void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &rawTile )
202 : : {
203 : 0 : QgsRenderContext &ctx = *renderContext();
204 : :
205 : 0 : QgsDebugMsgLevel( QStringLiteral( "Drawing tile " ) + rawTile.id.toString(), 2 );
206 : :
207 : 0 : QElapsedTimer tLoad;
208 : 0 : tLoad.start();
209 : :
210 : : // currently only MVT encoding supported
211 : 0 : QgsVectorTileMVTDecoder decoder;
212 : 0 : if ( !decoder.decode( rawTile.id, rawTile.data ) )
213 : : {
214 : 0 : QgsDebugMsgLevel( QStringLiteral( "Failed to parse raw tile data! " ) + rawTile.id.toString(), 2 );
215 : 0 : return;
216 : : }
217 : :
218 : 0 : if ( ctx.renderingStopped() )
219 : 0 : return;
220 : :
221 : 0 : QgsCoordinateTransform ct = ctx.coordinateTransform();
222 : :
223 : 0 : QgsVectorTileRendererData tile( rawTile.id );
224 : 0 : tile.setFields( mPerLayerFields );
225 : 0 : tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
226 : :
227 : : try
228 : : {
229 : 0 : tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrix, ctx.mapToPixel() ) );
230 : 0 : }
231 : : catch ( QgsCsException & )
232 : : {
233 : 0 : QgsDebugMsgLevel( QStringLiteral( "Failed to generate tile polygon " ) + rawTile.id.toString(), 2 );
234 : : return;
235 : 0 : }
236 : :
237 : 0 : mTotalDecodeTime += tLoad.elapsed();
238 : :
239 : : // calculate tile polygon in screen coordinates
240 : :
241 : 0 : if ( ctx.renderingStopped() )
242 : 0 : return;
243 : :
244 : : // set up clipping so that rendering does not go behind tile's extent
245 : 0 : 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 : 0 : ctx.painter()->setClipRegion( QRegion( tile.tilePolygon() ), Qt::IntersectClip );
249 : :
250 : 0 : QElapsedTimer tDraw;
251 : 0 : tDraw.start();
252 : :
253 : 0 : mRenderer->renderTile( tile, ctx );
254 : 0 : mTotalDrawTime += tDraw.elapsed();
255 : :
256 : 0 : if ( mLabelProvider )
257 : 0 : mLabelProvider->registerTileFeatures( tile, ctx );
258 : :
259 : 0 : if ( mDrawTileBoundaries )
260 : : {
261 : 0 : QgsScopedQPainterState savePainterState( ctx.painter() );
262 : 0 : ctx.painter()->setClipping( false );
263 : :
264 : 0 : QPen pen( Qt::red );
265 : 0 : pen.setWidth( 3 );
266 : 0 : ctx.painter()->setPen( pen );
267 : 0 : ctx.painter()->drawPolygon( tile.tilePolygon() );
268 : 0 : }
269 : 0 : }
|