Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsvectorlayerdiagramprovider.cpp
3 : : --------------------------------------
4 : : Date : September 2015
5 : : Copyright : (C) 2015 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 "qgsvectorlayerdiagramprovider.h"
17 : :
18 : : #include "qgsgeometry.h"
19 : : #include "qgslabelsearchtree.h"
20 : : #include "qgsvectorlayer.h"
21 : : #include "qgsvectorlayerfeatureiterator.h"
22 : : #include "diagram/qgsdiagram.h"
23 : : #include "qgsgeos.h"
24 : : #include "qgslabelingresults.h"
25 : :
26 : : #include "feature.h"
27 : : #include "labelposition.h"
28 : :
29 : 0 : QgsVectorLayerDiagramProvider::QgsVectorLayerDiagramProvider( QgsVectorLayer *layer, bool ownFeatureLoop )
30 : 0 : : QgsAbstractLabelProvider( layer )
31 : 0 : , mSettings( *layer->diagramLayerSettings() )
32 : 0 : , mDiagRenderer( layer->diagramRenderer()->clone() )
33 : 0 : , mFields( layer->fields() )
34 : 0 : , mLayerCrs( layer->crs() )
35 : 0 : , mSource( ownFeatureLoop ? new QgsVectorLayerFeatureSource( layer ) : nullptr )
36 : 0 : , mOwnsSource( ownFeatureLoop )
37 : 0 : {
38 : 0 : init();
39 : 0 : }
40 : :
41 : :
42 : 0 : void QgsVectorLayerDiagramProvider::init()
43 : : {
44 : 0 : mName = mLayerId;
45 : 0 : mPriority = 1 - mSettings.priority() / 10.0; // convert 0..10 --> 1..0
46 : 0 : mPlacement = QgsPalLayerSettings::Placement( mSettings.placement() );
47 : 0 : }
48 : :
49 : :
50 : 0 : QgsVectorLayerDiagramProvider::~QgsVectorLayerDiagramProvider()
51 : 0 : {
52 : 0 : if ( mOwnsSource )
53 : 0 : delete mSource;
54 : :
55 : 0 : qDeleteAll( mFeatures );
56 : :
57 : : // renderer is owned by mSettings
58 : 0 : }
59 : :
60 : :
61 : 0 : QList<QgsLabelFeature *> QgsVectorLayerDiagramProvider::labelFeatures( QgsRenderContext &context )
62 : : {
63 : 0 : if ( !mSource )
64 : : {
65 : : // we have created the provider with "own feature loop" == false
66 : : // so it is assumed that prepare() has been already called followed by registerFeature() calls
67 : 0 : return mFeatures;
68 : : }
69 : :
70 : 0 : QSet<QString> attributeNames;
71 : 0 : if ( !prepare( context, attributeNames ) )
72 : 0 : return QList<QgsLabelFeature *>();
73 : :
74 : 0 : QgsRectangle layerExtent = context.extent();
75 : 0 : if ( mSettings.coordinateTransform().isValid() )
76 : 0 : layerExtent = mSettings.coordinateTransform().transformBoundingBox( context.extent(), QgsCoordinateTransform::ReverseTransform );
77 : :
78 : 0 : QgsFeatureRequest request;
79 : 0 : request.setFilterRect( layerExtent );
80 : 0 : request.setSubsetOfAttributes( attributeNames, mFields );
81 : 0 : const QgsFeatureFilterProvider *featureFilterProvider = context.featureFilterProvider();
82 : 0 : if ( featureFilterProvider )
83 : : {
84 : 0 : featureFilterProvider->filterFeatures( qobject_cast<QgsVectorLayer *>( mLayer ), request );
85 : 0 : }
86 : 0 : QgsFeatureIterator fit = mSource->getFeatures( request );
87 : :
88 : 0 : QgsFeature fet;
89 : 0 : while ( fit.nextFeature( fet ) )
90 : : {
91 : 0 : context.expressionContext().setFeature( fet );
92 : 0 : registerFeature( fet, context );
93 : : }
94 : :
95 : 0 : return mFeatures;
96 : 0 : }
97 : :
98 : :
99 : 0 : void QgsVectorLayerDiagramProvider::drawLabel( QgsRenderContext &context, pal::LabelPosition *label ) const
100 : : {
101 : : #if 1 // XXX strk
102 : : // features are pre-rotated but not scaled/translated,
103 : : // so we only disable rotation here. Ideally, they'd be
104 : : // also pre-scaled/translated, as suggested here:
105 : : // https://github.com/qgis/QGIS/issues/20071
106 : 0 : QgsMapToPixel xform = context.mapToPixel();
107 : 0 : xform.setMapRotation( 0, 0, 0 );
108 : : #else
109 : : const QgsMapToPixel &xform = context.mapToPixel();
110 : : #endif
111 : :
112 : 0 : QgsDiagramLabelFeature *dlf = dynamic_cast<QgsDiagramLabelFeature *>( label->getFeaturePart()->feature() );
113 : :
114 : 0 : QgsFeature feature;
115 : 0 : feature.setFields( mFields );
116 : 0 : feature.setValid( true );
117 : 0 : feature.setId( label->getFeaturePart()->featureId() );
118 : 0 : feature.setAttributes( dlf->attributes() );
119 : :
120 : 0 : context.expressionContext().setFeature( feature );
121 : :
122 : : //calculate top-left point for diagram
123 : : //first, calculate the centroid of the label (accounts for PAL creating
124 : : //rotated labels when we do not want to draw the diagrams rotated)
125 : 0 : double centerX = 0;
126 : 0 : double centerY = 0;
127 : 0 : for ( int i = 0; i < 4; ++i )
128 : : {
129 : 0 : centerX += label->getX( i );
130 : 0 : centerY += label->getY( i );
131 : 0 : }
132 : 0 : QgsPointXY outPt( centerX / 4.0, centerY / 4.0 );
133 : : //then, calculate the top left point for the diagram with this center position
134 : 0 : QgsPointXY centerPt = xform.transform( outPt.x() - label->getWidth() / 2,
135 : 0 : outPt.y() - label->getHeight() / 2 );
136 : :
137 : 0 : mSettings.renderer()->renderDiagram( feature, context, centerPt.toQPointF(), mSettings.dataDefinedProperties() );
138 : :
139 : : //insert into label search tree to manipulate position interactively
140 : 0 : mEngine->results()->mLabelSearchTree->insertLabel( label, label->getFeaturePart()->featureId(), mLayerId, QString(), QFont(), true, false );
141 : :
142 : 0 : }
143 : :
144 : :
145 : 0 : bool QgsVectorLayerDiagramProvider::prepare( const QgsRenderContext &context, QSet<QString> &attributeNames )
146 : : {
147 : 0 : QgsDiagramLayerSettings &s2 = mSettings;
148 : 0 : const QgsMapSettings &mapSettings = mEngine->mapSettings();
149 : :
150 : 0 : if ( context.coordinateTransform().isValid() )
151 : : // this is context for layer rendering
152 : 0 : s2.setCoordinateTransform( context.coordinateTransform() );
153 : : else
154 : : {
155 : : // otherwise fall back to creating our own CT
156 : 0 : s2.setCoordinateTransform( QgsCoordinateTransform( mLayerCrs, mapSettings.destinationCrs(), context.transformContext() ) );
157 : : }
158 : :
159 : 0 : s2.setRenderer( mDiagRenderer );
160 : :
161 : 0 : bool result = s2.prepare( context.expressionContext() );
162 : :
163 : : //add attributes needed by the diagram renderer
164 : 0 : attributeNames.unite( s2.referencedFields( context.expressionContext() ) );
165 : :
166 : 0 : return result;
167 : 0 : }
168 : :
169 : :
170 : 0 : void QgsVectorLayerDiagramProvider::registerFeature( QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
171 : : {
172 : 0 : QgsLabelFeature *label = registerDiagram( feature, context, obstacleGeometry );
173 : 0 : if ( label )
174 : 0 : mFeatures << label;
175 : 0 : }
176 : :
177 : 0 : void QgsVectorLayerDiagramProvider::setClipFeatureGeometry( const QgsGeometry &geometry )
178 : : {
179 : 0 : mLabelClipFeatureGeom = geometry;
180 : 0 : }
181 : :
182 : 0 : QgsLabelFeature *QgsVectorLayerDiagramProvider::registerDiagram( QgsFeature &feat, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
183 : : {
184 : 0 : const QgsMapSettings &mapSettings = mEngine->mapSettings();
185 : :
186 : 0 : const QgsDiagramRenderer *dr = mSettings.renderer();
187 : 0 : if ( dr )
188 : : {
189 : 0 : QList<QgsDiagramSettings> settingList = dr->diagramSettings();
190 : 0 : if ( !settingList.isEmpty() && settingList.at( 0 ).scaleBasedVisibility )
191 : : {
192 : 0 : double maxScale = settingList.at( 0 ).maximumScale;
193 : 0 : if ( maxScale > 0 && context.rendererScale() < maxScale )
194 : : {
195 : 0 : return nullptr;
196 : : }
197 : :
198 : 0 : double minScale = settingList.at( 0 ).minimumScale;
199 : 0 : if ( minScale > 0 && context.rendererScale() > minScale )
200 : : {
201 : 0 : return nullptr;
202 : : }
203 : 0 : }
204 : 0 : }
205 : :
206 : : // data defined show diagram? check this before doing any other processing
207 : 0 : if ( !mSettings.dataDefinedProperties().valueAsBool( QgsDiagramLayerSettings::Show, context.expressionContext(), true ) )
208 : 0 : return nullptr;
209 : :
210 : : // data defined obstacle?
211 : 0 : bool isObstacle = mSettings.dataDefinedProperties().valueAsBool( QgsDiagramLayerSettings::IsObstacle, context.expressionContext(), mSettings.isObstacle() );
212 : :
213 : : //convert geom to geos
214 : 0 : QgsGeometry geom = feat.geometry();
215 : 0 : QgsGeometry extentGeom = QgsGeometry::fromRect( mapSettings.visibleExtent() );
216 : 0 : if ( !qgsDoubleNear( mapSettings.rotation(), 0.0 ) )
217 : : {
218 : : //PAL features are prerotated, so extent also needs to be unrotated
219 : 0 : extentGeom.rotate( -mapSettings.rotation(), mapSettings.visibleExtent().center() );
220 : 0 : }
221 : :
222 : 0 : if ( QgsPalLabeling::geometryRequiresPreparation( geom, context, mSettings.coordinateTransform(), extentGeom ) )
223 : : {
224 : 0 : geom = QgsPalLabeling::prepareGeometry( geom, context, mSettings.coordinateTransform(), extentGeom );
225 : 0 : }
226 : 0 : if ( geom.isEmpty() )
227 : 0 : return nullptr;
228 : :
229 : 0 : const QgsGeometry clipGeometry = mLabelClipFeatureGeom.isNull() ? context.featureClipGeometry() : mLabelClipFeatureGeom;
230 : 0 : if ( !clipGeometry.isEmpty() )
231 : : {
232 : 0 : const QgsWkbTypes::GeometryType expectedType = geom.type();
233 : 0 : geom = geom.intersection( clipGeometry );
234 : 0 : geom.convertGeometryCollectionToSubclass( expectedType );
235 : 0 : }
236 : 0 : if ( geom.isEmpty() )
237 : 0 : return nullptr;
238 : :
239 : 0 : QgsGeometry preparedObstacleGeom;
240 : 0 : if ( isObstacle && !obstacleGeometry.isNull() && QgsPalLabeling::geometryRequiresPreparation( obstacleGeometry, context, mSettings.coordinateTransform(), extentGeom ) )
241 : : {
242 : 0 : preparedObstacleGeom = QgsPalLabeling::prepareGeometry( obstacleGeometry, context, mSettings.coordinateTransform(), extentGeom );
243 : 0 : }
244 : 0 : else if ( mSettings.isObstacle() && !obstacleGeometry.isNull() )
245 : : {
246 : 0 : preparedObstacleGeom = obstacleGeometry;
247 : 0 : }
248 : :
249 : 0 : double diagramWidth = 0;
250 : 0 : double diagramHeight = 0;
251 : 0 : if ( dr )
252 : : {
253 : 0 : QSizeF diagSize = dr->sizeMapUnits( feat, context );
254 : 0 : if ( diagSize.isValid() )
255 : : {
256 : 0 : diagramWidth = diagSize.width();
257 : 0 : diagramHeight = diagSize.height();
258 : 0 : }
259 : 0 : }
260 : :
261 : : // feature to the layer
262 : 0 : bool alwaysShow = mSettings.showAllDiagrams();
263 : 0 : context.expressionContext().setOriginalValueVariable( alwaysShow );
264 : 0 : alwaysShow = mSettings.dataDefinedProperties().valueAsBool( QgsDiagramLayerSettings::AlwaysShow, context.expressionContext(), alwaysShow );
265 : :
266 : : // new style data defined position
267 : 0 : bool ddPos = false;
268 : 0 : double ddPosX = 0.0;
269 : 0 : double ddPosY = 0.0;
270 : 0 : if ( mSettings.dataDefinedProperties().hasProperty( QgsDiagramLayerSettings::PositionX )
271 : 0 : && mSettings.dataDefinedProperties().property( QgsDiagramLayerSettings::PositionX ).isActive()
272 : 0 : && mSettings.dataDefinedProperties().hasProperty( QgsDiagramLayerSettings::PositionY )
273 : 0 : && mSettings.dataDefinedProperties().property( QgsDiagramLayerSettings::PositionY ).isActive() )
274 : : {
275 : 0 : ddPosX = mSettings.dataDefinedProperties().valueAsDouble( QgsDiagramLayerSettings::PositionX, context.expressionContext(), std::numeric_limits<double>::quiet_NaN() );
276 : 0 : ddPosY = mSettings.dataDefinedProperties().valueAsDouble( QgsDiagramLayerSettings::PositionY, context.expressionContext(), std::numeric_limits<double>::quiet_NaN() );
277 : :
278 : 0 : ddPos = !std::isnan( ddPosX ) && !std::isnan( ddPosY );
279 : :
280 : 0 : if ( ddPos )
281 : : {
282 : 0 : QgsCoordinateTransform ct = mSettings.coordinateTransform();
283 : 0 : if ( ct.isValid() && !ct.isShortCircuited() )
284 : : {
285 : 0 : double z = 0;
286 : 0 : ct.transformInPlace( ddPosX, ddPosY, z );
287 : 0 : }
288 : : //data defined diagram position is always centered
289 : 0 : ddPosX -= diagramWidth / 2.0;
290 : 0 : ddPosY -= diagramHeight / 2.0;
291 : 0 : }
292 : 0 : }
293 : :
294 : 0 : QgsDiagramLabelFeature *lf = new QgsDiagramLabelFeature( feat.id(), QgsGeos::asGeos( geom ), QSizeF( diagramWidth, diagramHeight ) );
295 : 0 : lf->setHasFixedPosition( ddPos );
296 : 0 : lf->setFixedPosition( QgsPointXY( ddPosX, ddPosY ) );
297 : 0 : lf->setHasFixedAngle( true );
298 : 0 : lf->setFixedAngle( 0 );
299 : 0 : lf->setAlwaysShow( alwaysShow );
300 : 0 : QgsLabelObstacleSettings os;
301 : 0 : os.setIsObstacle( isObstacle );
302 : 0 : os.setObstacleGeometry( preparedObstacleGeom );
303 : 0 : lf->setObstacleSettings( os );
304 : :
305 : 0 : if ( dr )
306 : : {
307 : : //append the diagram attributes to lbl
308 : 0 : lf->setAttributes( feat.attributes() );
309 : 0 : }
310 : :
311 : : // data defined priority?
312 : 0 : if ( mSettings.dataDefinedProperties().hasProperty( QgsDiagramLayerSettings::Priority )
313 : 0 : && mSettings.dataDefinedProperties().property( QgsDiagramLayerSettings::Priority ).isActive() )
314 : : {
315 : 0 : context.expressionContext().setOriginalValueVariable( mSettings.priority() );
316 : 0 : double priorityD = mSettings.dataDefinedProperties().valueAsDouble( QgsDiagramLayerSettings::Priority, context.expressionContext(), mSettings.priority() );
317 : 0 : priorityD = std::clamp( priorityD, 0.0, 10.0 );
318 : 0 : priorityD = 1 - priorityD / 10.0; // convert 0..10 --> 1..0
319 : 0 : lf->setPriority( priorityD );
320 : 0 : }
321 : :
322 : : // z-Index
323 : 0 : double zIndex = mSettings.zIndex();
324 : 0 : if ( mSettings.dataDefinedProperties().hasProperty( QgsDiagramLayerSettings::ZIndex )
325 : 0 : && mSettings.dataDefinedProperties().property( QgsDiagramLayerSettings::ZIndex ).isActive() )
326 : : {
327 : 0 : context.expressionContext().setOriginalValueVariable( zIndex );
328 : 0 : zIndex = mSettings.dataDefinedProperties().valueAsDouble( QgsDiagramLayerSettings::ZIndex, context.expressionContext(), zIndex );
329 : 0 : }
330 : 0 : lf->setZIndex( zIndex );
331 : :
332 : : // label distance
333 : 0 : QgsPointXY ptZero = mapSettings.mapToPixel().toMapCoordinates( 0, 0 );
334 : 0 : QgsPointXY ptOne = mapSettings.mapToPixel().toMapCoordinates( 1, 0 );
335 : 0 : double dist = mSettings.distance();
336 : :
337 : 0 : if ( mSettings.dataDefinedProperties().hasProperty( QgsDiagramLayerSettings::Distance )
338 : 0 : && mSettings.dataDefinedProperties().property( QgsDiagramLayerSettings::Distance ).isActive() )
339 : : {
340 : 0 : context.expressionContext().setOriginalValueVariable( dist );
341 : 0 : dist = mSettings.dataDefinedProperties().valueAsDouble( QgsDiagramLayerSettings::Distance, context.expressionContext(), dist );
342 : 0 : }
343 : :
344 : 0 : dist *= ptOne.distance( ptZero );
345 : :
346 : 0 : lf->setDistLabel( dist );
347 : 0 : return lf;
348 : 0 : }
349 : :
|