Skip to content
Permalink
Browse files

Add method to determine approximate rendered symbol bounds for markers

Sponsored by City of Uster
  • Loading branch information
nyalldawson committed Nov 20, 2015
1 parent 4d02e48 commit 4203a7c1f4c78e06c505de425519e7c19aae22a7
Showing with 792 additions and 222 deletions.
  1. +1 −0 python/core/qgsmapsettings.sip
  2. +1 −0 python/core/qgsrendercontext.sip
  3. +2 −0 python/core/symbology-ng/qgsellipsesymbollayerv2.sip
  4. +7 −0 python/core/symbology-ng/qgsmarkersymbollayerv2.sip
  5. +9 −0 python/core/symbology-ng/qgssymbollayerv2.sip
  6. +6 −0 python/core/symbology-ng/qgssymbolv2.sip
  7. +1 −0 src/core/qgsmapsettings.h
  8. +1 −0 src/core/qgsrendercontext.cpp
  9. +1 −0 src/core/qgsrendercontext.h
  10. +131 −42 src/core/symbology-ng/qgsellipsesymbollayerv2.cpp
  11. +4 −0 src/core/symbology-ng/qgsellipsesymbollayerv2.h
  12. +425 −180 src/core/symbology-ng/qgsmarkersymbollayerv2.cpp
  13. +22 −0 src/core/symbology-ng/qgsmarkersymbollayerv2.h
  14. +7 −0 src/core/symbology-ng/qgsrendererv2.cpp
  15. +9 −0 src/core/symbology-ng/qgssymbollayerv2.h
  16. +15 −0 src/core/symbology-ng/qgssymbolv2.cpp
  17. +6 −0 src/core/symbology-ng/qgssymbolv2.h
  18. +18 −0 tests/src/core/testqgsellipsemarker.cpp
  19. +18 −0 tests/src/core/testqgsfontmarker.cpp
  20. +16 −0 tests/src/core/testqgssimplemarker.cpp
  21. +16 −0 tests/src/core/testqgssvgmarker.cpp
  22. BIN ...ntrol_images/symbol_ellipsemarker/expected_ellipsemarker_bounds/expected_ellipsemarker_bounds.png
  23. BIN tests/testdata/control_images/symbol_fontmarker/expected_fontmarker/expected_fontmarker_mask.png
  24. BIN ...stdata/control_images/symbol_fontmarker/expected_fontmarker_bounds/expected_fontmarker_bounds.png
  25. BIN ...a/control_images/symbol_fontmarker/expected_fontmarker_bounds/expected_fontmarker_bounds_mask.png
  26. BIN .../control_images/symbol_simplemarker/expected_simplemarker_bounds/expected_simplemarker_bounds.png
  27. BIN .../testdata/control_images/symbol_svgmarker/expected_svgmarker_bounds/expected_svgmarker_bounds.png
  28. BIN tests/testdata/qgis_server_accesscontrol/_helloworld.db
  29. +76 −0 tests/testdata/test_symbol_svg.svg
@@ -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;
@@ -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;

@@ -67,4 +67,6 @@ class QgsEllipseSymbolLayerV2 : QgsMarkerSymbolLayerV2

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

QRectF bounds( const QPointF& point, QgsSymbolV2RenderContext& context );
};
@@ -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 );

@@ -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
@@ -190,4 +194,7 @@ class QgsFontMarkerSymbolLayerV2 : QgsMarkerSymbolLayerV2

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

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

};
@@ -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 );

@@ -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/;
};

@@ -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 )
@@ -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() );
@@ -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 )

@@ -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
@@ -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
@@ -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
@@ -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 );
}
}

@@ -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
@@ -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;
@@ -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

0 comments on commit 4203a7c

Please sign in to comment.
You can’t perform that action at this time.