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