Skip to content

Commit

Permalink
Add method to determine approximate rendered symbol bounds for markers
Browse files Browse the repository at this point in the history
Sponsored by City of Uster
  • Loading branch information
nyalldawson committed Nov 20, 2015
1 parent 4d02e48 commit 4203a7c
Show file tree
Hide file tree
Showing 29 changed files with 792 additions and 222 deletions.
1 change: 1 addition & 0 deletions python/core/qgsmapsettings.sip
Expand Up @@ -88,6 +88,7 @@ class QgsMapSettings
DrawLabeling, //!< Enable drawing of labels on top of the map
UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations
DrawSelection, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing)
// TODO: ignore scale-based visibility (overview)
};
typedef QFlags<QgsMapSettings::Flag> Flags;
Expand Down
1 change: 1 addition & 0 deletions python/core/qgsrendercontext.sip
Expand Up @@ -18,6 +18,7 @@ class QgsRenderContext
UseAdvancedEffects, //!< Enable layer transparency and blending effects
UseRenderingOptimization, //!< Enable vector simplification and other rendering optimizations
DrawSelection, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds, //!< Draw bounds of symbols (for debugging/testing)
};
typedef QFlags<QgsRenderContext::Flag> Flags;

Expand Down
2 changes: 2 additions & 0 deletions python/core/symbology-ng/qgsellipsesymbollayerv2.sip
Expand Up @@ -67,4 +67,6 @@ class QgsEllipseSymbolLayerV2 : QgsMarkerSymbolLayerV2

void setMapUnitScale( const QgsMapUnitScale& scale );
QgsMapUnitScale mapUnitScale() const;

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
};
7 changes: 7 additions & 0 deletions python/core/symbology-ng/qgsmarkersymbollayerv2.sip
Expand Up @@ -75,6 +75,8 @@ class QgsSimpleMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
void setMapUnitScale( const QgsMapUnitScale& scale );
QgsMapUnitScale mapUnitScale() const;

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );

protected:
void drawMarker( QPainter* p, QgsSymbolV2RenderContext& context );

Expand Down Expand Up @@ -145,6 +147,8 @@ class QgsSvgMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
QgsMapUnitScale mapUnitScale() const;

bool writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext* context, const QgsFeature* f, const QPointF& shift = QPointF( 0.0, 0.0 ) ) const;

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
};

class QgsFontMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2
Expand Down Expand Up @@ -190,4 +194,7 @@ class QgsFontMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2

QChar character() const;
void setCharacter( QChar ch );

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );

};
9 changes: 9 additions & 0 deletions python/core/symbology-ng/qgssymbollayerv2.sip
Expand Up @@ -385,6 +385,15 @@ class QgsMarkerSymbolLayerV2 : QgsSymbolLayerV2
void setVerticalAnchorPoint( VerticalAnchorPoint v );
VerticalAnchorPoint verticalAnchorPoint() const;

/** Returns the approximate bounding box of the marker symbol layer, taking into account
* any data defined overrides and offsets which are set for the marker layer.
* @returns approximate symbol bounds, in painter units
* @note added in QGIS 2.14
* @note this method will become pure virtual in QGIS 3.0
*/
//TODO QGIS 3.0 - make pure virtual
virtual QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );

protected:
QgsMarkerSymbolLayerV2( bool locked = false );

Expand Down
6 changes: 6 additions & 0 deletions python/core/symbology-ng/qgssymbolv2.sip
Expand Up @@ -305,6 +305,12 @@ class QgsMarkerSymbolV2 : QgsSymbolV2

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

/** Returns the approximate bounding box of the marker symbol, which includes the bounding box
* of all symbol layers for the symbol.
* @returns approximate symbol bounds, in painter units
* @note added in QGIS 2.14 */
QRectF bounds( const QPointF& point, QgsRenderContext& context ) const;

virtual QgsMarkerSymbolV2* clone() const /Factory/;
};

