Skip to content

Commit 4203a7c

Browse files
committed
Add method to determine approximate rendered symbol bounds for markers
Sponsored by City of Uster
1 parent 4d02e48 commit 4203a7c

29 files changed

+792
-222
lines changed

python/core/qgsmapsettings.sip

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ class QgsMapSettings
8888
DrawLabeling, //!< Enable drawing of labels on top of the map
8989
UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations
9090
DrawSelection, //!< Whether vector selections should be shown in the rendered map
91+
DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing)
9192
// TODO: ignore scale-based visibility (overview)
9293
};
9394
typedef QFlags<QgsMapSettings::Flag> Flags;

python/core/qgsrendercontext.sip

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class QgsRenderContext
1818
UseAdvancedEffects, //!< Enable layer transparency and blending effects
1919
UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations
2020
DrawSelection, //!< Whether vector selections should be shown in the rendered map
21+
DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing)
2122
};
2223
typedef QFlags<QgsRenderContext::Flag> Flags;
2324

python/core/symbology-ng/qgsellipsesymbollayerv2.sip

+2
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,6 @@ class QgsEllipseSymbolLayerV2 : QgsMarkerSymbolLayerV2
6767

6868
void setMapUnitScale( const QgsMapUnitScale& scale );
6969
QgsMapUnitScale mapUnitScale() const;
70+
71+
QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
7072
};

python/core/symbology-ng/qgsmarkersymbollayerv2.sip

+7
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class QgsSimpleMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
7575
void setMapUnitScale( const QgsMapUnitScale& scale );
7676
QgsMapUnitScale mapUnitScale() const;
7777

78+
QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
79+
7880
protected:
7981
void drawMarker( QPainter* p, QgsSymbolV2RenderContext& context );
8082

@@ -145,6 +147,8 @@ class QgsSvgMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
145147
QgsMapUnitScale mapUnitScale() const;
146148

147149
bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, const QPointF& shift = QPointF( 0.0, 0.0 ) ) const;
150+
151+
QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
148152
};
149153

150154
class QgsFontMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
@@ -190,4 +194,7 @@ class QgsFontMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
190194

191195
QChar character() const;
192196
void setCharacter( QChar ch );
197+
198+
QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
199+
193200
};

python/core/symbology-ng/qgssymbollayerv2.sip

+9
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,15 @@ class QgsMarkerSymbolLayerV2 : QgsSymbolLayerV2
385385
void setVerticalAnchorPoint( VerticalAnchorPoint v );
386386
VerticalAnchorPoint verticalAnchorPoint() const;
387387

388+
/** Returns the approximate bounding box of the marker symbol layer, taking into account
389+
* any data defined overrides and offsets which are set for the marker layer.
390+
* @returns approximate symbol bounds, in painter units
391+
* @note added in QGIS 2.14
392+
* @note this method will become pure virtual in QGIS 3.0
393+
*/
394+
//TODO QGIS 3.0 - make pure virtual
395+
virtual QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
396+
388397
protected:
389398
QgsMarkerSymbolLayerV2( bool locked = false );
390399

python/core/symbology-ng/qgssymbolv2.sip

+6
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ class QgsMarkerSymbolV2 : QgsSymbolV2
305305

306306
void renderPoint( const QPointF& point, const QgsFeature* f, QgsRenderContext& context, int layer = -1, bool selected = false );
307307

308+
/** Returns the approximate bounding box of the marker symbol, which includes the bounding box
309+
* of all symbol layers for the symbol.
310+
* @returns approximate symbol bounds, in painter units
311+
* @note added in QGIS 2.14 */
312+
QRectF bounds( const QPointF& point, QgsRenderContext& context ) const;
313+
308314
virtual QgsMarkerSymbolV2* clone() const /Factory/;
309315
};
310316

src/core/qgsmapsettings.h

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ class CORE_EXPORT QgsMapSettings
135135
DrawLabeling = 0x10, //!< Enable drawing of labels on top of the map
136136
UseRenderingOptimization = 0x20, //!< Enable vector simplification and other rendering optimizations
137137
DrawSelection = 0x40, //!< Whether vector selections should be shown in the rendered map
138+
DrawSymbolBounds = 0x80, //!< Draw bounds of symbols (for debugging/testing)
138139
// TODO: ignore scale-based visibility (overview)
139140
};
140141
Q_DECLARE_FLAGS( Flags, Flag )

