Skip to content

Commit

Permalink
Add "octagon", "asterisk" and "square with corners" shapes to simple …
Browse files Browse the repository at this point in the history
…marker

available shapes
  • Loading branch information
nyalldawson committed Feb 11, 2021
1 parent a81ba3c commit c582967
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 1 deletion.
Expand Up @@ -49,6 +49,9 @@ leaves the actual drawing of the symbols to subclasses.
DiagonalHalfSquare,
RightHalfTriangle,
LeftHalfTriangle,
Octagon,
SquareWithCorners,
AsteriskFill,
};

static QList< QgsSimpleMarkerSymbolLayerBase::Shape > availableShapes();
Expand Down
92 changes: 91 additions & 1 deletion src/core/symbology/qgsmarkersymbollayer.cpp
Expand Up @@ -64,6 +64,8 @@ QList<QgsSimpleMarkerSymbolLayerBase::Shape> QgsSimpleMarkerSymbolLayerBase::ava
<< Diamond
<< Pentagon
<< Hexagon
<< Octagon
<< SquareWithCorners
<< Triangle
<< EquilateralTriangle
<< Star
Expand All @@ -82,7 +84,9 @@ QList<QgsSimpleMarkerSymbolLayerBase::Shape> QgsSimpleMarkerSymbolLayerBase::ava
<< HalfSquare
<< DiagonalHalfSquare
<< RightHalfTriangle
<< LeftHalfTriangle;
<< LeftHalfTriangle
<< AsteriskFill;

return shapes;
}

Expand All @@ -105,6 +109,8 @@ bool QgsSimpleMarkerSymbolLayerBase::shapeIsFilled( QgsSimpleMarkerSymbolLayerBa
case Diamond:
case Pentagon:
case Hexagon:
case Octagon:
case SquareWithCorners:
case Triangle:
case EquilateralTriangle:
case Star:
Expand All @@ -120,6 +126,7 @@ bool QgsSimpleMarkerSymbolLayerBase::shapeIsFilled( QgsSimpleMarkerSymbolLayerBa
case DiagonalHalfSquare:
case RightHalfTriangle:
case LeftHalfTriangle:
case AsteriskFill:
return true;

case Cross:
Expand Down Expand Up @@ -299,12 +306,16 @@ QgsSimpleMarkerSymbolLayerBase::Shape QgsSimpleMarkerSymbolLayerBase::decodeShap

if ( cleaned == QLatin1String( "square" ) || cleaned == QLatin1String( "rectangle" ) )
return Square;
else if ( cleaned == QLatin1String( "square_with_corners" ) )
return SquareWithCorners;
else if ( cleaned == QLatin1String( "diamond" ) )
return Diamond;
else if ( cleaned == QLatin1String( "pentagon" ) )
return Pentagon;
else if ( cleaned == QLatin1String( "hexagon" ) )
return Hexagon;
else if ( cleaned == QLatin1String( "octagon" ) )
return Octagon;
else if ( cleaned == QLatin1String( "triangle" ) )
return Triangle;
else if ( cleaned == QLatin1String( "equilateral_triangle" ) )
Expand Down Expand Up @@ -343,6 +354,8 @@ QgsSimpleMarkerSymbolLayerBase::Shape QgsSimpleMarkerSymbolLayerBase::decodeShap
return RightHalfTriangle;
else if ( cleaned == QLatin1String( "left_half_triangle" ) )
return LeftHalfTriangle;
else if ( cleaned == QLatin1String( "asterisk_fill" ) )
return AsteriskFill;

if ( ok )
*ok = false;
Expand All @@ -367,6 +380,10 @@ QString QgsSimpleMarkerSymbolLayerBase::encodeShape( QgsSimpleMarkerSymbolLayerB
return QStringLiteral( "pentagon" );
case Hexagon:
return QStringLiteral( "hexagon" );
case Octagon:
return QStringLiteral( "octagon" );
case SquareWithCorners:
return QStringLiteral( "square_with_corners" );
case Triangle:
return QStringLiteral( "triangle" );
case EquilateralTriangle:
Expand Down Expand Up @@ -399,6 +416,8 @@ QString QgsSimpleMarkerSymbolLayerBase::encodeShape( QgsSimpleMarkerSymbolLayerB
return QStringLiteral( "third_circle" );
case QuarterCircle:
return QStringLiteral( "quarter_circle" );
case AsteriskFill:
return QStringLiteral( "asterisk_fill" );
}
return QString();
}
Expand All @@ -418,6 +437,22 @@ bool QgsSimpleMarkerSymbolLayerBase::shapeToPolygon( QgsSimpleMarkerSymbolLayerB
polygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 1, 1 ) ) );
return true;