Expand Down
1 change: 1 addition & 0 deletions src/core/qgsmapsettings.h
Expand Up @@ -135,6 +135,7 @@ class CORE_EXPORT QgsMapSettings
DrawLabeling = 0x10, //!< Enable drawing of labels on top of the map
UseRenderingOptimization = 0x20, //!< Enable vector simplification and other rendering optimizations
DrawSelection = 0x40, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds = 0x80, //!< Draw bounds of symbols (for debugging/testing)
// TODO: ignore scale-based visibility (overview)
};
Q_DECLARE_FLAGS( Flags, Flag )
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsrendercontext.cpp
Expand Up @@ -80,6 +80,7 @@ QgsRenderContext QgsRenderContext::fromMapSettings( const QgsMapSettings& mapSet
ctx.setCoordinateTransform( 0 );
ctx.setSelectionColor( mapSettings.selectionColor() );
ctx.setFlag( DrawSelection, mapSettings.testFlag( QgsMapSettings::DrawSelection ) );
ctx.setFlag( DrawSymbolBounds, mapSettings.testFlag( QgsMapSettings::DrawSymbolBounds ) );
ctx.setRasterScaleFactor( 1.0 );
ctx.setScaleFactor( mapSettings.outputDpi() / 25.4 ); // = pixels per mm
ctx.setRendererScale( mapSettings.scale() );
Expand Down
1 change: 1 addition & 0 deletions src/core/qgsrendercontext.h
Expand Up @@ -57,6 +57,7 @@ class CORE_EXPORT QgsRenderContext
UseAdvancedEffects = 0x04, //!< Enable layer transparency and blending effects
UseRenderingOptimization = 0x08, //!< Enable vector simplification and other rendering optimizations
DrawSelection = 0x10, //!< Whether vector selections should be shown in the rendered map
DrawSymbolBounds = 0x20, //!< Draw bounds of symbols (for debugging/testing)
};
Q_DECLARE_FLAGS( Flags, Flag )

Expand Down
173 changes: 131 additions & 42 deletions src/core/symbology-ng/qgsellipsesymbollayerv2.cpp
Expand Up @@ -250,47 +250,76 @@ void QgsEllipseSymbolLayerV2::renderPoint( const QPointF& point, QgsSymbolV2Rend
preparePath( symbolName, context, &scaledWidth, &scaledHeight, context.feature() );
}

//offset
double offsetX = 0;
double offsetY = 0;
markerOffset( context, scaledWidth, scaledHeight, mSymbolWidthUnit, mSymbolHeightUnit, offsetX, offsetY, mSymbolWidthMapUnitScale, mSymbolHeightMapUnitScale );
QPointF off( offsetX, offsetY );
//offset and rotation
bool hasDataDefinedRotation = false;
QPointF offset;
double angle = 0;
calculateOffsetAndRotation( context, scaledWidth, scaledHeight, hasDataDefinedRotation, offset, angle );

QPainter* p = context.renderContext().painter();
if ( !p )
{
return;
}

//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
double rotation = 0.0;

if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION ) )
QMatrix transform;
transform.translate( point.x() + offset.x(), point.y() + offset.y() );
if ( !qgsDoubleNear( angle, 0.0 ) )
{
context.setOriginalValueVariable( mAngle );
rotation = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, context, mAngle ).toDouble() + mLineAngle;

const QgsMapToPixel& m2p = context.renderContext().mapToPixel();
rotation += m2p.mapRotation();
transform.rotate( angle );
}
else if ( !qgsDoubleNear( mAngle + mLineAngle, 0.0 ) )

p->setPen( mPen );
p->setBrush( mBrush );
p->drawPath( transform.map( mPainterPath ) );
}


