[FEATURE] Average line angles for marker and hashed line symbology

Previously, when marker or hash lines were rendered using interval
or center point place placement, the symbol angles were determined
by taking the exact line orientation at the position of the symbol.

This often leads to undesirable rendering effects, where little
jaggies or corners in lines which occur at the position of the
symbol cause the marker or hash line to be oriented at a very
different angle to what the eye expects to see.

With this new option, the angle is instead calculated by averaging
the line over a specified distance either side of the symbol. E.g.
averaging the line angle over 4mm means we take the points along
the line 2mm from either side of the symbol placement, and use these
instead to calculate the line angle for that symbol. This has the
effect of smoothing (or removing) any tiny local deviations from
the overall line direction, resulting in much nicer visual
orientation of marker or hash lines.

Like all symbol settings, the average angle smoothing distance
can be set using mm/pixels/map units/etc, and supports data-defined
values.

Closed rings also correctly consider wrapping around these average
angles from the start/end vertex.

(Sponsored by an anonymous corporate backer)
This commit is contained in:
Nyall Dawson 2019-03-25 17:58:47 +10:00
parent 4d7f0c2f50
commit 426382c919
25 changed files with 1257 additions and 252 deletions

View File

@ -425,6 +425,82 @@ Returns the map unit scale used for calculating the offset in map units along li
Sets the map unit ``scale`` used for calculating the offset in map units along line for symbols.
.. seealso:: :py:func:`offsetAlongLineMapUnitScale`
%End
double averageAngleLength() const;
%Docstring
Returns the length of line over which the line's direction is averaged when
calculating individual symbol angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are retrieved through averageAngleUnit()
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleLength( double length );
%Docstring
Sets the ``length`` of line over which the line's direction is averaged when
calculating individual symbol angles. Longer lengths smooth out angles from jagged lines to a greater extent.
Units are set through setAverageAngleUnit()
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
void setAverageAngleUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the ``unit`` for the length over which the line's direction is averaged when
calculating individual symbol angles.
.. seealso:: :py:func:`averageAngleUnit`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
%End
QgsUnitTypes::RenderUnit averageAngleUnit() const;
%Docstring
Returns the unit for the length over which the line's direction is averaged when
calculating individual symbol angles.
.. seealso:: :py:func:`setAverageAngleUnit`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleMapUnitScale`
%End
void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the length over which the line's direction is averaged when
calculating individual symbol angles.
.. seealso:: :py:func:`averageAngleMapUnitScale`
.. seealso:: :py:func:`setAverageAngleLength`
.. seealso:: :py:func:`setAverageAngleUnit`
%End
const QgsMapUnitScale &averageAngleMapUnitScale() const;
%Docstring
Returns the map unit scale for the length over which the line's direction is averaged when
calculating individual symbol angles.
.. seealso:: :py:func:`setAverageAngleMapUnitScale`
.. seealso:: :py:func:`averageAngleLength`
.. seealso:: :py:func:`averageAngleUnit`
%End
virtual void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) ${SIP_FINAL};

View File

@ -123,6 +123,7 @@ class QgsSymbolLayer
PropertyPlacement,
PropertyInterval,
PropertyOffsetAlongLine,
PropertyAverageAngleLength,
PropertyHorizontalAnchor,
PropertyVerticalAnchor,
PropertyLayerEnabled,

View File

@ -799,16 +799,24 @@ void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, Q
context.renderContext().painter()->save();
double averageOver = mAverageAngleLength;
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyAverageAngleLength ) )
{
context.setOriginalValueVariable( mAverageAngleLength );
averageOver = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyAverageAngleLength, context.renderContext().expressionContext(), mAverageAngleLength );
}
averageOver = context.renderContext().convertToPainterUnits( averageOver, mAverageAngleLengthUnit, mAverageAngleLengthMapUnitScale ) / 2.0;
if ( qgsDoubleNear( offset, 0.0 ) )
{
switch ( placement )
{
case Interval:
renderPolylineInterval( points, context );
renderPolylineInterval( points, context, averageOver );
break;
case CentralPoint:
renderPolylineCentral( points, context );
renderPolylineCentral( points, context, averageOver );
break;
case Vertex:
@ -831,11 +839,11 @@ void QgsTemplatedLineSymbolLayerBase::renderPolyline( const QPolygonF &points, Q
switch ( placement )
{
case Interval:
renderPolylineInterval( points2, context );
renderPolylineInterval( points2, context, averageOver );
break;
case CentralPoint:
renderPolylineCentral( points2, context );
renderPolylineCentral( points2, context, averageOver );
break;
case Vertex:
@ -937,6 +945,10 @@ QgsStringMap QgsTemplatedLineSymbolLayerBase::properties() const
map[QStringLiteral( "offset_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetMapUnitScale );
map[QStringLiteral( "interval_unit" )] = QgsUnitTypes::encodeUnit( intervalUnit() );
map[QStringLiteral( "interval_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( intervalMapUnitScale() );
map[QStringLiteral( "average_angle_length" )] = QString::number( mAverageAngleLength );
map[QStringLiteral( "average_angle_unit" )] = QgsUnitTypes::encodeUnit( mAverageAngleLengthUnit );
map[QStringLiteral( "average_angle_map_unit_scale" )] = QgsSymbolLayerUtils::encodeMapUnitScale( mAverageAngleLengthMapUnitScale );
switch ( mPlacement )
{
case Vertex:
@ -975,6 +987,9 @@ void QgsTemplatedLineSymbolLayerBase::copyTemplateSymbolProperties( QgsTemplated
destLayer->setOffsetAlongLine( offsetAlongLine() );
destLayer->setOffsetAlongLineMapUnitScale( offsetAlongLineMapUnitScale() );
destLayer->setOffsetAlongLineUnit( offsetAlongLineUnit() );
destLayer->setAverageAngleLength( mAverageAngleLength );
destLayer->setAverageAngleUnit( mAverageAngleLengthUnit );
destLayer->setAverageAngleMapUnitScale( mAverageAngleLengthMapUnitScale );
destLayer->setRingFilter( mRingFilter );
copyDataDefinedProperties( destLayer );
copyPaintEffect( destLayer );
@ -1016,6 +1031,19 @@ void QgsTemplatedLineSymbolLayerBase::setCommonProperties( QgsTemplatedLineSymbo
destLayer->setIntervalMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "interval_map_unit_scale" )] ) );
}
if ( properties.contains( QStringLiteral( "average_angle_length" ) ) )
{
destLayer->setAverageAngleLength( properties[QStringLiteral( "average_angle_length" )].toDouble() );
}
if ( properties.contains( QStringLiteral( "average_angle_unit" ) ) )
{
destLayer->setAverageAngleUnit( QgsUnitTypes::decodeRenderUnit( properties[QStringLiteral( "average_angle_unit" )] ) );
}
if ( properties.contains( ( QStringLiteral( "average_angle_map_unit_scale" ) ) ) )
{
destLayer->setAverageAngleMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( properties[QStringLiteral( "average_angle_map_unit_scale" )] ) );
}
if ( properties.contains( QStringLiteral( "placement" ) ) )
{
if ( properties[QStringLiteral( "placement" )] == QLatin1String( "vertex" ) )
@ -1040,12 +1068,11 @@ void QgsTemplatedLineSymbolLayerBase::setCommonProperties( QgsTemplatedLineSymbo
destLayer->restoreOldDataDefinedProperties( properties );
}
void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context )
void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageOver )
{
if ( points.isEmpty() )
return;
QPointF lastPt = points[0];
double lengthLeft = 0; // how much is left until next marker
QgsRenderContext &rc = context.renderContext();
@ -1070,45 +1097,106 @@ void QgsTemplatedLineSymbolLayerBase::renderPolylineInterval( const QPolygonF &p
offsetAlongLine = mDataDefinedProperties.valueAsDouble( QgsSymbolLayer::PropertyOffsetAlongLine, context.renderContext().expressionContext(), mOffsetAlongLine );
}
double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
lengthLeft = painterUnitInterval - rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() );
const double painterUnitInterval = rc.convertToPainterUnits( interval, intervalUnit(), intervalMapUnitScale() );
int pointNum = 0;
for ( int i = 1; i < points.count(); ++i )
if ( painterUnitInterval < 0 )
return;
const double painterUnitOffsetAlongLine = rc.convertToPainterUnits( offsetAlongLine, offsetAlongLineUnit(), offsetAlongLineMapUnitScale() );
lengthLeft = painterUnitInterval - painterUnitOffsetAlongLine;
if ( averageOver > 0 && !qgsDoubleNear( averageOver, 0.0 ) )
{
const QPointF &pt = points[i];
QVector< QPointF > angleStartPoints;
QVector< QPointF > symbolPoints;
QVector< QPointF > angleEndPoints;
if ( lastPt == pt ) // must not be equal!
continue;
// we collect 3 arrays of points. These correspond to
// 1. the actual point at which to render the symbol
// 2. the start point of a line averaging the angle over the desired distance (i.e. -averageOver distance from the points in array 1)
// 3. the end point of a line averaging the angle over the desired distance (i.e. +averageOver distance from the points in array 2)
// it gets quite tricky, because for closed rings we need to trace backwards from the initial point to calculate this
// (or trace past the final point)
collectOffsetPoints( points, symbolPoints, painterUnitInterval, lengthLeft );
// for each line, find out dx and dy, and length
MyLine l( lastPt, pt );
QPointF diff = l.diffForInterval( painterUnitInterval );
// if there's some length left from previous line
// use only the rest for the first point in new line segment
double c = 1 - lengthLeft / painterUnitInterval;
lengthLeft += l.length();
// rotate marker (if desired)
if ( rotateSymbols() )
if ( symbolPoints.constFirst() == symbolPoints.constLast() )
{
setSymbolLineAngle( l.angle() * 180 / M_PI );
// avoid duplicate points at start and end of closed rings
symbolPoints.pop_back();
}
// while we're not at the end of line segment, draw!
while ( lengthLeft > painterUnitInterval )
angleEndPoints.reserve( symbolPoints.size() );
angleStartPoints.reserve( symbolPoints.size() );
if ( averageOver <= painterUnitOffsetAlongLine )
{
// "c" is 1 for regular point or in interval (0,1] for begin of line segment
lastPt += c * diff;
lengthLeft -= painterUnitInterval;
collectOffsetPoints( points, angleStartPoints, painterUnitInterval, lengthLeft + averageOver, 0, symbolPoints.size() );
}
else
{
collectOffsetPoints( points, angleStartPoints, painterUnitInterval, 0, averageOver - painterUnitOffsetAlongLine, symbolPoints.size() );
}
collectOffsetPoints( points, angleEndPoints, painterUnitInterval, lengthLeft - averageOver, 0, symbolPoints.size() );
int pointNum = 0;
for ( int i = 0; i < symbolPoints.size(); ++ i )
{
const QPointF pt = symbolPoints[i];
const QPointF startPt = angleStartPoints[i];
const QPointF endPt = angleEndPoints[i];
MyLine l( startPt, endPt );
// rotate marker (if desired)
if ( rotateSymbols() )
{
setSymbolLineAngle( l.angle() * 180 / M_PI );
}
scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
c = 1; // reset c (if wasn't 1 already)
renderSymbol( pt, context.feature(), rc, -1, context.selected() );
}
}
else
{
// not averaging line angle -- always use exact section angle
int pointNum = 0;
QPointF lastPt = points[0];
for ( int i = 1; i < points.count(); ++i )
{
const QPointF &pt = points[i];
if ( lastPt == pt ) // must not be equal!
continue;
// for each line, find out dx and dy, and length
MyLine l( lastPt, pt );
QPointF diff = l.diffForInterval( painterUnitInterval );
// if there's some length left from previous line
// use only the rest for the first point in new line segment
double c = 1 - lengthLeft / painterUnitInterval;
lengthLeft += l.length();
// rotate marker (if desired)
if ( rotateSymbols() )
{
setSymbolLineAngle( l.angle() * 180 / M_PI );
}
// while we're not at the end of line segment, draw!
while ( lengthLeft > painterUnitInterval )
{
// "c" is 1 for regular point or in interval (0,1] for begin of line segment
lastPt += c * diff;
lengthLeft -= painterUnitInterval;
scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
renderSymbol( lastPt, context.feature(), rc, -1, context.selected() );
c = 1; // reset c (if wasn't 1 already)
}
lastPt = pt;
}
lastPt = pt;
}
delete context.renderContext().expressionContext().popScope();
@ -1397,7 +1485,116 @@ void QgsTemplatedLineSymbolLayerBase::renderOffsetVertexAlongLine( const QPolygo
//didn't find point
}
void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context )
void QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( const QVector<QPointF> &p, QVector<QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag, int numberPointsRequired )
{
if ( p.empty() )
return;
QVector< QPointF > points = p;
const bool closedRing = points.first() == points.last();
double lengthLeft = initialOffset;
double initialLagLeft = initialLag > 0 ? -initialLag : 1; // an initialLagLeft of > 0 signifies end of lagging start points
if ( initialLagLeft < 0 && closedRing )
{
// tracking back around the ring from the first point, insert pseudo vertices before the first vertex
QPointF lastPt = points.constLast();
QVector< QPointF > pseudoPoints;
for ( int i = points.count() - 2; i > 0; --i )
{
if ( initialLagLeft >= 0 )
{
break;
}
const QPointF &pt = points[i];
if ( lastPt == pt ) // must not be equal!
continue;
MyLine l( lastPt, pt );
initialLagLeft += l.length();
lastPt = pt;
pseudoPoints << pt;
}
std::reverse( pseudoPoints.begin(), pseudoPoints.end() );
points = pseudoPoints;
points.append( p );
}
else
{
while ( initialLagLeft < 0 )
{
dest << points.constFirst();
initialLagLeft += intervalPainterUnits;
}
}
if ( initialLag > 0 )
{
lengthLeft += intervalPainterUnits - initialLagLeft;
}
QPointF lastPt = points[0];
for ( int i = 1; i < points.count(); ++i )
{
const QPointF &pt = points[i];
if ( lastPt == pt ) // must not be equal!
{
if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
{
lastPt = points[0];
i = 0;
}
continue;
}
// for each line, find out dx and dy, and length
MyLine l( lastPt, pt );
QPointF diff = l.diffForInterval( intervalPainterUnits );
// if there's some length left from previous line
// use only the rest for the first point in new line segment
double c = 1 - lengthLeft / intervalPainterUnits;
lengthLeft += l.length();
while ( lengthLeft > intervalPainterUnits || qgsDoubleNear( lengthLeft, intervalPainterUnits, 0.000000001 ) )
{
// "c" is 1 for regular point or in interval (0,1] for begin of line segment
lastPt += c * diff;
lengthLeft -= intervalPainterUnits;
dest << lastPt;
c = 1; // reset c (if wasn't 1 already)
if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
break;
}
lastPt = pt;
if ( numberPointsRequired > 0 && dest.size() >= numberPointsRequired )
break;
// if a closed ring, we keep looping around the ring until we hit the required number of points
if ( closedRing && i == points.count() - 1 && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
{
lastPt = points[0];
i = 0;
}
}
if ( !closedRing && numberPointsRequired > 0 && dest.size() < numberPointsRequired )
{
// pad with repeating last point to match desired size
while ( dest.size() < numberPointsRequired )
dest << points.constLast();
}
}
void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver )
{
if ( !points.isEmpty() )
{
@ -1412,33 +1609,56 @@ void QgsTemplatedLineSymbolLayerBase::renderPolylineCentral( const QPolygonF &po
last = *it;
}
// find the segment where the central point lies
it = points.constBegin();
last = *it;
qreal last_at = 0, next_at = 0;
QPointF next;
int segment = 0;
for ( ++it; it != points.constEnd(); ++it )
{
next = *it;
next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
( last.y() - it->y() ) * ( last.y() - it->y() ) );
if ( next_at >= length / 2 )
break; // we have reached the center
last = *it;
last_at = next_at;
segment++;
}
const double midPoint = length / 2;
// find out the central point on segment
MyLine l( last, next ); // for line angle
qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
QPointF pt = last + ( next - last ) * k;
QPointF pt;
double thisSymbolAngle = 0;
if ( averageAngleOver > 0 && !qgsDoubleNear( averageAngleOver, 0.0 ) )
{
QVector< QPointF > angleStartPoints;
QVector< QPointF > symbolPoints;
QVector< QPointF > angleEndPoints;
// collectOffsetPoints will have the first point in the line as the first result -- we don't want this, we need the second
collectOffsetPoints( points, symbolPoints, midPoint, midPoint, 0.0, 2 );
collectOffsetPoints( points, angleStartPoints, midPoint, 0, averageAngleOver, 2 );
collectOffsetPoints( points, angleEndPoints, midPoint, midPoint - averageAngleOver, 0, 2 );
pt = symbolPoints.at( 1 );
MyLine l( angleStartPoints.at( 1 ), angleEndPoints.at( 1 ) );
thisSymbolAngle = l.angle();
}
else
{
// find the segment where the central point lies
it = points.constBegin();
last = *it;
qreal last_at = 0, next_at = 0;
QPointF next;
int segment = 0;
for ( ++it; it != points.constEnd(); ++it )
{
next = *it;
next_at += std::sqrt( ( last.x() - it->x() ) * ( last.x() - it->x() ) +
( last.y() - it->y() ) * ( last.y() - it->y() ) );
if ( next_at >= midPoint )
break; // we have reached the center
last = *it;
last_at = next_at;
segment++;
}
// find out the central point on segment
MyLine l( last, next ); // for line angle
qreal k = ( length * 0.5 - last_at ) / ( next_at - last_at );
pt = last + ( next - last ) * k;
thisSymbolAngle = l.angle();
}
// draw the marker
double origAngle = symbolAngle();
if ( rotateSymbols() )
setSymbolAngle( origAngle + l.angle() * 180 / M_PI );
setSymbolAngle( origAngle + thisSymbolAngle * 180 / M_PI );
renderSymbol( pt, context.feature(), context.renderContext(), -1, context.selected() );
if ( rotateSymbols() )
setSymbolAngle( origAngle );

View File

@ -401,6 +401,70 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
*/
void setOffsetAlongLineMapUnitScale( const QgsMapUnitScale &scale ) { mOffsetAlongLineMapUnitScale = scale; }
/**
* Returns the length of line over which the line's direction is averaged when
* calculating individual symbol angles. Longer lengths smooth out angles from jagged lines to a greater extent.
*
* Units are retrieved through averageAngleUnit()
*
* \see setAverageAngleLength()
* \see averageAngleUnit()
* \see averageAngleMapUnitScale()
*/
double averageAngleLength() const { return mAverageAngleLength; }
/**
* Sets the \a length of line over which the line's direction is averaged when
* calculating individual symbol angles. Longer lengths smooth out angles from jagged lines to a greater extent.
*
* Units are set through setAverageAngleUnit()
*
* \see averageAngleLength()
* \see setAverageAngleUnit()
* \see setAverageAngleMapUnitScale()
*/
void setAverageAngleLength( double length ) { mAverageAngleLength = length; }
/**
* Sets the \a unit for the length over which the line's direction is averaged when
* calculating individual symbol angles.
*
* \see averageAngleUnit()
* \see setAverageAngleLength()
* \see setAverageAngleMapUnitScale()
*/
void setAverageAngleUnit( QgsUnitTypes::RenderUnit unit ) { mAverageAngleLengthUnit = unit; }
/**
* Returns the unit for the length over which the line's direction is averaged when
* calculating individual symbol angles.
*
* \see setAverageAngleUnit()
* \see averageAngleLength()
* \see averageAngleMapUnitScale()
*/
QgsUnitTypes::RenderUnit averageAngleUnit() const { return mAverageAngleLengthUnit; }
/**
* Sets the map unit \a scale for the length over which the line's direction is averaged when
* calculating individual symbol angles.
*
* \see averageAngleMapUnitScale()
* \see setAverageAngleLength()
* \see setAverageAngleUnit()
*/
void setAverageAngleMapUnitScale( const QgsMapUnitScale &scale ) { mAverageAngleLengthMapUnitScale = scale; }
/**
* Returns the map unit scale for the length over which the line's direction is averaged when
* calculating individual symbol angles.
*
* \see setAverageAngleMapUnitScale()
* \see averageAngleLength()
* \see averageAngleUnit()
*/
const QgsMapUnitScale &averageAngleMapUnitScale() const { return mAverageAngleLengthMapUnitScale; }
void renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context ) FINAL;
void renderPolygonStroke( const QPolygonF &points, QList<QPolygonF> *rings, QgsSymbolRenderContext &context ) FINAL;
QgsUnitTypes::RenderUnit outputUnit() const FINAL;
@ -455,9 +519,9 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
private:
void renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context );
void renderPolylineInterval( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver );
void renderPolylineVertex( const QPolygonF &points, QgsSymbolRenderContext &context, QgsTemplatedLineSymbolLayerBase::Placement placement = QgsTemplatedLineSymbolLayerBase::Vertex );
void renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context );
void renderPolylineCentral( const QPolygonF &points, QgsSymbolRenderContext &context, double averageAngleOver );
double markerAngle( const QPolygonF &points, bool isRing, int vertex );
/**
@ -473,6 +537,11 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
*/
void renderOffsetVertexAlongLine( const QPolygonF &points, int vertex, double distance, QgsSymbolRenderContext &context );
static void collectOffsetPoints( const QVector< QPointF> &points,
QVector< QPointF> &dest, double intervalPainterUnits, double initialOffset, double initialLag = 0,
int numberPointsRequired = -1 );
bool mRotateSymbols = true;
double mInterval = 3;
QgsUnitTypes::RenderUnit mIntervalUnit = QgsUnitTypes::RenderMillimeters;
@ -481,6 +550,12 @@ class CORE_EXPORT QgsTemplatedLineSymbolLayerBase : public QgsLineSymbolLayer
double mOffsetAlongLine = 0; //distance to offset along line before marker is drawn
QgsUnitTypes::RenderUnit mOffsetAlongLineUnit = QgsUnitTypes::RenderMillimeters; //unit for offset along line
QgsMapUnitScale mOffsetAlongLineMapUnitScale;
double mAverageAngleLength = 4;
QgsUnitTypes::RenderUnit mAverageAngleLengthUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mAverageAngleLengthMapUnitScale;
friend class TestQgsMarkerLineSymbol;
};
/**

View File

@ -84,6 +84,7 @@ void QgsSymbolLayer::initPropertyDefinitions()
{ QgsSymbolLayer::PropertyPlacement, QgsPropertyDefinition( "placement", QgsPropertyDefinition::DataTypeString, QObject::tr( "Marker placement" ), QObject::tr( "string " ) + "[<b>interval</b>|<b>vertex</b>|<b>lastvertex</b>|<b>firstvertex</b>|<b>centerpoint</b>|<b>curvepoint</b>]", origin )},
{ QgsSymbolLayer::PropertyInterval, QgsPropertyDefinition( "interval", QObject::tr( "Marker interval" ), QgsPropertyDefinition::DoublePositive, origin )},
{ QgsSymbolLayer::PropertyOffsetAlongLine, QgsPropertyDefinition( "offsetAlongLine", QObject::tr( "Offset along line" ), QgsPropertyDefinition::DoublePositive, origin )},
{ QgsSymbolLayer::PropertyAverageAngleLength, QgsPropertyDefinition( "averageAngleLength", QObject::tr( "Average line angles over" ), QgsPropertyDefinition::DoublePositive, origin )},
{ QgsSymbolLayer::PropertyHorizontalAnchor, QgsPropertyDefinition( "hAnchor", QObject::tr( "Horizontal anchor point" ), QgsPropertyDefinition::HorizontalAnchor, origin )},
{ QgsSymbolLayer::PropertyVerticalAnchor, QgsPropertyDefinition( "vAnchor", QObject::tr( "Vertical anchor point" ), QgsPropertyDefinition::VerticalAnchor, origin )},
{ QgsSymbolLayer::PropertyLayerEnabled, QgsPropertyDefinition( "enabled", QObject::tr( "Layer enabled" ), QgsPropertyDefinition::Boolean, origin )},

View File

@ -165,6 +165,7 @@ class CORE_EXPORT QgsSymbolLayer
PropertyPlacement, //!< Line marker placement
PropertyInterval, //!< Line marker interval
PropertyOffsetAlongLine, //!< Offset along line
PropertyAverageAngleLength, //!< Length to average symbol angles over
PropertyHorizontalAnchor, //!< Horizontal anchor point
PropertyVerticalAnchor, //!< Vertical anchor point
PropertyLayerEnabled, //!< Whether symbol layer is enabled

View File

@ -1670,12 +1670,15 @@ QgsMarkerLineSymbolLayerWidget::QgsMarkerLineSymbolLayerWidget( QgsVectorLayer *
connect( mIntervalUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsMarkerLineSymbolLayerWidget::mIntervalUnitWidget_changed );
connect( mOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsMarkerLineSymbolLayerWidget::mOffsetUnitWidget_changed );
connect( mOffsetAlongLineUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsMarkerLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed );
connect( mAverageAngleUnit, &QgsUnitSelectionWidget::changed, this, &QgsMarkerLineSymbolLayerWidget::averageAngleUnitChanged );
mIntervalUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mOffsetUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mOffsetAlongLineUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mAverageAngleUnit->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mRingFilterComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconAllRings.svg" ) ), tr( "All Rings" ), QgsLineSymbolLayer::AllRings );
mRingFilterComboBox->addItem( QgsApplication::getThemeIcon( QStringLiteral( "mIconExteriorRing.svg" ) ), tr( "Exterior Ring Only" ), QgsLineSymbolLayer::ExteriorRingOnly );
@ -1698,10 +1701,13 @@ QgsMarkerLineSymbolLayerWidget::QgsMarkerLineSymbolLayerWidget( QgsVectorLayer *
mRingsLabel->hide();
}
mSpinAverageAngleLength->setClearValue( 4.0 );
connect( spinInterval, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setInterval );
connect( mSpinOffsetAlongLine, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setOffsetAlongLine );
connect( chkRotateMarker, &QAbstractButton::clicked, this, &QgsMarkerLineSymbolLayerWidget::setRotate );
connect( spinOffset, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setOffset );
connect( mSpinAverageAngleLength, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsMarkerLineSymbolLayerWidget::setAverageAngle );
connect( radInterval, &QAbstractButton::clicked, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
connect( radVertex, &QAbstractButton::clicked, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
connect( radVertexLast, &QAbstractButton::clicked, this, &QgsMarkerLineSymbolLayerWidget::setPlacement );
@ -1758,6 +1764,10 @@ void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
mOffsetAlongLineUnitWidget->setMapUnitScale( mLayer->offsetAlongLineMapUnitScale() );
mOffsetAlongLineUnitWidget->blockSignals( false );
whileBlocking( mAverageAngleUnit )->setUnit( mLayer->averageAngleUnit() );
whileBlocking( mAverageAngleUnit )->setMapUnitScale( mLayer->averageAngleMapUnitScale() );
whileBlocking( mSpinAverageAngleLength )->setValue( mLayer->averageAngleLength() );
whileBlocking( mRingFilterComboBox )->setCurrentIndex( mRingFilterComboBox->findData( mLayer->ringFilter() ) );
setPlacement(); // update gui
@ -1766,6 +1776,7 @@ void QgsMarkerLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
registerDataDefinedButton( mLineOffsetDDBtn, QgsSymbolLayer::PropertyOffset );
registerDataDefinedButton( mPlacementDDBtn, QgsSymbolLayer::PropertyPlacement );
registerDataDefinedButton( mOffsetAlongLineDDBtn, QgsSymbolLayer::PropertyOffsetAlongLine );
registerDataDefinedButton( mAverageAngleDDBtn, QgsSymbolLayer::PropertyAverageAngleLength );
}
QgsSymbolLayer *QgsMarkerLineSymbolLayerWidget::symbolLayer()
@ -1787,6 +1798,9 @@ void QgsMarkerLineSymbolLayerWidget::setOffsetAlongLine( double val )
void QgsMarkerLineSymbolLayerWidget::setRotate()
{
mSpinAverageAngleLength->setEnabled( chkRotateMarker->isChecked() && ( radInterval->isChecked() || radCentralPoint->isChecked() ) );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
mLayer->setRotateSymbols( chkRotateMarker->isChecked() );
emit changed();
}
@ -1802,6 +1816,9 @@ void QgsMarkerLineSymbolLayerWidget::setPlacement()
bool interval = radInterval->isChecked();
spinInterval->setEnabled( interval );
mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() );
mOffsetAlongLineUnitWidget->setEnabled( mSpinOffsetAlongLine->isEnabled() );
mSpinAverageAngleLength->setEnabled( chkRotateMarker->isChecked() && ( radInterval->isChecked() || radCentralPoint->isChecked() ) );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
//mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayer::Interval : QgsMarkerLineSymbolLayer::Vertex );
if ( radInterval->isChecked() )
mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval );
@ -1849,6 +1866,25 @@ void QgsMarkerLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed()
emit changed();
}
void QgsMarkerLineSymbolLayerWidget::averageAngleUnitChanged()
{
if ( mLayer )
{
mLayer->setAverageAngleUnit( mAverageAngleUnit->unit() );
mLayer->setAverageAngleMapUnitScale( mAverageAngleUnit->getMapUnitScale() );
}
emit changed();
}
void QgsMarkerLineSymbolLayerWidget::setAverageAngle( double val )
{
if ( mLayer )
{
mLayer->setAverageAngleLength( val );
emit changed();
}
}
///////////
@ -1861,6 +1897,7 @@ QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *
connect( mIntervalUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mIntervalUnitWidget_changed );
connect( mOffsetUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mOffsetUnitWidget_changed );
connect( mOffsetAlongLineUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::mOffsetAlongLineUnitWidget_changed );
connect( mAverageAngleUnit, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::averageAngleUnitChanged );
connect( mHashLengthUnitWidget, &QgsUnitSelectionWidget::changed, this, &QgsHashedLineSymbolLayerWidget::hashLengthUnitWidgetChanged );
mIntervalUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
@ -1868,7 +1905,8 @@ QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mOffsetAlongLineUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mAverageAngleUnit->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
mHashLengthUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << QgsUnitTypes::RenderMillimeters << QgsUnitTypes::RenderMetersInMapUnits << QgsUnitTypes::RenderMapUnits << QgsUnitTypes::RenderPixels
<< QgsUnitTypes::RenderPoints << QgsUnitTypes::RenderInches );
@ -1894,6 +1932,7 @@ QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *
}
mHashRotationSpinBox->setClearValue( 0 );
mSpinAverageAngleLength->setClearValue( 4.0 );
connect( spinInterval, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setInterval );
connect( mSpinOffsetAlongLine, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setOffsetAlongLine );
@ -1901,6 +1940,7 @@ QgsHashedLineSymbolLayerWidget::QgsHashedLineSymbolLayerWidget( QgsVectorLayer *
connect( mHashRotationSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setHashAngle );
connect( chkRotateMarker, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setRotate );
connect( spinOffset, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setOffset );
connect( mSpinAverageAngleLength, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsHashedLineSymbolLayerWidget::setAverageAngle );
connect( radInterval, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement );
connect( radVertex, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement );
connect( radVertexLast, &QAbstractButton::clicked, this, &QgsHashedLineSymbolLayerWidget::setPlacement );
@ -1958,7 +1998,9 @@ void QgsHashedLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
mOffsetAlongLineUnitWidget->setUnit( mLayer->offsetAlongLineUnit() );
mOffsetAlongLineUnitWidget->setMapUnitScale( mLayer->offsetAlongLineMapUnitScale() );
mOffsetAlongLineUnitWidget->blockSignals( false );
whileBlocking( mAverageAngleUnit )->setUnit( mLayer->averageAngleUnit() );
whileBlocking( mAverageAngleUnit )->setMapUnitScale( mLayer->averageAngleMapUnitScale() );
whileBlocking( mSpinAverageAngleLength )->setValue( mLayer->averageAngleLength() );
whileBlocking( mHashLengthUnitWidget )->setUnit( mLayer->hashLengthUnit() );
whileBlocking( mHashLengthUnitWidget )->setMapUnitScale( mLayer->hashLengthMapUnitScale() );
@ -1972,6 +2014,7 @@ void QgsHashedLineSymbolLayerWidget::setSymbolLayer( QgsSymbolLayer *layer )
registerDataDefinedButton( mOffsetAlongLineDDBtn, QgsSymbolLayer::PropertyOffsetAlongLine );
registerDataDefinedButton( mHashLengthDDBtn, QgsSymbolLayer::PropertyLineDistance );
registerDataDefinedButton( mHashRotationDDBtn, QgsSymbolLayer::PropertyLineAngle );
registerDataDefinedButton( mAverageAngleDDBtn, QgsSymbolLayer::PropertyAverageAngleLength );
}
QgsSymbolLayer *QgsHashedLineSymbolLayerWidget::symbolLayer()
@ -2005,6 +2048,9 @@ void QgsHashedLineSymbolLayerWidget::setHashAngle( double val )
void QgsHashedLineSymbolLayerWidget::setRotate()
{
mSpinAverageAngleLength->setEnabled( chkRotateMarker->isChecked() && ( radInterval->isChecked() || radCentralPoint->isChecked() ) );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
mLayer->setRotateSymbols( chkRotateMarker->isChecked() );
emit changed();
}
@ -2020,6 +2066,9 @@ void QgsHashedLineSymbolLayerWidget::setPlacement()
bool interval = radInterval->isChecked();
spinInterval->setEnabled( interval );
mSpinOffsetAlongLine->setEnabled( radInterval->isChecked() || radVertexLast->isChecked() || radVertexFirst->isChecked() );
mOffsetAlongLineUnitWidget->setEnabled( mSpinOffsetAlongLine->isEnabled() );
mSpinAverageAngleLength->setEnabled( chkRotateMarker->isChecked() && ( radInterval->isChecked() || radCentralPoint->isChecked() ) );
mAverageAngleUnit->setEnabled( mSpinAverageAngleLength->isEnabled() );
//mLayer->setPlacement( interval ? QgsMarkerLineSymbolLayer::Interval : QgsMarkerLineSymbolLayer::Vertex );
if ( radInterval->isChecked() )
mLayer->setPlacement( QgsTemplatedLineSymbolLayerBase::Interval );
@ -2077,6 +2126,25 @@ void QgsHashedLineSymbolLayerWidget::hashLengthUnitWidgetChanged()
emit changed();
}
void QgsHashedLineSymbolLayerWidget::averageAngleUnitChanged()
{
if ( mLayer )
{
mLayer->setAverageAngleUnit( mAverageAngleUnit->unit() );
mLayer->setAverageAngleMapUnitScale( mAverageAngleUnit->getMapUnitScale() );
}
emit changed();
}
void QgsHashedLineSymbolLayerWidget::setAverageAngle( double val )
{
if ( mLayer )
{
mLayer->setAverageAngleLength( val );
emit changed();
}
}
///////////

View File

@ -498,6 +498,8 @@ class GUI_EXPORT QgsMarkerLineSymbolLayerWidget : public QgsSymbolLayerWidget, p
void mIntervalUnitWidget_changed();
void mOffsetUnitWidget_changed();
void mOffsetAlongLineUnitWidget_changed();
void averageAngleUnitChanged();
void setAverageAngle( double val );
};
@ -549,6 +551,8 @@ class GUI_EXPORT QgsHashedLineSymbolLayerWidget : public QgsSymbolLayerWidget, p
void mOffsetUnitWidget_changed();
void mOffsetAlongLineUnitWidget_changed();
void hashLengthUnitWidgetChanged();
void averageAngleUnitChanged();
void setAverageAngle( double val );
private:
QgsHashedLineSymbolLayer *mLayer = nullptr;

View File

@ -191,166 +191,29 @@
<property name="topMargin">
<number>0</number>
</property>
<item row="2" column="3">
<item row="2" column="4">
<widget class="QgsPropertyOverrideButton" name="mHashRotationDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QgsDoubleSpinBox" name="mSpinOffsetAlongLine">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QgsPropertyOverrideButton" name="mLineOffsetDDBtn">
<item row="4" column="4">
<widget class="QgsPropertyOverrideButton" name="mAverageAngleDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Hash rotation</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Offset along line</string>
</property>
</widget>
</item>
<item row="0" column="3">
<item row="0" column="4">
<widget class="QgsPropertyOverrideButton" name="mOffsetAlongLineDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetAlongLineUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="mRingsLabel">
<property name="text">
<string>Rings</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Line offset</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<widget class="QComboBox" name="mRingFilterComboBox"/>
</item>
<item row="4" column="1">
<widget class="QgsDoubleSpinBox" name="spinOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-999999999.000000000000000</double>
</property>
<property name="maximum">
<double>999999999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QCheckBox" name="chkRotateMarker">
<property name="text">
<string>Rotate hash to follow line direction</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Hash length</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QgsDoubleSpinBox" name="mSpinHashLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsUnitSelectionWidget" name="mHashLengthUnitWidget" native="true">
<item row="4" column="3">
<widget class="QgsUnitSelectionWidget" name="mAverageAngleUnit" native="true">
<property name="minimumSize">
<size>
<width>20</width>
@ -362,14 +225,21 @@
</property>
</widget>
</item>
<item row="1" column="3">
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Line offset</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QgsPropertyOverrideButton" name="mHashLengthDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="2" column="2">
<widget class="QgsDoubleSpinBox" name="mHashRotationSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@ -397,6 +267,201 @@
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Hash rotation</string>
</property>
</widget>
</item>
<item row="5" column="3">
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Offset along line</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Hash length</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QgsDoubleSpinBox" name="mSpinHashLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="2" colspan="3">
<widget class="QComboBox" name="mRingFilterComboBox"/>
</item>
<item row="0" column="3">
<widget class="QgsUnitSelectionWidget" name="mOffsetAlongLineUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="mRingsLabel">
<property name="text">
<string>Rings</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QgsDoubleSpinBox" name="spinOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-999999999.000000000000000</double>
</property>
<property name="maximum">
<double>999999999.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Average angle over</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QgsUnitSelectionWidget" name="mHashLengthUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="2">
<widget class="QgsDoubleSpinBox" name="mSpinAverageAngleLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="4">
<widget class="QgsPropertyOverrideButton" name="mLineOffsetDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QgsDoubleSpinBox" name="mSpinOffsetAlongLine">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0" colspan="5">
<widget class="QCheckBox" name="chkRotateMarker">
<property name="text">
<string>Rotate hash to follow line direction</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -430,6 +495,22 @@
<tabstop>radVertexFirst</tabstop>
<tabstop>radCentralPoint</tabstop>
<tabstop>radCurvePoint</tabstop>
<tabstop>mSpinOffsetAlongLine</tabstop>
<tabstop>mOffsetAlongLineUnitWidget</tabstop>
<tabstop>mOffsetAlongLineDDBtn</tabstop>
<tabstop>mSpinHashLength</tabstop>
<tabstop>mHashLengthUnitWidget</tabstop>
<tabstop>mHashLengthDDBtn</tabstop>
<tabstop>mHashRotationSpinBox</tabstop>
<tabstop>mHashRotationDDBtn</tabstop>
<tabstop>chkRotateMarker</tabstop>
<tabstop>mSpinAverageAngleLength</tabstop>
<tabstop>mAverageAngleUnit</tabstop>
<tabstop>mAverageAngleDDBtn</tabstop>
<tabstop>spinOffset</tabstop>
<tabstop>mOffsetUnitWidget</tabstop>
<tabstop>mLineOffsetDDBtn</tabstop>
<tabstop>mRingFilterComboBox</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -191,7 +191,7 @@
<property name="topMargin">
<number>0</number>
</property>
<item row="0" column="1">
<item row="0" column="2">
<widget class="QgsDoubleSpinBox" name="mSpinOffsetAlongLine">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@ -213,32 +213,28 @@
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QgsPropertyOverrideButton" name="mOffsetAlongLineDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
<item row="4" column="2" colspan="3">
<widget class="QComboBox" name="mRingFilterComboBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Offset along line</string>
</property>
</widget>
</item>
<item row="2" column="3">
<item row="3" column="4">
<widget class="QgsPropertyOverrideButton" name="mLineOffsetDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="0" column="2">
<item row="2" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Average angle over</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QgsUnitSelectionWidget" name="mOffsetAlongLineUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<width>20</width>
<height>0</height>
</size>
</property>
@ -247,27 +243,7 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Line offset</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="2">
<widget class="QgsDoubleSpinBox" name="spinOffset">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@ -289,23 +265,112 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<item row="3" column="3">
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QgsUnitSelectionWidget" name="mAverageAngleUnit" native="true">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::TabFocus</enum>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QgsPropertyOverrideButton" name="mAverageAngleDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QgsPropertyOverrideButton" name="mOffsetAlongLineDDBtn">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QgsDoubleSpinBox" name="mSpinAverageAngleLength">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="maximum">
<double>10000000.000000000000000</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Offset along line</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="5">
<widget class="QCheckBox" name="chkRotateMarker">
<property name="text">
<string>Rotate marker</string>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="3" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Line offset</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QLabel" name="mRingsLabel">
<property name="text">
<string>Rings</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="3">
<widget class="QComboBox" name="mRingFilterComboBox"/>
</item>
</layout>
</item>
</layout>
@ -339,6 +404,17 @@
<tabstop>radVertexFirst</tabstop>
<tabstop>radCentralPoint</tabstop>
<tabstop>radCurvePoint</tabstop>
<tabstop>mSpinOffsetAlongLine</tabstop>
<tabstop>mOffsetAlongLineUnitWidget</tabstop>
<tabstop>mOffsetAlongLineDDBtn</tabstop>
<tabstop>chkRotateMarker</tabstop>
<tabstop>mSpinAverageAngleLength</tabstop>
<tabstop>mAverageAngleUnit</tabstop>
<tabstop>mAverageAngleDDBtn</tabstop>
<tabstop>spinOffset</tabstop>
<tabstop>mOffsetUnitWidget</tabstop>
<tabstop>mLineOffsetDDBtn</tabstop>
<tabstop>mRingFilterComboBox</tabstop>
</tabstops>
<resources/>
<connections/>

View File

@ -60,6 +60,8 @@ class TestQgsMarkerLineSymbol : public QObject
void lineOffset();
void pointNumInterval();
void pointNumVertex();
void collectPoints_data();
void collectPoints();
private:
bool render( const QString &fileName );
@ -132,7 +134,7 @@ void TestQgsMarkerLineSymbol::lineOffset()
QVERIFY( success );
mMapSettings->setExtent( QgsRectangle( -140, -140, 140, 140 ) );
QVERIFY( render( "line_offset" ) );
QVERIFY( render( QStringLiteral( "line_offset" ) ) );
// TODO: -0.0 offset, see
// https://issues.qgis.org/issues/13811#note-1
@ -165,7 +167,7 @@ void TestQgsMarkerLineSymbol::pointNumInterval()
mLinesLayer->setRenderer( r );
mMapSettings->setExtent( QgsRectangle( -140, -140, 140, 140 ) );
QVERIFY( render( "point_num_interval" ) );
QVERIFY( render( QStringLiteral( "point_num_interval" ) ) );
}
void TestQgsMarkerLineSymbol::pointNumVertex()
@ -194,7 +196,240 @@ void TestQgsMarkerLineSymbol::pointNumVertex()
mLinesLayer->setRenderer( r );
mMapSettings->setExtent( QgsRectangle( -140, -140, 140, 140 ) );
QVERIFY( render( "point_num_vertex" ) );
QVERIFY( render( QStringLiteral( "point_num_vertex" ) ) );
}
void TestQgsMarkerLineSymbol::collectPoints_data()
{
QTest::addColumn<QVector<QPointF>>( "input" );
QTest::addColumn<double>( "interval" );
QTest::addColumn<double>( "initialOffset" );
QTest::addColumn<double>( "initialLag" );
QTest::addColumn<int>( "numberPointsRequired" );
QTest::addColumn<QVector<QPointF>>( "expected" );
QTest::newRow( "empty" )
<< QVector< QPointF >()
<< 1.0 << 0.0 << 0.0 << 0
<< QVector< QPointF >();
QTest::newRow( "a" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) )
<< 1.0 << 0.0 << 0.0 << 0
<< ( QVector< QPointF >() );
QTest::newRow( "b" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 1.0 << 0.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 2, 2 ) << QPointF( 3, 2 )
<< QPointF( 4, 2 ) << QPointF( 5, 2 ) << QPointF( 6, 2 ) << QPointF( 7, 2 ) << QPointF( 8, 2 )
<< QPointF( 9, 2 ) << QPointF( 10, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "b maxpoints" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 1.0 << 0.0 << 0.0 << 3
<< ( QVector< QPointF >() << QPointF( 2, 2 ) << QPointF( 3, 2 )
<< QPointF( 4, 2 ) );
QTest::newRow( "b pad points" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 1.0 << 0.0 << 0.0 << 13
<< ( QVector< QPointF >() << QPointF( 2, 2 ) << QPointF( 3, 2 )
<< QPointF( 4, 2 ) << QPointF( 5, 2 ) << QPointF( 6, 2 ) << QPointF( 7, 2 ) << QPointF( 8, 2 )
<< QPointF( 9, 2 ) << QPointF( 10, 2 ) << QPointF( 11, 2 ) << QPointF( 11, 2 ) << QPointF( 11, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "c" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 1.0 << 1.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 3, 2 )
<< QPointF( 4, 2 ) << QPointF( 5, 2 ) << QPointF( 6, 2 ) << QPointF( 7, 2 ) << QPointF( 8, 2 )
<< QPointF( 9, 2 ) << QPointF( 10, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "c3" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 0.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 3, 2 ) << QPointF( 5, 2 )
<< QPointF( 7, 2 ) << QPointF( 9, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "d" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 1.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 2, 2 ) << QPointF( 4, 2 )
<< QPointF( 6, 2 ) << QPointF( 8, 2 ) << QPointF( 10, 2 ) );
QTest::newRow( "e" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 2.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 3, 2 ) << QPointF( 5, 2 )
<< QPointF( 7, 2 ) << QPointF( 9, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "f" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 0.0 << 1.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 4, 2 ) << QPointF( 6, 2 ) << QPointF( 8, 2 )
<< QPointF( 10, 2 ) );
QTest::newRow( "g" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 0.0 << 2.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 1, 2 ) << QPointF( 3, 2 ) << QPointF( 5, 2 ) << QPointF( 7, 2 )
<< QPointF( 9, 2 ) << QPointF( 11, 2 ) );
QTest::newRow( "h" )
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 11, 2 ) )
<< 2.0 << 0.0 << 2.1 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 1, 2 ) << QPointF( 2.9, 2 ) << QPointF( 4.9, 2 ) << QPointF( 6.9, 2 )
<< QPointF( 8.9, 2 ) << QPointF( 10.9, 2 ) );
QTest::newRow( "i" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 8, 2 ) )
<< 2.0 << 2.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 4, 2 ) << QPointF( 6, 2 ) << QPointF( 8, 2 ) );
QTest::newRow( "j" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 8, 2 ) )
<< 2.0 << 0.0 << 2.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 4, 2 ) << QPointF( 6, 2 ) << QPointF( 8, 2 ) );
QTest::newRow( "k" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 8, 2 ) )
<< 2.0 << 0.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 2, 2 ) << QPointF( 4, 2 ) << QPointF( 6, 2 ) << QPointF( 8, 2 ) );
QTest::newRow( "closed ring" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 2.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring required points" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 2.0 << 0.0 << 7
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) );
QTest::newRow( "closed ring 1.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 1.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring 1.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 1.0 << 0.0 << 11
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) );
QTest::newRow( "closed ring initial offset 1.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring initial offset 1.0 num points" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 0.0 << 10
<< ( QVector< QPointF >() << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) );
QTest::newRow( "closed ring 1.0 initial lag 1.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 1.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring 2.0 initial lag" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 0.0 << 1.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 1 ) << QPointF( 1, 2 ) << QPointF( 2, 1 ) << QPointF( 1, 0 ) << QPointF( 0, 1 ) );
QTest::newRow( "closed ring 1.0 initial lag 0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 0.5 << 0
<< ( QVector< QPointF >() << QPointF( 0, 1.5 ) << QPointF( 0.5, 2 ) << QPointF( 1.5, 2 ) << QPointF( 2, 1.5 ) << QPointF( 2, 0.5 ) << QPointF( 1.5, 0 )
<< QPointF( 0.5, 0 ) << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) );
QTest::newRow( "closed ring 1.0 initial offset 0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.5 << 0.0 << 10
<< ( QVector< QPointF >() << QPointF( 0.5, 2 ) << QPointF( 1.5, 2 ) << QPointF( 2, 1.5 ) << QPointF( 2, 0.5 ) << QPointF( 1.5, 0 )
<< QPointF( 0.5, 0 ) << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) << QPointF( 0.5, 2.0 ) << QPointF( 1.5, 2.0 ) );
QTest::newRow( "closed ring 1.0 initial lag 1.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 1.5 << 0
<< ( QVector< QPointF >() << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) << QPointF( 0.5, 2 ) << QPointF( 1.5, 2 ) << QPointF( 2, 1.5 ) << QPointF( 2, 0.5 ) << QPointF( 1.5, 0 )
<< QPointF( 0.5, 0 ) << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) );
QTest::newRow( "closed ring 1.0 initial lag 2.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 2.0 << 0
<< ( QVector< QPointF >() << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring 1.0 initial lag 3.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 3.0 << 0
<< ( QVector< QPointF >() << QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring 1.0 initial lag 3.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 3.5 << 0
<< ( QVector< QPointF >() << QPointF( 1.5, 0 ) << QPointF( 0.5, 0 ) << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) << QPointF( 0.5, 2 ) << QPointF( 1.5, 2 ) << QPointF( 2, 1.5 ) << QPointF( 2, 0.5 )
<< QPointF( 1.5, 0 ) << QPointF( 0.5, 0 ) << QPointF( 0, 0.5 ) << QPointF( 0, 1.5 ) );
QTest::newRow( "closed ring 1.0 initial lag 4.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 4.0 << 0
<< ( QVector< QPointF >() << QPointF( 2, 0 ) << QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "closed ring 1.0 initial lag 5.0" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 1.0 << 0.0 << 5.0 << 0
<< ( QVector< QPointF >() << QPointF( 2, 1 ) << QPointF( 2, 0 ) << QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) << QPointF( 1, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 1 ) << QPointF( 2, 0 )
<< QPointF( 1, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 1 ) << QPointF( 0, 2 ) );
QTest::newRow( "simulate initial offset 0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 1.5 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.5, 2 ) << QPointF( 2, 1.5 ) );
QTest::newRow( "simulate initial offset 0.5 lag 0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 2.0 - ( 0.5 - 0.5 ) << 0.5 - 0.5 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) );
QTest::newRow( "simulate initial offset 0.5 lag 0.1" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 2.0 - ( 0.5 - 0.1 ) << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.4, 2 ) << QPointF( 2, 1.6 ) );
QTest::newRow( "simulate initial offset 0.1 lag 0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 0.0 << 0.5 - 0.1 << 0
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 1.6, 2.0 ) << QPointF( 2.0, 0.4 ) );
QTest::newRow( "simulate initial offset 0.5 lag -0.1" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 2.0 - 0.5 - 0.1 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.6, 2 ) << QPointF( 2, 1.4 ) );
QTest::newRow( "simulate initial offset 0.1 lag -0.5" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) )
<< 2.0 << 2.0 - 0.1 - 0.5 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.6, 2 ) << QPointF( 2.0, 1.4 ) );
QTest::newRow( "simulate initial offset 0.5 closed" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 1.5 << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.5, 2 ) << QPointF( 2, 1.5 ) << QPointF( 1.5, 0 ) << QPointF( 0.0, 0.5 ) );
QTest::newRow( "simulate initial offset 0.5 lag 0.1 closed" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 2.0 - ( 0.5 - 0.1 ) << 0.0 << 0
<< ( QVector< QPointF >() << QPointF( 0.4, 2 ) << QPointF( 2, 1.6 ) << QPointF( 1.6, 0 ) << QPointF( 0, 0.4 ) );
QTest::newRow( "simulate initial offset 0.1 lag 0.5 closed" )
<< ( QVector< QPointF >() << QPointF( 0, 2 ) << QPointF( 2, 2 ) << QPointF( 2, 0 ) << QPointF( 0, 0 ) << QPointF( 0, 2 ) )
<< 2.0 << 0.0 << 0.5 - 0.1 << 0
<< ( QVector< QPointF >() << QPointF( 0, 1.6 ) << QPointF( 1.6, 2 ) << QPointF( 2.0, 0.4 ) << QPointF( 0.4, 0.0 ) << QPointF( 0.0, 1.6 ) );
}
void TestQgsMarkerLineSymbol::collectPoints()
{
QFETCH( QVector< QPointF >, input );
QFETCH( double, interval );
QFETCH( double, initialOffset );
QFETCH( double, initialLag );
QFETCH( int, numberPointsRequired );
QFETCH( QVector< QPointF >, expected );
QVector <QPointF> dest;
QgsTemplatedLineSymbolLayerBase::collectOffsetPoints( input, dest, interval, initialOffset, initialLag, numberPointsRequired );
QCOMPARE( dest, expected );
}
bool TestQgsMarkerLineSymbol::render( const QString &testType )

View File

@ -119,6 +119,7 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(7)
hash_line.setHashAngle(45)
hash_line.setAverageAngleLength(0)
s.appendSymbolLayer(hash_line.clone())
@ -132,6 +133,74 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('line_hash_no_rotate', 'line_hash_no_rotate', rendered_image)
def testHashAverageAngle(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
hash_line = QgsHashedLineSymbolLayer(True)
hash_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
hash_line.setInterval(6)
simple_line = QgsSimpleLineSymbolLayer()
simple_line.setColor(QColor(0, 255, 0))
simple_line.setWidth(1)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(7)
hash_line.setHashAngle(45)
hash_line.setAverageAngleLength(30)
s.appendSymbolLayer(hash_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('line_hash_average_angle', 'line_hash_average_angle', rendered_image)
def testHashAverageAngle(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
hash_line = QgsHashedLineSymbolLayer(True)
hash_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
simple_line = QgsSimpleLineSymbolLayer()
simple_line.setColor(QColor(0, 255, 0))
simple_line.setWidth(1)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(7)
hash_line.setHashAngle(45)
hash_line.setAverageAngleLength(30)
s.appendSymbolLayer(hash_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('line_hash_center_average_angle', 'line_hash_center_average_angle', rendered_image)
def testHashAverageAngleClosedRing(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
hash_line = QgsHashedLineSymbolLayer(True)
hash_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
hash_line.setInterval(6)
simple_line = QgsSimpleLineSymbolLayer()
simple_line.setColor(QColor(0, 255, 0))
simple_line.setWidth(1)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(7)
hash_line.setHashAngle(0)
hash_line.setAverageAngleLength(30)
s.appendSymbolLayer(hash_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10, 10 0, 0 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('line_hash_ring_average_angle', 'line_hash_ring_average_angle', rendered_image)
def testHashPlacement(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
@ -146,6 +215,7 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(7)
hash_line.setAverageAngleLength(0)
s.appendSymbolLayer(hash_line.clone())
@ -180,6 +250,7 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(10)
hash_line.setAverageAngleLength(0)
s.appendSymbolLayer(hash_line.clone())
self.assertEqual(s.symbolLayer(0).ringFilter(), QgsLineSymbolLayer.AllRings)
@ -226,6 +297,7 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(10)
hash_line.setAverageAngleLength(0)
s.appendSymbolLayer(hash_line.clone())
@ -253,6 +325,7 @@ class TestQgsHashedLineSymbolLayer(unittest.TestCase):
line_symbol.changeSymbolLayer(0, simple_line)
hash_line.setSubSymbol(line_symbol)
hash_line.setHashLength(10)
hash_line.setAverageAngleLength(0)
s.appendSymbolLayer(hash_line.clone())

View File

@ -41,6 +41,7 @@ from qgis.core import (QgsGeometry,
QgsSymbolLayerUtils,
QgsSimpleMarkerSymbolLayer,
QgsLineSymbolLayer,
QgsTemplatedLineSymbolLayerBase,
QgsMarkerLineSymbolLayer,
QgsMarkerSymbol,
QgsGeometryGeneratorSymbolLayer,
@ -137,6 +138,7 @@ class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
s3.appendSymbolLayer(
QgsMarkerLineSymbolLayer())
s3.symbolLayer(0).setRingFilter(QgsLineSymbolLayer.ExteriorRingOnly)
s3.symbolLayer(0).setAverageAngleLength(0)
g = QgsGeometry.fromWkt('Polygon((0 0, 10 0, 10 10, 0 10, 0 0),(1 1, 1 2, 2 2, 2 1, 1 1),(8 8, 9 8, 9 9, 8 9, 8 8))')
rendered_image = self.renderGeometry(s3, g)
@ -164,6 +166,7 @@ class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
marker_line.setAverageAngleLength(0)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, marker_line)
sym_layer.setSubSymbol(line_symbol)
@ -181,6 +184,97 @@ class TestQgsMarkerLineSymbolLayer(unittest.TestCase):
rendered_image = self.renderGeometry(s, g, buffer=4)
assert self.imageCheck('part_count_variable', 'part_count_variable', rendered_image)
def testMarkerAverageAngle(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
marker_line = QgsMarkerLineSymbolLayer(True)
marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
marker_line.setInterval(6)
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
marker.setColor(QColor(255, 0, 0))
marker.setStrokeStyle(Qt.NoPen)
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
marker_line.setAverageAngleLength(60)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, marker_line)
s.appendSymbolLayer(marker_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('markerline_average_angle', 'markerline_average_angle', rendered_image)
def testMarkerAverageAngleRing(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
marker_line = QgsMarkerLineSymbolLayer(True)
marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
marker_line.setInterval(6)
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
marker.setColor(QColor(255, 0, 0))
marker.setStrokeStyle(Qt.NoPen)
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
marker_line.setAverageAngleLength(60)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, marker_line)
s.appendSymbolLayer(marker_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10, 10 0, 0 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('markerline_ring_average_angle', 'markerline_ring_average_angle', rendered_image)
def testMarkerAverageAngleCenter(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
marker_line = QgsMarkerLineSymbolLayer(True)
marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.CentralPoint)
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Triangle, 4)
marker.setColor(QColor(255, 0, 0))
marker.setStrokeStyle(Qt.NoPen)
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
marker_line.setAverageAngleLength(60)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, marker_line)
s.appendSymbolLayer(marker_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 10 10, 10 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('markerline_center_average_angle', 'markerline_center_average_angle', rendered_image)
def testRingNoDupe(self):
s = QgsLineSymbol()
s.deleteSymbolLayer(0)
marker_line = QgsMarkerLineSymbolLayer(True)
marker_line.setPlacement(QgsTemplatedLineSymbolLayerBase.Interval)
marker_line.setInterval(10)
marker_line.setIntervalUnit(QgsUnitTypes.RenderMapUnits)
marker = QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayer.Circle, 4)
marker.setColor(QColor(255, 0, 0, 100))
marker.setStrokeStyle(Qt.NoPen)
marker_symbol = QgsMarkerSymbol()
marker_symbol.changeSymbolLayer(0, marker)
marker_line.setSubSymbol(marker_symbol)
line_symbol = QgsLineSymbol()
line_symbol.changeSymbolLayer(0, marker_line)
s.appendSymbolLayer(marker_line.clone())
g = QgsGeometry.fromWkt('LineString(0 0, 0 10, 10 10, 10 0, 0 0)')
rendered_image = self.renderGeometry(s, g)
assert self.imageCheck('markerline_ring_no_dupes', 'markerline_ring_no_dupes', rendered_image)
def renderGeometry(self, symbol, geom, buffer=20):
f = QgsFeature()
f.setGeometry(geom)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B