case SquareWithCorners:
{
static constexpr double VERTEX_OFFSET_FROM_ORIGIN = 0.6072;

polygon << QPointF( - VERTEX_OFFSET_FROM_ORIGIN, 1 )
<< QPointF( VERTEX_OFFSET_FROM_ORIGIN, 1 )
<< QPointF( 1, VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( 1, -VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( VERTEX_OFFSET_FROM_ORIGIN, -1 )
<< QPointF( -VERTEX_OFFSET_FROM_ORIGIN, -1 )
<< QPointF( -1, -VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( -1, VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( -VERTEX_OFFSET_FROM_ORIGIN, 1 );
return true;
}

case QuarterSquare:
polygon = QPolygonF( QRectF( QPointF( -1, -1 ), QPointF( 0, 0 ) ) );
return true;
Expand Down Expand Up @@ -467,6 +502,22 @@ bool QgsSimpleMarkerSymbolLayerBase::shapeToPolygon( QgsSimpleMarkerSymbolLayerB
<< QPointF( -0.8660, -0.5 );
return true;

case Octagon:
{
static constexpr double VERTEX_OFFSET_FROM_ORIGIN = 1.0 / ( 1 + M_SQRT2 );

polygon << QPointF( - VERTEX_OFFSET_FROM_ORIGIN, 1 )
<< QPointF( VERTEX_OFFSET_FROM_ORIGIN, 1 )
<< QPointF( 1, VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( 1, -VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( VERTEX_OFFSET_FROM_ORIGIN, -1 )
<< QPointF( -VERTEX_OFFSET_FROM_ORIGIN, -1 )
<< QPointF( -1, -VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( -1, VERTEX_OFFSET_FROM_ORIGIN )
<< QPointF( -VERTEX_OFFSET_FROM_ORIGIN, 1 );
return true;
}

case Triangle:
polygon << QPointF( -1, 1 ) << QPointF( 1, 1 ) << QPointF( 0, -1 ) << QPointF( -1, 1 );
return true;
Expand Down Expand Up @@ -540,6 +591,42 @@ bool QgsSimpleMarkerSymbolLayerBase::shapeToPolygon( QgsSimpleMarkerSymbolLayerB
<< QPointF( -1, -0.2 );
return true;

case AsteriskFill:
{
static constexpr double THICKNESS = 0.3;
static constexpr double HALF_THICKNESS = THICKNESS / 2.0;
static constexpr double INTERSECTION_POINT = THICKNESS / M_SQRT2;
static constexpr double DIAGONAL1 = M_SQRT1_2 - INTERSECTION_POINT * 0.5;
static constexpr double DIAGONAL2 = M_SQRT1_2 + INTERSECTION_POINT * 0.5;

polygon << QPointF( -HALF_THICKNESS, -1 )
<< QPointF( HALF_THICKNESS, -1 )
<< QPointF( HALF_THICKNESS, -HALF_THICKNESS - INTERSECTION_POINT )
<< QPointF( DIAGONAL1, -DIAGONAL2 )
<< QPointF( DIAGONAL2, -DIAGONAL1 )
<< QPointF( HALF_THICKNESS + INTERSECTION_POINT, -HALF_THICKNESS )
<< QPointF( 1, -HALF_THICKNESS )
<< QPointF( 1, HALF_THICKNESS )
<< QPointF( HALF_THICKNESS + INTERSECTION_POINT, HALF_THICKNESS )
<< QPointF( DIAGONAL2, DIAGONAL1 )
<< QPointF( DIAGONAL1, DIAGONAL2 )
<< QPointF( HALF_THICKNESS, HALF_THICKNESS + INTERSECTION_POINT )
<< QPointF( HALF_THICKNESS, 1 )
<< QPointF( -HALF_THICKNESS, 1 )
<< QPointF( -HALF_THICKNESS, HALF_THICKNESS + INTERSECTION_POINT )
<< QPointF( -DIAGONAL1, DIAGONAL2 )
<< QPointF( -DIAGONAL2, DIAGONAL1 )
<< QPointF( -HALF_THICKNESS - INTERSECTION_POINT, HALF_THICKNESS )
<< QPointF( -1, HALF_THICKNESS )
<< QPointF( -1, -HALF_THICKNESS )
<< QPointF( -HALF_THICKNESS - INTERSECTION_POINT, -HALF_THICKNESS )
<< QPointF( -DIAGONAL2, -DIAGONAL1 )
<< QPointF( -DIAGONAL1, -DIAGONAL2 )
<< QPointF( -HALF_THICKNESS, -HALF_THICKNESS - INTERSECTION_POINT )
<< QPointF( -HALF_THICKNESS, -1 );
return true;
}

case Circle:
case Cross:
case Cross2:
Expand Down Expand Up @@ -606,12 +693,14 @@ bool QgsSimpleMarkerSymbolLayerBase::prepareMarkerPath( QgsSimpleMarkerSymbolLay
return true;

case Square:
case SquareWithCorners:
case QuarterSquare:
case HalfSquare:
case DiagonalHalfSquare:
case Diamond:
case Pentagon:
case Hexagon:
case Octagon:
case Triangle:
case EquilateralTriangle:
case LeftHalfTriangle:
Expand All @@ -620,6 +709,7 @@ bool QgsSimpleMarkerSymbolLayerBase::prepareMarkerPath( QgsSimpleMarkerSymbolLay
case Arrow:
case ArrowHeadFilled:
case CrossFill:
case AsteriskFill:
return false;
}
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/core/symbology/qgsmarkersymbollayer.h
Expand Up @@ -42,6 +42,7 @@
*/
class CORE_EXPORT QgsSimpleMarkerSymbolLayerBase : public QgsMarkerSymbolLayer
{

public:

//! Marker symbol shapes
Expand Down Expand Up @@ -70,6 +71,9 @@ class CORE_EXPORT QgsSimpleMarkerSymbolLayerBase : public QgsMarkerSymbolLayer
DiagonalHalfSquare, //!< Diagonal half square (bottom left half)
RightHalfTriangle, //!< Right half of triangle
LeftHalfTriangle, //!< Left half of triangle
Octagon, //!< Octagon (since QGIS 3.18)
SquareWithCorners, //!< A square with diagonal corners (since QGIS 3.18)
AsteriskFill, //!< A filled asterisk shape (since QGIS 3.18)
};

//! Returns a list of all available shape types.
Expand Down
99 changes: 99 additions & 0 deletions tests/src/core/testqgssimplemarker.cpp
Expand Up @@ -69,13 +69,18 @@ class TestQgsSimpleMarkerSymbol : public QObject
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.

void decodeShape_data();
void decodeShape();
void simpleMarkerSymbol();
void simpleMarkerSymbolRotation();
void simpleMarkerSymbolPreviewRotation();
void simpleMarkerSymbolPreviewRotation_data();
void simpleMarkerSymbolBevelJoin();
void simpleMarkerSymbolMiterJoin();
void simpleMarkerSymbolRoundJoin();
void simpleMarkerOctagon();
void simpleMarkerSquareWithCorners();
void simpleMarkerAsterisk();
void bounds();
void boundsWithOffset();
void boundsWithRotation();
Expand Down Expand Up @@ -151,6 +156,61 @@ void TestQgsSimpleMarkerSymbol::cleanupTestCase()
QgsApplication::exitQgis();
}

void TestQgsSimpleMarkerSymbol::decodeShape_data()
{
QTest::addColumn<QString>( "string" );
QTest::addColumn<int>( "shape" );
QTest::addColumn<bool>( "ok" );

QTest::newRow( "empty string" ) << "" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Circle ) << false;
QTest::newRow( "invalid character" ) << "@" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Circle ) << false;
QTest::newRow( "square" ) << "square" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Square ) << true;
QTest::newRow( "square case" ) << "SQUARE" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Square ) << true;
QTest::newRow( "square case spaces" ) << " SQUARE " << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Square ) << true;
QTest::newRow( "square_with_corners" ) << "square_with_corners" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::SquareWithCorners ) << true;
QTest::newRow( "rectangle" ) << "rectangle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Square ) << true;
QTest::newRow( "diamond" ) << "diamond" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Diamond ) << true;
QTest::newRow( "pentagon" ) << "pentagon" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Pentagon ) << true;
QTest::newRow( "hexagon" ) << "hexagon" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Hexagon ) << true;
QTest::newRow( "octagon" ) << "octagon" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Octagon ) << true;
QTest::newRow( "triangle" ) << "triangle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Triangle ) << true;
QTest::newRow( "equilateral_triangle" ) << "equilateral_triangle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::EquilateralTriangle ) << true;
QTest::newRow( "star" ) << "star" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Star ) << true;
QTest::newRow( "regular_star" ) << "regular_star" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Star ) << true;
QTest::newRow( "arrow" ) << "arrow" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Arrow ) << true;
QTest::newRow( "circle" ) << "circle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Circle ) << true;
QTest::newRow( "cross" ) << "cross" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Cross ) << true;
QTest::newRow( "cross_fill" ) << "cross_fill" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::CrossFill ) << true;
QTest::newRow( "cross2" ) << "cross2" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Cross2 ) << true;
QTest::newRow( "x" ) << "x" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Cross2 ) << true;
QTest::newRow( "line" ) << "line" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::Line ) << true;
QTest::newRow( "arrowhead" ) << "arrowhead" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::ArrowHead ) << true;
QTest::newRow( "filled_arrowhead" ) << "filled_arrowhead" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::ArrowHeadFilled ) << true;
QTest::newRow( "semi_circle" ) << "semi_circle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::SemiCircle ) << true;
QTest::newRow( "third_circle" ) << "third_circle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::ThirdCircle ) << true;
QTest::newRow( "quarter_circle" ) << "quarter_circle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::QuarterCircle ) << true;
QTest::newRow( "quarter_square" ) << "quarter_square" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::QuarterSquare ) << true;
QTest::newRow( "half_square" ) << "half_square" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::HalfSquare ) << true;
QTest::newRow( "diagonal_half_square" ) << "diagonal_half_square" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::DiagonalHalfSquare ) << true;
QTest::newRow( "right_half_triangle" ) << "right_half_triangle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::RightHalfTriangle ) << true;
QTest::newRow( "left_half_triangle" ) << "left_half_triangle" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::LeftHalfTriangle ) << true;
QTest::newRow( "asterisk_fill" ) << "asterisk_fill" << static_cast< int >( QgsSimpleMarkerSymbolLayerBase::AsteriskFill ) << true;
}