src/core/qgsrendercontext.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings& mapSet
8080
ctx.setCoordinateTransform( 0 );
8181
ctx.setSelectionColor( mapSettings.selectionColor() );
8282
ctx.setFlag( DrawSelection, mapSettings.testFlag( QgsMapSettings::DrawSelection ) );
83+
ctx.setFlag( DrawSymbolBounds, mapSettings.testFlag( QgsMapSettings::DrawSymbolBounds ) );
8384
ctx.setRasterScaleFactor( 1.0 );
8485
ctx.setScaleFactor( mapSettings.outputDpi() / 25.4 ); // = pixels per mm
8586
ctx.setRendererScale( mapSettings.scale() );

src/core/qgsrendercontext.h

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class CORE_EXPORT QgsRenderContext
5757
UseAdvancedEffects = 0x04, //!< Enable layer transparency and blending effects
5858
UseRenderingOptimization = 0x08, //!< Enable vector simplification and other rendering optimizations
5959
DrawSelection = 0x10, //!< Whether vector selections should be shown in the rendered map
60+
DrawSymbolBounds = 0x20, //!< Draw bounds of symbols (for debugging/testing)
6061
};
6162
Q_DECLARE_FLAGS( Flags, Flag )
6263

src/core/symbology-ng/qgsellipsesymbollayerv2.cpp

+131-42
Original file line numberDiff line numberDiff line change
@@ -250,47 +250,76 @@ void QgsEllipseSymbolLayerV2::renderPoint( const QPointF& point, QgsSymbolV2Rend
250250
preparePath( symbolName, context, &scaledWidth, &scaledHeight, context.feature() );
251251
}
252252

253-
//offset
254-
double offsetX = 0;
255-
double offsetY = 0;
256-
markerOffset( context, scaledWidth, scaledHeight, mSymbolWidthUnit, mSymbolHeightUnit, offsetX, offsetY, mSymbolWidthMapUnitScale, mSymbolHeightMapUnitScale );
257-
QPointF off( offsetX, offsetY );
253+
//offset and rotation
254+
bool hasDataDefinedRotation = false;
255+
QPointF offset;
256+
double angle = 0;
257+
calculateOffsetAndRotation( context, scaledWidth, scaledHeight, hasDataDefinedRotation, offset, angle );
258258

259259
QPainter* p = context.renderContext().painter();
260260
if ( !p )
261261
{
262262
return;
263263
}
264264

265-
//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
266-
double rotation = 0.0;
267-
268-
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION ) )
265+
QMatrix transform;
266+
transform.translate( point.x() + offset.x(), point.y() + offset.y() );
267+
if ( !qgsDoubleNear( angle, 0.0 ) )
269268
{
270-
context.setOriginalValueVariable( mAngle );
271-
rotation = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, context, mAngle ).toDouble() + mLineAngle;
272-
273-
const QgsMapToPixel& m2p = context.renderContext().mapToPixel();
274-
rotation += m2p.mapRotation();
269+
transform.rotate( angle );
275270
}
276-
else if ( !qgsDoubleNear( mAngle + mLineAngle, 0.0 ) )
271+
272+
p->setPen( mPen );
273+
p->setBrush( mBrush );
274+
p->drawPath( transform.map( mPainterPath ) );
275+
}
276+
277+
278+
void QgsEllipseSymbolLayerV2::calculateOffsetAndRotation( QgsSymbolV2RenderContext& context,
279+
double scaledWidth,
280+
double scaledHeight,
281+
bool& hasDataDefinedRotation,
282+
QPointF& offset,
283+
double& angle ) const
284+
{
285+
double offsetX = 0;
286+
double offsetY = 0;
287+
markerOffset( context, scaledWidth, scaledHeight, mSymbolWidthUnit, mSymbolHeightUnit, offsetX, offsetY, mSymbolWidthMapUnitScale, mSymbolHeightMapUnitScale );
288+
offset = QPointF( offsetX, offsetY );
289+
290+
//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
291+
bool ok = true;
292+
angle = mAngle + mLineAngle;
293+
bool usingDataDefinedRotation = false;
294+
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION ) )
277295
{
278-
rotation = mAngle + mLineAngle;
296+
context.setOriginalValueVariable( angle );
297+
angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, context, mAngle, &ok ).toDouble() + mLineAngle;
298+
usingDataDefinedRotation = ok;
279299
}
280300