void QgsEllipseSymbolLayerV2::calculateOffsetAndRotation( QgsSymbolV2RenderContext& context,
double scaledWidth,
double scaledHeight,
bool& hasDataDefinedRotation,
QPointF& offset,
double& angle ) const
{
double offsetX = 0;
double offsetY = 0;
markerOffset( context, scaledWidth, scaledHeight, mSymbolWidthUnit, mSymbolHeightUnit, offsetX, offsetY, mSymbolWidthMapUnitScale, mSymbolHeightMapUnitScale );
offset = QPointF( offsetX, offsetY );

//priority for rotation: 1. data defined symbol level, 2. symbol layer rotation (mAngle)
bool ok = true;
angle = mAngle + mLineAngle;
bool usingDataDefinedRotation = false;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION ) )
{
rotation = mAngle + mLineAngle;
context.setOriginalValueVariable( angle );
angle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_ROTATION, context, mAngle, &ok ).toDouble() + mLineAngle;
usingDataDefinedRotation = ok;
}

if ( rotation )
off = _rotatedOffset( off, rotation );

QMatrix transform;
transform.translate( point.x() + off.x(), point.y() + off.y() );
if ( !qgsDoubleNear( rotation, 0.0 ) )
hasDataDefinedRotation = context.renderHints() & QgsSymbolV2::DataDefinedRotation || usingDataDefinedRotation;
if ( hasDataDefinedRotation )
{
transform.rotate( rotation );
// For non-point markers, "dataDefinedRotation" means following the
// shape (shape-data defined). For them, "field-data defined" does
// not work at all. TODO: if "field-data defined" ever gets implemented
// we'll need a way to distinguish here between the two, possibly
// using another flag in renderHints()
const QgsFeature* f = context.feature();
if ( f )
{
const QgsGeometry *g = f->constGeometry();
if ( g && g->type() == QGis::Point )
{
const QgsMapToPixel& m2p = context.renderContext().mapToPixel();
angle += m2p.mapRotation();
}
}
}

p->setPen( mPen );
p->setBrush( mBrush );
p->drawPath( transform.map( mPainterPath ) );
if ( angle )
offset = _rotatedOffset( offset, angle );
}

QString QgsEllipseSymbolLayerV2::layerType() const
Expand Down Expand Up @@ -465,11 +494,8 @@ QgsStringMap QgsEllipseSymbolLayerV2::properties() const
return map;
}

void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight, const QgsFeature* )
QSizeF QgsEllipseSymbolLayerV2::calculateSize( QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight )
{
mPainterPath = QPainterPath();
const QgsRenderContext& ct = context.renderContext();

double width = 0;

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

double height = 0;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_HEIGHT ) ) //1. priority: data defined setting on symbol layer level
Expand All @@ -509,29 +535,37 @@ void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV
{
*scaledHeight = height;
}
height = QgsSymbolLayerV2Utils::convertToPainterUnits( ct, height, mSymbolHeightUnit, mSymbolHeightMapUnitScale );
height = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), height, mSymbolHeightUnit, mSymbolHeightMapUnitScale );
return QSizeF( width, height );
}

void QgsEllipseSymbolLayerV2::preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth, double* scaledHeight, const QgsFeature* )
{
mPainterPath = QPainterPath();

QSizeF size = calculateSize( context, scaledWidth, scaledHeight );

if ( symbolName == "circle" )
{
mPainterPath.addEllipse( QRectF( -width / 2.0, -height / 2.0, width, height ) );
mPainterPath.addEllipse( QRectF( -size.width() / 2.0, -size.height() / 2.0, size.width(), size.height() ) );
}
else if ( symbolName == "rectangle" )
{
mPainterPath.addRect( QRectF( -width / 2.0, -height / 2.0, width, height ) );
mPainterPath.addRect( QRectF( -size.width() / 2.0, -size.height() / 2.0, size.width(), size.height() ) );
}
else if ( symbolName == "cross" )
{
mPainterPath.moveTo( 0, -height / 2.0 );
mPainterPath.lineTo( 0, height / 2.0 );
mPainterPath.moveTo( -width / 2.0, 0 );
mPainterPath.lineTo( width / 2.0, 0 );
mPainterPath.moveTo( 0, -size.height() / 2.0 );
mPainterPath.lineTo( 0, size.height() / 2.0 );
mPainterPath.moveTo( -size.width() / 2.0, 0 );
mPainterPath.lineTo( size.width() / 2.0, 0 );
}
else if ( symbolName == "triangle" )
{
mPainterPath.moveTo( 0, -height / 2.0 );
mPainterPath.lineTo( -width / 2.0, height / 2.0 );
mPainterPath.lineTo( width / 2.0, height / 2.0 );
mPainterPath.lineTo( 0, -height / 2.0 );
mPainterPath.moveTo( 0, -size.height() / 2.0 );
mPainterPath.lineTo( -size.width() / 2.0, size.height() / 2.0 );
mPainterPath.lineTo( size.width() / 2.0, size.height() / 2.0 );
mPainterPath.lineTo( 0, -size.height() / 2.0 );
}
}