void TestQgsSimpleMarkerSymbol::decodeShape()
{
QFETCH( QString, string );
QFETCH( int, shape );
QFETCH( bool, ok );

bool res = false;
QCOMPARE( static_cast< int >( QgsSimpleMarkerSymbolLayerBase::decodeShape( string, &res ) ), shape );
QCOMPARE( res, ok );

// round trip through encode
QCOMPARE( static_cast< int >( QgsSimpleMarkerSymbolLayerBase::decodeShape( QgsSimpleMarkerSymbolLayerBase::encodeShape( static_cast< QgsSimpleMarkerSymbolLayerBase::Shape>( shape ) ) ) ), shape );
}

void TestQgsSimpleMarkerSymbol::simpleMarkerSymbol()
{
mReport += QLatin1String( "<h2>Simple marker symbol layer test</h2>\n" );
Expand Down Expand Up @@ -248,6 +308,45 @@ void TestQgsSimpleMarkerSymbol::simpleMarkerSymbolRoundJoin()
QVERIFY( imageCheck( "simplemarker_roundjoin" ) );
}

void TestQgsSimpleMarkerSymbol::simpleMarkerOctagon()
{
mReport += QLatin1String( "<h2>Simple marker octagon</h2>\n" );

mSimpleMarkerLayer->setColor( Qt::blue );
mSimpleMarkerLayer->setStrokeColor( Qt::black );
mSimpleMarkerLayer->setShape( QgsSimpleMarkerSymbolLayerBase::Octagon );
mSimpleMarkerLayer->setSize( 25 );
mSimpleMarkerLayer->setStrokeWidth( 2 );
mSimpleMarkerLayer->setPenJoinStyle( Qt::MiterJoin );
QVERIFY( imageCheck( "simplemarker_octagon" ) );
}

