Skip to content

Commit

Permalink
[FEATURE][symbology] Add density-based point count for the random mar…
Browse files Browse the repository at this point in the history
…ker fill
  • Loading branch information
nirvn committed Oct 30, 2019
1 parent 25a07ec commit b2e7121
Show file tree
Hide file tree
Showing 11 changed files with 384 additions and 16 deletions.
80 changes: 79 additions & 1 deletion python/core/auto_generated/symbology/qgsfillsymbollayer.sip.in
Expand Up @@ -1925,7 +1925,13 @@ A fill symbol layer which places markers at random locations within polygons.
%End
public:

QgsRandomMarkerFillSymbolLayer( int pointCount = 10, unsigned long seed = 0 );
enum CountMethod
{
AbsoluteCount,
DensityBasedCount,
};

QgsRandomMarkerFillSymbolLayer( int pointCount = 10, CountMethod method = AbsoluteCount, double densityArea = 250.0, unsigned long seed = 0 );
%Docstring
Constructor for QgsRandomMarkerFillSymbolLayer, with the specified ``pointCount``.

Expand Down Expand Up @@ -2021,6 +2027,78 @@ Returns ``True`` if point markers should be clipped to the polygon boundary.
Sets whether point markers should be ``clipped`` to the polygon boundary.

.. seealso:: :py:func:`clipPoints`
%End

CountMethod countMethod() const;
%Docstring
Returns the count method used to randomly fill the polygon.

.. seealso:: :py:func:`setCountMethod`
%End

void setCountMethod( CountMethod method );
%Docstring
Sets the count ``method`` used to randomly fill the polygon.

.. seealso:: :py:func:`countMethod`
%End

double densityArea() const;
%Docstring
Returns the density area used to count the number of points to randomly fill the polygon.

Only used when the count method is set to QgsRandomMarkerFillSymbolLayer.DensityBasedCount.

Units are specified by setDensityAreaUnit().

.. seealso:: :py:func:`setDensityArea`
%End

void setDensityArea( double area );
%Docstring
Sets the density ``area`` used to count the number of points to randomly fill the polygon.

.. seealso:: :py:func:`densityArea`
%End

void setDensityAreaUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the units for the density area.

:param unit: width units

.. seealso:: :py:func:`densityAreaUnit`
%End

QgsUnitTypes::RenderUnit densityAreaUnit() const;
%Docstring
Returns the units for the density area.

.. seealso:: :py:func:`setDensityAreaUnit`
%End

void setDensityAreaUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map scale for the density area.

:param scale: density area map unit scale

.. seealso:: :py:func:`densityAreaUnitScale`

.. seealso:: :py:func:`setDensityArea`

.. seealso:: :py:func:`setDensityAreaUnit`
%End

const QgsMapUnitScale &densityAreaUnitScale() const;
%Docstring
Returns the map scale for the density area.

.. seealso:: :py:func:`setDensityAreaUnitScale`

.. seealso:: :py:func:`densityArea`

.. seealso:: :py:func:`densityAreaUnit`
%End

virtual void startFeatureRender( const QgsFeature &feature, QgsRenderContext &context );
Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/symbology/qgssymbollayer.sip.in
Expand Up @@ -140,6 +140,7 @@ class QgsSymbolLayer
PropertyPointCount,
PropertyRandomSeed,
PropertyClipPoints,
PropertyDensityArea,
};

static const QgsPropertiesDefinition &propertyDefinitions();
Expand Down
64 changes: 60 additions & 4 deletions src/core/symbology/qgsfillsymbollayer.cpp
Expand Up @@ -3918,16 +3918,20 @@ void QgsRasterFillSymbolLayer::applyPattern( QBrush &brush, const QString &image
// QgsRandomMarkerFillSymbolLayer
//

QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, unsigned long seed )
: mPointCount( pointCount )
QgsRandomMarkerFillSymbolLayer::QgsRandomMarkerFillSymbolLayer( int pointCount, CountMethod method, double densityArea, unsigned long seed )
: mCountMethod( method )
, mPointCount( pointCount )
, mDensityArea( densityArea )
, mSeed( seed )
{
setSubSymbol( new QgsMarkerSymbol() );
}

QgsSymbolLayer *QgsRandomMarkerFillSymbolLayer::create( const QgsStringMap &properties )
{
const CountMethod countMethod = static_cast< CountMethod >( properties.value( QStringLiteral( "count_method" ), QStringLiteral( "0" ) ).toInt() );
const int pointCount = properties.value( QStringLiteral( "point_count" ), QStringLiteral( "10" ) ).toInt();
const double densityArea = properties.value( QStringLiteral( "density_area" ), QStringLiteral( "250.0" ) ).toDouble();

unsigned long seed = 0;
if ( properties.contains( QStringLiteral( "seed" ) ) )
Expand All @@ -3942,7 +3946,12 @@ QgsSymbolLayer *QgsRandomMarkerFillSymbolLayer::create( const QgsStringMap &prop
seed = uniformDist( mt );
}

std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = qgis::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, seed );
std::unique_ptr< QgsRandomMarkerFillSymbolLayer > sl = qgis::make_unique< QgsRandomMarkerFillSymbolLayer >( pointCount, countMethod, densityArea, seed );