281-
if ( rotation )
282-
off = _rotatedOffset( off, rotation );
283-
284-
QMatrix transform;
285-
transform.translate( point.x() + off.x(), point.y() + off.y() );
286-
if ( !qgsDoubleNear( rotation, 0.0 ) )
301+
hasDataDefinedRotation = context.renderHints() & QgsSymbolV2::DataDefinedRotation || usingDataDefinedRotation;
302+
if ( hasDataDefinedRotation )
287303
{
288-
transform.rotate( rotation );
304+
// For non-point markers, "dataDefinedRotation" means following the
305+
// shape (shape-data defined). For them, "field-data defined" does
306+
// not work at all. TODO: if "field-data defined" ever gets implemented
307+
// we'll need a way to distinguish here between the two, possibly
308+
// using another flag in renderHints()
309+
const QgsFeature* f = context.feature();
310+
if ( f )
311+
{
312+
const QgsGeometry *g = f->constGeometry();
313+
if ( g && g->type() == QGis::Point )
314+
{
315+
const QgsMapToPixel& m2p = context.renderContext().mapToPixel();
316+
angle += m2p.mapRotation();
317+
}
318+
}
289319
}
290320

291-
p->setPen( mPen );
292-
p->setBrush( mBrush );
293-
p->drawPath( transform.map( mPainterPath ) );
321+
if ( angle )
322+
offset = _rotatedOffset( offset, angle );
294323
}
295324

296325
QString QgsEllipseSymbolLayerV2::layerType() const
@@ -465,11 +494,8 @@ QgsStringMap QgsEllipseSymbolLayerV2::properties() const
465494
return map;
466495
}
467496

468-
void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight, const QgsFeature* )
497+
QSizeF QgsEllipseSymbolLayerV2::calculateSize( QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight )
469498
{
470-
mPainterPath = QPainterPath();
471-
const QgsRenderContext& ct = context.renderContext();
472-
473499
double width = 0;
474500

475501
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_WIDTH ) ) //1. priority: data defined setting on symbol layer le
@@ -489,7 +515,7 @@ void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV
489515
{
490516
*scaledWidth = width;
491517
}
492-
width = QgsSymbolLayerV2Utils::convertToPainterUnits( ct, width, mSymbolWidthUnit, mSymbolHeightMapUnitScale );
518+
width = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), width, mSymbolWidthUnit, mSymbolHeightMapUnitScale );
493519

494520
double height = 0;
495521
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_HEIGHT ) ) //1. priority: data defined setting on symbol layer level
@@ -509,29 +535,37 @@ void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV
509535
{
510536
*scaledHeight = height;
511537
}
512-
height = QgsSymbolLayerV2Utils::convertToPainterUnits( ct, height, mSymbolHeightUnit, mSymbolHeightMapUnitScale );
538+
height = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), height, mSymbolHeightUnit, mSymbolHeightMapUnitScale );
539+
return QSizeF( width, height );
540+
}
541+
542+
void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight, const QgsFeature* )
543+
{
544+
mPainterPath = QPainterPath();
545+
546+
QSizeF size = calculateSize( context, scaledWidth, scaledHeight );
513547

514548
if ( symbolName == "circle" )
515549
{
516-
mPainterPath.addEllipse( QRectF( -width / 2.0, -height / 2.0, width, height ) );
550+
mPainterPath.addEllipse( QRectF( -size.width() / 2.0, -size.height() / 2.0, size.width(), size.height() ) );
517551
}
518552
else if ( symbolName == "rectangle" )
519553
{
520-
mPainterPath.addRect( QRectF( -width / 2.0, -height / 2.0, width, height ) );
554+
mPainterPath.addRect( QRectF( -size.width() / 2.0, -size.height() / 2.0, size.width(), size.height() ) );
521555
}
522556
else if ( symbolName == "cross" )
523557
{
524-
mPainterPath.moveTo( 0, -height / 2.0 );
525-
mPainterPath.lineTo( 0, height / 2.0 );
526-
mPainterPath.moveTo( -width / 2.0, 0 );
527-
mPainterPath.lineTo( width / 2.0, 0 );
558+
mPainterPath.moveTo( 0, -size.height() / 2.0 );
559+
mPainterPath.lineTo( 0, size.height() / 2.0 );
560+
mPainterPath.moveTo( -size.width() / 2.0, 0 );
561+
mPainterPath.lineTo( size.width() / 2.0, 0 );
528562
}
529563
else if ( symbolName == "triangle" )
530564
{
531-
mPainterPath.moveTo( 0, -height / 2.0 );
532-
mPainterPath.lineTo( -width / 2.0, height / 2.0 );
533-
mPainterPath.lineTo( width / 2.0, height / 2.0 );
534-
mPainterPath.lineTo( 0, -height / 2.0 );
565+
mPainterPath.moveTo( 0, -size.height() / 2.0 );
566+
mPainterPath.lineTo( -size.width() / 2.0, size.height() / 2.0 );
567+
mPainterPath.lineTo( size.width() / 2.0, size.height() / 2.0 );
568+
mPainterPath.lineTo( 0, -size.height() / 2.0 );
535569
}
536570
}
537571

