1 /***************************************************************************
2   qgsvectortilebasiclabeling.cpp
3   --------------------------------------
4   Date                 : April 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 "qgsvectortilebasiclabeling.h"
17 
18 #include "qgsexpressioncontextutils.h"
19 #include "qgslogger.h"
20 #include "qgsvectortilelayer.h"
21 #include "qgsvectortilerenderer.h"
22 #include "qgsvectortileutils.h"
23 #include "qgsrendercontext.h"
24 
25 
writeXml(QDomElement & elem,const QgsReadWriteContext & context) const26 void QgsVectorTileBasicLabelingStyle::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
27 {
28   elem.setAttribute( QStringLiteral( "name" ), mStyleName );
29   elem.setAttribute( QStringLiteral( "layer" ), mLayerName );
30   elem.setAttribute( QStringLiteral( "geometry" ), mGeometryType );
31   elem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
32   elem.setAttribute( QStringLiteral( "expression" ), mExpression );
33   elem.setAttribute( QStringLiteral( "min-zoom" ), mMinZoomLevel );
34   elem.setAttribute( QStringLiteral( "max-zoom" ), mMaxZoomLevel );
35 
36   QDomDocument doc = elem.ownerDocument();
37   QDomElement elemLabelSettings = mLabelSettings.writeXml( doc, context );
38   elem.appendChild( elemLabelSettings );
39 }
40 
readXml(const QDomElement & elem,const QgsReadWriteContext & context)41 void QgsVectorTileBasicLabelingStyle::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
42 {
43   mStyleName = elem.attribute( QStringLiteral( "name" ) );
44   mLayerName = elem.attribute( QStringLiteral( "layer" ) );
45   mGeometryType = static_cast<QgsWkbTypes::GeometryType>( elem.attribute( QStringLiteral( "geometry" ) ).toInt() );
46   mEnabled = elem.attribute( QStringLiteral( "enabled" ) ).toInt();
47   mExpression = elem.attribute( QStringLiteral( "expression" ) );
48   mMinZoomLevel = elem.attribute( QStringLiteral( "min-zoom" ) ).toInt();
49   mMaxZoomLevel = elem.attribute( QStringLiteral( "max-zoom" ) ).toInt();
50 
51   QDomElement elemLabelSettings = elem.firstChildElement( QStringLiteral( "settings" ) );
52   mLabelSettings.readXml( elemLabelSettings, context );
53 }
54 
55 
56 //
57 
58 
QgsVectorTileBasicLabeling()59 QgsVectorTileBasicLabeling::QgsVectorTileBasicLabeling()
60 {
61 }
62 
type() const63 QString QgsVectorTileBasicLabeling::type() const
64 {
65   return QStringLiteral( "basic" );
66 }
67 
clone() const68 QgsVectorTileLabeling *QgsVectorTileBasicLabeling::clone() const
69 {
70   QgsVectorTileBasicLabeling *l = new QgsVectorTileBasicLabeling;
71   l->mStyles = mStyles;
72   return l;
73 }
74 
provider(QgsVectorTileLayer * layer) const75 QgsVectorTileLabelProvider *QgsVectorTileBasicLabeling::provider( QgsVectorTileLayer *layer ) const
76 {
77   return new QgsVectorTileBasicLabelProvider( layer, mStyles );
78 }
79 
writeXml(QDomElement & elem,const QgsReadWriteContext & context) const80 void QgsVectorTileBasicLabeling::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
81 {
82   QDomDocument doc = elem.ownerDocument();
83   QDomElement elemStyles = doc.createElement( QStringLiteral( "styles" ) );
84   for ( const QgsVectorTileBasicLabelingStyle &layerStyle : mStyles )
85   {
86     QDomElement elemStyle = doc.createElement( QStringLiteral( "style" ) );
87     layerStyle.writeXml( elemStyle, context );
88     elemStyles.appendChild( elemStyle );
89   }
90   elem.appendChild( elemStyles );
91 }
92 
readXml(const QDomElement & elem,const QgsReadWriteContext & context)93 void QgsVectorTileBasicLabeling::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
94 {
95   mStyles.clear();
96 
97   QDomElement elemStyles = elem.firstChildElement( QStringLiteral( "styles" ) );
98   QDomElement elemStyle = elemStyles.firstChildElement( QStringLiteral( "style" ) );
99   while ( !elemStyle.isNull() )
100   {
101     QgsVectorTileBasicLabelingStyle layerStyle;
102     layerStyle.readXml( elemStyle, context );
103     mStyles.append( layerStyle );
104     elemStyle = elemStyle.nextSiblingElement( QStringLiteral( "style" ) );
105   }
106 }
107 
108 
109 //
110 
111 
QgsVectorTileBasicLabelProvider(QgsVectorTileLayer * layer,const QList<QgsVectorTileBasicLabelingStyle> & styles)112 QgsVectorTileBasicLabelProvider::QgsVectorTileBasicLabelProvider( QgsVectorTileLayer *layer, const QList<QgsVectorTileBasicLabelingStyle> &styles )
113   : QgsVectorTileLabelProvider( layer )
114   , mStyles( styles )
115 {
116 
117   for ( int i = 0; i < mStyles.count(); ++i )
118   {
119     const QgsVectorTileBasicLabelingStyle &style = mStyles[i];
120     //QgsFields fields = QgsVectorTileUtils::makeQgisFields( mRequiredFields[style.layerName()] );
121     QString providerId = QString::number( i );
122     QgsPalLayerSettings labelSettings = style.labelSettings();
123     mSubProviders.append( new QgsVectorLayerLabelProvider( style.geometryType(), QgsFields(), layer->crs(), providerId, &labelSettings, layer ) );
124   }
125 }
126 
usedAttributes(const QgsRenderContext & context,int tileZoom) const127 QMap<QString, QSet<QString> > QgsVectorTileBasicLabelProvider::usedAttributes( const QgsRenderContext &context, int tileZoom ) const
128 {
129   QMap<QString, QSet<QString> > requiredFields;
130   for ( const QgsVectorTileBasicLabelingStyle &layerStyle : std::as_const( mStyles ) )
131   {
132     if ( !layerStyle.isActive( tileZoom ) )
133       continue;
134 
135     if ( !layerStyle.filterExpression().isEmpty() )
136     {
137       QgsExpression expr( layerStyle.filterExpression() );
138       requiredFields[layerStyle.layerName()].unite( expr.referencedColumns() );
139     }
140 
141     requiredFields[layerStyle.layerName()].unite( layerStyle.labelSettings().referencedFields( context ) );
142   }
143   return requiredFields;
144 }
145 
requiredLayers(QgsRenderContext &,int tileZoom) const146 QSet<QString> QgsVectorTileBasicLabelProvider::requiredLayers( QgsRenderContext &, int tileZoom ) const
147 {
148   QSet< QString > res;
149   for ( const QgsVectorTileBasicLabelingStyle &layerStyle : std::as_const( mStyles ) )
150   {
151     if ( layerStyle.isActive( tileZoom ) )
152     {
153       res.insert( layerStyle.layerName() );
154     }
155   }
156   return res;
157 }
158 
setFields(const QMap<QString,QgsFields> & perLayerFields)159 void QgsVectorTileBasicLabelProvider::setFields( const QMap<QString, QgsFields> &perLayerFields )
160 {
161   mPerLayerFields = perLayerFields;
162 }
163 
subProviders()164 QList<QgsAbstractLabelProvider *> QgsVectorTileBasicLabelProvider::subProviders()
165 {
166   QList<QgsAbstractLabelProvider *> lst;
167   for ( QgsVectorLayerLabelProvider *subprovider : std::as_const( mSubProviders ) )
168   {
169     if ( subprovider )  // sub-providers that failed to initialize are set to null
170       lst << subprovider;
171   }
172   return lst;
173 }
174 
prepare(QgsRenderContext & context,QSet<QString> & attributeNames)175 bool QgsVectorTileBasicLabelProvider::prepare( QgsRenderContext &context, QSet<QString> &attributeNames )
176 {
177   for ( QgsVectorLayerLabelProvider *provider : std::as_const( mSubProviders ) )
178     provider->setEngine( mEngine );
179 
180   // populate sub-providers
181   for ( int i = 0; i < mSubProviders.count(); ++i )
182   {
183     QgsFields fields = mPerLayerFields[mStyles[i].layerName()];
184 
185     QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Layer" ) ); // will be deleted by popper
186     scope->setFields( fields );
187     QgsExpressionContextScopePopper popper( context.expressionContext(), scope );
188 
189     mSubProviders[i]->setFields( fields );
190     if ( !mSubProviders[i]->prepare( context, attributeNames ) )
191     {
192       QgsDebugMsg( QStringLiteral( "Failed to prepare labeling for style index" ) + QString::number( i ) );
193       mSubProviders[i] = nullptr;
194     }
195   }
196   return true;
197 }
198 
registerTileFeatures(const QgsVectorTileRendererData & tile,QgsRenderContext & context)199 void QgsVectorTileBasicLabelProvider::registerTileFeatures( const QgsVectorTileRendererData &tile, QgsRenderContext &context )
200 {
201   const QgsVectorTileFeatures tileData = tile.features();
202   int zoomLevel = tile.id().zoomLevel();
203 
204   for ( int i = 0; i < mStyles.count(); ++i )
205   {
206     const QgsVectorTileBasicLabelingStyle &layerStyle = mStyles.at( i );
207     if ( !layerStyle.isActive( zoomLevel ) )
208       continue;
209 
210     QgsFields fields = mPerLayerFields[layerStyle.layerName()];
211 
212     QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Layer" ) ); // will be deleted by popper
213     scope->setFields( fields );
214     QgsExpressionContextScopePopper popper( context.expressionContext(), scope );
215 
216     QgsExpression filterExpression( layerStyle.filterExpression() );
217     filterExpression.prepare( &context.expressionContext() );
218 
219     QgsVectorLayerLabelProvider *subProvider = mSubProviders[i];
220     if ( !subProvider )
221       continue;  // sub-providers that failed to initialize are set to null
222 
223     if ( layerStyle.layerName().isEmpty() )
224     {
225       // matching all layers
226       for ( QString layerName : tileData.keys() )
227       {
228         for ( const QgsFeature &f : tileData[layerName] )
229         {
230           scope->setFeature( f );
231           if ( filterExpression.isValid() && !filterExpression.evaluate( &context.expressionContext() ).toBool() )
232             continue;
233 
234           const QgsWkbTypes::GeometryType featureType = QgsWkbTypes::geometryType( f.geometry().wkbType() );
235           if ( featureType == layerStyle.geometryType() )
236           {
237             subProvider->registerFeature( f, context );
238           }
239           else if ( featureType == QgsWkbTypes::PolygonGeometry && layerStyle.geometryType() == QgsWkbTypes::PointGeometry )
240           {
241             // be tolerant and permit labeling polygons with a point layer style, as some style definitions use this approach
242             // to label the polygon center
243             QgsFeature centroid = f;
244             const QgsRectangle boundingBox = f.geometry().boundingBox();
245             centroid.setGeometry( f.geometry().poleOfInaccessibility( std::min( boundingBox.width(), boundingBox.height() ) / 20 ) );
246             subProvider->registerFeature( centroid, context );
247           }
248         }
249       }
250     }
251     else if ( tileData.contains( layerStyle.layerName() ) )
252     {
253       // matching one particular layer
254       for ( const QgsFeature &f : tileData[layerStyle.layerName()] )
255       {
256         scope->setFeature( f );
257         if ( filterExpression.isValid() && !filterExpression.evaluate( &context.expressionContext() ).toBool() )
258           continue;
259 
260         const QgsWkbTypes::GeometryType featureType = QgsWkbTypes::geometryType( f.geometry().wkbType() );
261         if ( featureType == layerStyle.geometryType() )
262         {
263           subProvider->registerFeature( f, context );
264         }
265         else if ( featureType == QgsWkbTypes::PolygonGeometry && layerStyle.geometryType() == QgsWkbTypes::PointGeometry )
266         {
267           // be tolerant and permit labeling polygons with a point layer style, as some style definitions use this approach
268           // to label the polygon center
269           QgsFeature centroid = f;
270           const QgsRectangle boundingBox = f.geometry().boundingBox();
271           centroid.setGeometry( f.geometry().poleOfInaccessibility( std::min( boundingBox.width(), boundingBox.height() ) / 20 ) );
272           subProvider->registerFeature( centroid, context );
273         }
274       }
275     }
276   }
277 }
278