if ( properties.contains( QStringLiteral( "density_area_unit" ) ) )
sl->setDensityAreaUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "density_area_unit" )] ) );
if ( properties.contains( QStringLiteral( "density_area_unit_scale" ) ) )
sl->setDensityAreaUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "density_area_unit_scale" )] ) );

if ( properties.contains( QStringLiteral( "clip_points" ) ) )
{
Expand Down Expand Up @@ -4047,12 +4056,33 @@ void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QV
context.painter()->setClipPath( path );
}


int count = mPointCount;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyPointCount ) )
{
context.expressionContext().setOriginalValueVariable( count );
count = mDataDefinedProperties.valueAsInt( QgsSymbolLayer::PropertyPointCount, context.expressionContext(), count );
}

switch ( mCountMethod )
{
case DensityBasedCount:
{
double densityArea = mDensityArea;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyDensityArea ) )
{
context.expressionContext().setOriginalValueVariable( densityArea );
densityArea = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyPointCount, context.expressionContext(), densityArea );
}
densityArea = context.convertToPainterUnits( std::sqrt( densityArea ), mDensityAreaUnit, mDensityAreaUnitScale );
densityArea = std::pow( densityArea, 2 );
count = std::max( 0.0, std::ceil( count * ( geom.area() / densityArea ) ) );
break;
}
case AbsoluteCount:
break;
}