void TestQgsSimpleMarkerSymbol::simpleMarkerSquareWithCorners()
{
mReport += QLatin1String( "<h2>Simple marker square with corners</h2>\n" );

mSimpleMarkerLayer->setColor( Qt::blue );
mSimpleMarkerLayer->setStrokeColor( Qt::black );
mSimpleMarkerLayer->setShape( QgsSimpleMarkerSymbolLayerBase::SquareWithCorners );
mSimpleMarkerLayer->setSize( 25 );
mSimpleMarkerLayer->setStrokeWidth( 2 );
mSimpleMarkerLayer->setPenJoinStyle( Qt::MiterJoin );
QVERIFY( imageCheck( "simplemarker_square_with_corners" ) );
}

void TestQgsSimpleMarkerSymbol::simpleMarkerAsterisk()
{
mReport += QLatin1String( "<h2>Simple marker asterisk</h2>\n" );

mSimpleMarkerLayer->setColor( Qt::blue );
mSimpleMarkerLayer->setStrokeColor( Qt::black );
mSimpleMarkerLayer->setShape( QgsSimpleMarkerSymbolLayerBase::AsteriskFill );
mSimpleMarkerLayer->setSize( 25 );
mSimpleMarkerLayer->setStrokeWidth( 2 );
mSimpleMarkerLayer->setPenJoinStyle( Qt::MiterJoin );
QVERIFY( imageCheck( "simplemarker_asterisk" ) );
}

void TestQgsSimpleMarkerSymbol::bounds()
{
mSimpleMarkerLayer->setColor( QColor( 200, 200, 200 ) );
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit c582967

Please sign in to comment.