Expand Down Expand Up @@ -572,6 +606,61 @@ QgsMapUnitScale QgsEllipseSymbolLayerV2::mapUnitScale() const
return QgsMapUnitScale();
}

QRectF QgsEllipseSymbolLayerV2::bounds( const QPointF& point, QgsSymbolV2RenderContext& context )
{
QSizeF size = calculateSize( context );

bool hasDataDefinedRotation = false;
QPointF offset;
double angle = 0;
calculateOffsetAndRotation( context, size.width(), size.height(), hasDataDefinedRotation, offset, angle );

double pixelSize = 1.0 / context.renderContext().rasterScaleFactor();

QMatrix transform;

// move to the desired position
transform.translate( point.x() + offset.x(), point.y() + offset.y() );

if ( !qgsDoubleNear( angle, 0.0 ) )
transform.rotate( angle );

double penWidth = 0.0;
bool ok = true;
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH ) )
{
context.setOriginalValueVariable( mOutlineWidth );
double outlineWidth = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_WIDTH, context, QVariant(), &ok ).toDouble();
if ( ok )
{
penWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), outlineWidth, mOutlineWidthUnit, mOutlineWidthMapUnitScale );
}
}
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_STYLE ) )
{
context.setOriginalValueVariable( QgsSymbolLayerV2Utils::encodePenStyle( mPen.style() ) );
QString outlineStyle = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OUTLINE_STYLE, context, QVariant(), &ok ).toString();
if ( ok && outlineStyle == "no" )
{
penWidth = 0.0;
}
}

//antialiasing
penWidth += pixelSize;

QRectF symbolBounds = transform.mapRect( QRectF( -size.width() / 2.0,
-size.height() / 2.0,
size.width(),
size.height() ) );

//extend bounds by pen width / 2.0
symbolBounds.adjust( -penWidth / 2.0, -penWidth / 2.0,
penWidth / 2.0, penWidth / 2.0 );

return symbolBounds;
}

bool QgsEllipseSymbolLayerV2::writeDxf( QgsDxfExport& e, double mmMapUnitScaleFactor, const QString& layerName, QgsSymbolV2RenderContext *context, const QgsFeature*, const QPointF& shift ) const
{
//width
Expand Down
4 changes: 4 additions & 0 deletions src/core/symbology-ng/qgsellipsesymbollayerv2.h
Expand Up @@ -87,6 +87,8 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2
void setMapUnitScale( const QgsMapUnitScale& scale ) override;
QgsMapUnitScale mapUnitScale() const override;

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context ) override;

private:
QString mSymbolName;
double mSymbolWidth;
Expand Down Expand Up @@ -114,6 +116,8 @@ class CORE_EXPORT QgsEllipseSymbolLayerV2: public QgsMarkerSymbolLayerV2
@param f optional feature to render (0 if no data defined rendering)
*/
void preparePath( const QString& symbolName, QgsSymbolV2RenderContext& context, double* scaledWidth = 0, double* scaledHeight = 0, const QgsFeature* f = 0 );
QSizeF calculateSize( QgsSymbolV2RenderContext& context, double* scaledWidth = 0, double* scaledHeight = 0 );
void calculateOffsetAndRotation( QgsSymbolV2RenderContext& context, double scaledWidth, double scaledHeight, bool& hasDataDefinedRotation, QPointF& offset, double& angle ) const;
};

#endif // QGSELLIPSESYMBOLLAYERV2_H
Expand Down

0 comments on commit 4203a7c

Please sign in to comment.