Skip to content

Commit

Permalink
[vectortiles] Optimise rendering of complex vector tiles
Browse files Browse the repository at this point in the history
Instead of ALWAYS converting all features in a tile to QGIS representations,
now we intelligently skip over any layers which aren't required for
rendering or labeling (e.g. because the current renderer/labeling
configuration is disabling these layers or doesn't have a rule for
them).

This improves rendering speed with sources like the OS ZoomStack tiles,
which have a LOT of detail even at small map scales (e.g. building
data is present in very zoomed out tiles!!).
  • Loading branch information
nyalldawson committed Sep 14, 2020
1 parent c116080 commit 789eccb
Show file tree
Hide file tree
Showing 12 changed files with 89 additions and 5 deletions.
Expand Up @@ -157,6 +157,8 @@ Constructs renderer with no styles

virtual void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange );

virtual QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const;

virtual void stopRender( QgsRenderContext &context );

virtual void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context );
Expand Down
12 changes: 12 additions & 0 deletions python/core/auto_generated/vectortile/qgsvectortilerenderer.sip.in
Expand Up @@ -109,6 +109,18 @@ Initializes rendering. It should be paired with a :py:func:`~QgsVectorTileRender
%End



virtual QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const;
%Docstring
Returns a list of the layers required for rendering.

Only layers which are visible at the specified ``tileZoom`` should be included in this list.

An empty string present in the list indicates that all layer in the tiles are required.

.. versionadded:: 3.16
%End

virtual void stopRender( QgsRenderContext &context ) = 0;
%Docstring
Finishes rendering and cleans up any resources
Expand Down
13 changes: 13 additions & 0 deletions src/core/vectortile/qgsvectortilebasiclabeling.cpp
Expand Up @@ -143,6 +143,19 @@ QMap<QString, QSet<QString> > QgsVectorTileBasicLabelProvider::usedAttributes( c
return requiredFields;
}

QSet<QString> QgsVectorTileBasicLabelProvider::requiredLayers( QgsRenderContext &, int tileZoom ) const
{
QSet< QString > res;
for ( const QgsVectorTileBasicLabelingStyle &layerStyle : qgis::as_const( mStyles ) )
{
if ( layerStyle.isActive( tileZoom ) )
{
res.insert( layerStyle.layerName() );
}
}
return res;
}

void QgsVectorTileBasicLabelProvider::setFields( const QMap<QString, QgsFields> &perLayerFields )
{
mPerLayerFields = perLayerFields;
Expand Down
1 change: 1 addition & 0 deletions src/core/vectortile/qgsvectortilebasiclabeling.h
Expand Up @@ -150,6 +150,7 @@ class QgsVectorTileBasicLabelProvider : public QgsVectorTileLabelProvider
// virtual functions from QgsVectorTileLabelProvider
void registerTileFeatures( const QgsVectorTileRendererData &tile, QgsRenderContext &context ) override;
QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext &context, int tileZoom ) const override;
QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const override;
void setFields( const QMap<QString, QgsFields> &perLayerFields ) override;

private:
Expand Down
13 changes: 13 additions & 0 deletions src/core/vectortile/qgsvectortilebasicrenderer.cpp
Expand Up @@ -142,6 +142,19 @@ QMap<QString, QSet<QString> > QgsVectorTileBasicRenderer::usedAttributes( const
return mRequiredFields;
}

QSet<QString> QgsVectorTileBasicRenderer::requiredLayers( QgsRenderContext &, int tileZoom ) const
{
QSet< QString > res;
for ( const QgsVectorTileBasicRendererStyle &layerStyle : qgis::as_const( mStyles ) )
{
if ( layerStyle.isActive( tileZoom ) )
{
res.insert( layerStyle.layerName() );
}
}
return res;
}

void QgsVectorTileBasicRenderer::stopRender( QgsRenderContext &context )
{
Q_UNUSED( context )
Expand Down
1 change: 1 addition & 0 deletions src/core/vectortile/qgsvectortilebasicrenderer.h
Expand Up @@ -134,6 +134,7 @@ class CORE_EXPORT QgsVectorTileBasicRenderer : public QgsVectorTileRenderer
QgsVectorTileBasicRenderer *clone() const override SIP_FACTORY;
void startRender( QgsRenderContext &context, int tileZoom, const QgsTileRange &tileRange ) override;
QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext & ) override SIP_SKIP;
QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const override;
void stopRender( QgsRenderContext &context ) override;
void renderTile( const QgsVectorTileRendererData &tile, QgsRenderContext &context ) override;
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
Expand Down
13 changes: 13 additions & 0 deletions src/core/vectortile/qgsvectortilelabeling.h
Expand Up @@ -39,6 +39,19 @@ class QgsVectorTileLabelProvider : public QgsVectorLayerLabelProvider
//! Returns field names for each sub-layer that are required for labeling
virtual QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext &context, int tileZoom ) const = 0;

//TODO QGIS 4.0 -- make pure virtual

/**
* Returns a list of the layers required for labeling.
*
* Only layers which are labeled at the specified \a tileZoom should be included in this list.
*
* An empty string present in the list indicates that all layer in the tiles are required.
*
* \since QGIS 3.16
*/
virtual QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const { Q_UNUSED( context ); Q_UNUSED( tileZoom ); return QSet< QString >() << QString(); }

//! Sets fields for each sub-layer
virtual void setFields( const QMap<QString, QgsFields> &perLayerFields ) = 0;

Expand Down
6 changes: 5 additions & 1 deletion src/core/vectortile/qgsvectortilelayerrenderer.cpp
Expand Up @@ -151,6 +151,8 @@ bool QgsVectorTileLayerRenderer::render()
for ( QString layerName : requiredFields.keys() )
mPerLayerFields[layerName] = QgsVectorTileUtils::makeQgisFields( requiredFields[layerName] );

mRequiredLayers = mRenderer->requiredLayers( ctx, mTileZoom );

if ( mLabelProvider )
{
mLabelProvider->setFields( mPerLayerFields );
Expand All @@ -160,6 +162,8 @@ bool QgsVectorTileLayerRenderer::render()
ctx.labelingEngine()->removeProvider( mLabelProvider );
mLabelProvider = nullptr; // provider is deleted by the engine
}

mRequiredLayers.unite( mLabelProvider->requiredLayers( ctx, mTileZoom ) );
}

if ( !isAsync )
Expand Down Expand Up @@ -212,7 +216,7 @@ void QgsVectorTileLayerRenderer::decodeAndDrawTile( const QgsVectorTileRawData &

QgsVectorTileRendererData tile( rawTile.id );
tile.setFields( mPerLayerFields );
tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct ) );
tile.setFeatures( decoder.layerFeatures( mPerLayerFields, ct, &mRequiredLayers ) );
tile.setTilePolygon( QgsVectorTileUtils::tilePolygon( rawTile.id, ct, mTileMatrix, ctx.mapToPixel() ) );

mTotalDecodeTime += tLoad.elapsed();
Expand Down
4 changes: 4 additions & 0 deletions src/core/vectortile/qgsvectortilelayerrenderer.h
Expand Up @@ -88,6 +88,10 @@ class QgsVectorTileLayerRenderer : public QgsMapLayerRenderer
QgsTileRange mTileRange;
//! Cached QgsFields object for each sub-layer that will be rendered
QMap<QString, QgsFields> mPerLayerFields;

//! Cached list of layers required for renderer and labeling
QSet< QString > mRequiredLayers;

//! Counter of total elapsed time to decode tiles (ms)
int mTotalDecodeTime = 0;
//! Counter of total elapsed time to render tiles (ms)
Expand Down
7 changes: 5 additions & 2 deletions src/core/vectortile/qgsvectortilemvtdecoder.cpp
Expand Up @@ -77,7 +77,7 @@ QStringList QgsVectorTileMVTDecoder::layerFieldNames( const QString &layerName )
return fieldNames;
}

QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct ) const
QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct, const QSet<QString> *layerSubset ) const
{
QgsVectorTileFeatures features;

Expand All @@ -93,7 +93,10 @@ QgsVectorTileFeatures QgsVectorTileMVTDecoder::layerFeatures( const QMap<QString
{
const ::vector_tile::Tile_Layer &layer = tile.layers( layerNum );

QString layerName = layer.name().c_str();
const QString layerName = layer.name().c_str();
if ( layerSubset && !layerSubset->contains( QString() ) && !layerSubset->contains( layerName ) )
continue;

QVector<QgsFeature> layerFeatures;
QgsFields layerFields = perLayerFields[layerName];

Expand Down
9 changes: 7 additions & 2 deletions src/core/vectortile/qgsvectortilemvtdecoder.h
Expand Up @@ -48,8 +48,13 @@ class CORE_EXPORT QgsVectorTileMVTDecoder
//! Returns a list of all field names in a tile. It can only be called after a successful decode()
QStringList layerFieldNames( const QString &layerName ) const;

//! Returns decoded features grouped by sub-layers. It can only be called after a successful decode()
QgsVectorTileFeatures layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct ) const;
/**
* Returns decoded features grouped by sub-layers. It can only be called after a successful decode()
*
* If \a layerSubset is specified then only features from the specified layers will be returned.
*/
QgsVectorTileFeatures layerFeatures( const QMap<QString, QgsFields> &perLayerFields, const QgsCoordinateTransform &ct,
const QSet< QString > *layerSubset = nullptr ) const;

private:
vector_tile::Tile tile;
Expand Down
13 changes: 13 additions & 0 deletions src/core/vectortile/qgsvectortilerenderer.h
Expand Up @@ -115,6 +115,19 @@ class CORE_EXPORT QgsVectorTileRenderer
//! Returns field names of sub-layers that will be used for rendering. Must be called between startRender/stopRender.
virtual QMap<QString, QSet<QString> > usedAttributes( const QgsRenderContext & ) SIP_SKIP { return QMap<QString, QSet<QString> >(); }

//TODO QGIS 4.0 -- make pure virtual

/**
* Returns a list of the layers required for rendering.
*
* Only layers which are visible at the specified \a tileZoom should be included in this list.
*
* An empty string present in the list indicates that all layer in the tiles are required.
*
* \since QGIS 3.16
*/
virtual QSet< QString > requiredLayers( QgsRenderContext &context, int tileZoom ) const { Q_UNUSED( context ); Q_UNUSED( tileZoom ); return QSet< QString >() << QString(); }

//! Finishes rendering and cleans up any resources
virtual void stopRender( QgsRenderContext &context ) = 0;

Expand Down

0 comments on commit 789eccb

Please sign in to comment.