@@ -572,6 +606,61 @@ QgsMapUnitScale QgsEllipseSymbolLayerV2::mapUnitScale() const
572606
return QgsMapUnitScale();
573607
}
574608

609+
QRectF QgsEllipseSymbolLayerV2::bounds( const QPointF& point, QgsSymbolV2RenderContext& context )
610+
{
611+
QSizeF size = calculateSize( context );
612+
613+
bool hasDataDefinedRotation = false;
614+
QPointF offset;
615+
double angle = 0;
616+
calculateOffsetAndRotation( context, size.width(), size.height(), hasDataDefinedRotation, offset, angle );
617+
618+
double pixelSize = 1.0 / context.renderContext().rasterScaleFactor();
619+
620+
QMatrix transform;
621+
622+
// move to the desired position
623+
transform.translate( point.x() + offset.x(), point.y() + offset.y() );
624+
625+
if ( !qgsDoubleNear( angle, 0.0 ) )
626+
transform.rotate( angle );
627+
628+
double penWidth = 0.0;
629+
bool ok = true;
630+
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
631+
{
632+
context.setOriginalValueVariable( mOutlineWidth );
633+
double outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, context, QVariant(), &ok ).toDouble();
634+
if ( ok )
635+
{
636+
penWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), outlineWidth, mOutlineWidthUnit, mOutlineWidthMapUnitScale );
637+
}
638+
}
639+
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_STYLE ) )
640+
{
641+
context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodePenStyle( mPen.style() ) );
642+
QString outlineStyle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_STYLE, context, QVariant(), &ok ).toString();
643+
if ( ok && outlineStyle == "no" )
644+
{
645+
penWidth = 0.0;
646+
}
647+
}
648+
649+
//antialiasing
650+
penWidth += pixelSize;
651+
652+
QRectF symbolBounds = transform.mapRect( QRectF( -size.width() / 2.0,
653+
-size.height() / 2.0,
654+
size.width(),
655+
size.height() ) );
656+
657+
//extend bounds by pen width / 2.0
658+
symbolBounds.adjust( -penWidth / 2.0, -penWidth / 2.0,
659+
penWidth / 2.0, penWidth / 2.0 );
660+
661+
return symbolBounds;
662+
}
663+
575664
bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext *context, const QgsFeature*, const QPointF& shift ) const
576665
{
577666
//width

src/core/symbology-ng/qgsellipsesymbollayerv2.h

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2
8787
void setMapUnitScale( const QgsMapUnitScale& scale ) override;
8888
QgsMapUnitScale mapUnitScale() const override;
8989

90+
QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context ) override;
91+
9092
private:
9193
QString mSymbolName;
9294
double mSymbolWidth;
@@ -114,6 +116,8 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2
114116
@param f optional feature to render (0 if no data defined rendering)
115117
*/
116118
void preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth = 0, double* scaledHeight = 0, const QgsFeature* f = 0 );
119+
QSizeF calculateSize( QgsSymbolV2RenderContext& context, double* scaledWidth = 0, double* scaledHeight = 0 );
120+
void calculateOffsetAndRotation( QgsSymbolV2RenderContext& context, double scaledWidth, double scaledHeight, bool& hasDataDefinedRotation, QPointF& offset, double& angle ) const;
117121
};
118122

119123
#endif // QGSELLIPSESYMBOLLAYERV2_H

0 commit comments

Comments
 (0)