unsigned long seed = mSeed;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyRandomSeed ) )
{
Expand Down Expand Up @@ -4083,17 +4113,23 @@ void QgsRandomMarkerFillSymbolLayer::render( QgsRenderContext &context, const QV
QgsStringMap QgsRandomMarkerFillSymbolLayer::properties() const
{
QgsStringMap map;
map.insert( QStringLiteral( "count_method" ), QString::number( static_cast< int >( mCountMethod ) ) );
map.insert( QStringLiteral( "point_count" ), QString::number( mPointCount ) );
map.insert( QStringLiteral( "density_area" ), QString::number( mDensityArea ) );
map.insert( QStringLiteral( "density_area_unit" ), QgsUnitTypes::encodeUnit( mDensityAreaUnit ) );
map.insert( QStringLiteral( "density_area_unit_scale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mDensityAreaUnitScale ) );
map.insert( QStringLiteral( "seed" ), QString::number( mSeed ) );
map.insert( QStringLiteral( "clip_points" ), QString::number( mClipPoints ) );
return map;
}

QgsRandomMarkerFillSymbolLayer *QgsRandomMarkerFillSymbolLayer::clone() const
{
std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = qgis::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mSeed );
std::unique_ptr< QgsRandomMarkerFillSymbolLayer > res = qgis::make_unique< QgsRandomMarkerFillSymbolLayer >( mPointCount, mCountMethod, mDensityArea, mSeed );
res->mAngle = mAngle;
res->mColor = mColor;
res->setDensityAreaUnit( mDensityAreaUnit );
res->setDensityAreaUnitScale( mDensityAreaUnitScale );
res->mClipPoints = mClipPoints;
res->setSubSymbol( mMarker->clone() );
copyDataDefinedProperties( res.get() );
Expand Down Expand Up @@ -4168,6 +4204,26 @@ void QgsRandomMarkerFillSymbolLayer::setClipPoints( bool clipPoints )
mClipPoints = clipPoints;
}

QgsRandomMarkerFillSymbolLayer::CountMethod QgsRandomMarkerFillSymbolLayer::countMethod() const
{
return mCountMethod;
}

void QgsRandomMarkerFillSymbolLayer::setCountMethod( CountMethod method )
{
mCountMethod = method;
}

double QgsRandomMarkerFillSymbolLayer::densityArea() const
{
return mDensityArea;
}

void QgsRandomMarkerFillSymbolLayer::setDensityArea( double area )
{
mDensityArea = area;
}

void QgsRandomMarkerFillSymbolLayer::startFeatureRender( const QgsFeature &, QgsRenderContext & )
{
mRenderingFeature = true;
Expand Down
75 changes: 74 additions & 1 deletion src/core/symbology/qgsfillsymbollayer.h
Expand Up @@ -1734,13 +1734,20 @@ class CORE_EXPORT QgsRandomMarkerFillSymbolLayer : public QgsFillSymbolLayer
{
public:

//! Methods to define the number of points randomly filling the polygon
enum CountMethod
{
AbsoluteCount, //!< The point count is used as an absolute count of markers
DensityBasedCount, //!< The point count is part of a marker density count
};

/**
* Constructor for QgsRandomMarkerFillSymbolLayer, with the specified \a pointCount.
*
* Optionally a specific random number \a seed can be used when generating points. A \a seed of 0 indicates that
* a truly random sequence will be used on every rendering, causing points to appear in different locations with every map refresh.
*/
QgsRandomMarkerFillSymbolLayer( int pointCount = 10, unsigned long seed = 0 );
QgsRandomMarkerFillSymbolLayer( int pointCount = 10, CountMethod method = AbsoluteCount, double densityArea = 250.0, unsigned long seed = 0 );

/**
* Creates a new QgsRandomMarkerFillSymbolLayer using the specified \a properties map containing symbol properties (see properties()).
Expand Down Expand Up @@ -1815,6 +1822,68 @@ class CORE_EXPORT QgsRandomMarkerFillSymbolLayer : public QgsFillSymbolLayer
*/
void setClipPoints( bool clipped );

/**
* Returns the count method used to randomly fill the polygon.
*
* \see setCountMethod()
*/
CountMethod countMethod() const;

/**
* Sets the count \a method used to randomly fill the polygon.
*
* \see countMethod()
*/
void setCountMethod( CountMethod method );

/**
* Returns the density area used to count the number of points to randomly fill the polygon.
*
* Only used when the count method is set to QgsRandomMarkerFillSymbolLayer::DensityBasedCount.
*
* Units are specified by setDensityAreaUnit().
*
* \see setDensityArea()
*/
double densityArea() const;

/**
* Sets the density \a area used to count the number of points to randomly fill the polygon.
*
* \see densityArea()
*/
void setDensityArea( double area );

/**
* Sets the units for the density area.
* \param unit width units
* \see densityAreaUnit()
*/
void setDensityAreaUnit( QgsUnitTypes::RenderUnit unit ) { mDensityAreaUnit = unit; }

/**
* Returns the units for the density area.
* \see setDensityAreaUnit()
*/
QgsUnitTypes::RenderUnit densityAreaUnit() const { return mDensityAreaUnit; }

/**
* Sets the map scale for the density area.
* \param scale density area map unit scale
* \see densityAreaUnitScale()
* \see setDensityArea()
* \see setDensityAreaUnit()
*/
void setDensityAreaUnitScale( const QgsMapUnitScale &scale ) { mDensityAreaUnitScale = scale; }

/**
* Returns the map scale for the density area.
* \see setDensityAreaUnitScale()
* \see densityArea()
* \see densityAreaUnit()
*/
const QgsMapUnitScale &densityAreaUnitScale() const { return mDensityAreaUnitScale; }

void startFeatureRender( const QgsFeature &feature, QgsRenderContext &context ) override;
void stopFeatureRender( const QgsFeature &feature, QgsRenderContext &context ) override;

Expand All @@ -1834,7 +1903,11 @@ class CORE_EXPORT QgsRandomMarkerFillSymbolLayer : public QgsFillSymbolLayer
void render( QgsRenderContext &context, const QVector< Part > &parts, const QgsFeature &feature, bool selected );

std::unique_ptr< QgsMarkerSymbol > mMarker;
CountMethod mCountMethod = AbsoluteCount;
int mPointCount = 10;
double mDensityArea = 250.0;
QgsUnitTypes::RenderUnit mDensityAreaUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mDensityAreaUnitScale;
unsigned long mSeed = 0;
bool mClipPoints = false;

Expand Down
1 change: 1 addition & 0 deletions src/core/symbology/qgssymbollayer.cpp
Expand Up @@ -98,6 +98,7 @@ void QgsSymbolLayer::initPropertyDefinitions()
{ QgsSymbolLayer::PropertyPointCount, QgsPropertyDefinition( "pointCount", QObject::tr( "Point count" ), QgsPropertyDefinition::IntegerPositive, origin )},
{ QgsSymbolLayer::PropertyRandomSeed, QgsPropertyDefinition( "randomSeed", QgsPropertyDefinition::DataTypeNumeric, QObject::tr( "Random number seed" ), QObject::tr( "integer > 0, or 0 for completely random sequence" ), origin )},
{ QgsSymbolLayer::PropertyClipPoints, QgsPropertyDefinition( "clipPoints", QObject::tr( "Clip markers" ), QgsPropertyDefinition::Boolean, origin )},
{ QgsSymbolLayer::PropertyClipPoints, QgsPropertyDefinition( "densityArea", QObject::tr( "Density area" ), QgsPropertyDefinition::DoublePositive, origin )},
};
}

Expand Down
1 change: 1 addition & 0 deletions src/core/symbology/qgssymbollayer.h
Expand Up @@ -182,6 +182,7 @@ class CORE_EXPORT QgsSymbolLayer
PropertyPointCount, //!< Point count
PropertyRandomSeed, //!< Random number seed
PropertyClipPoints, //!< Whether markers should be clipped to polygon boundaries
PropertyDensityArea, //<! Density area
};

/**
Expand Down

0 comments on commit b2e7121

Please sign in to comment.