Add a new "arrow" symbol layer

The "arrow" symbol layer is a symbol layer allowing to draw straight
or curved arrows from a line layer.
This commit is contained in:
Hugo Mercier 2016-01-20 09:11:48 +01:00
parent 81744ecf90
commit 34b7ebc2b5
17 changed files with 1539 additions and 0 deletions

View File

@ -305,6 +305,7 @@
%Include symbology-ng/qgssymbollayerv2registry.sip
%Include symbology-ng/qgssymbollayerv2utils.sip
%Include symbology-ng/qgsvectorfieldsymbollayer.sip
%Include symbology-ng/qgsarrowsymbollayer.sip
%Include symbology-ng/qgssymbologyv2conversion.sip

View File

@ -0,0 +1,111 @@
class QgsArrowSymbolLayer : public QgsLineSymbolLayerV2
{
%TypeHeaderCode
#include <qgsarrowsymbollayer.h>
%End
public:
/** Simple constructor */
QgsArrowSymbolLayer();
/**
* Create a new QgsArrowSymbolLayer
*
* @param properties A property map to deserialize saved information from properties()
*
* @return A new QgsArrowSymbolLayer
*/
static QgsSymbolLayerV2* create( const QgsStringMap& properties ) /Factory/;
/** Virtual constructor */
virtual QgsArrowSymbolLayer* clone() const /Factory/;
/** Get the sub symbol used for filling */
virtual QgsSymbolV2* subSymbol();
/** Set the sub symbol used for filling */
virtual bool setSubSymbol( QgsSymbolV2* symbol );
/** Return a list of attributes required to render this feature */
virtual QSet<QString> usedAttributes() const;
/** Get current arrow width */
double arrowWidth() const;
/** Set the arrow width */
void setArrowWidth( double w );
/** Get the unit for the arrow width */
QgsSymbolV2::OutputUnit arrowWidthUnit() const;
/** Set the unit for the arrow width */
void setArrowWidthUnit( QgsSymbolV2::OutputUnit u );
/** Get the scale for the arrow width */
QgsMapUnitScale arrowWidthUnitScale() const;
/** Set the scale for the arrow width */
void setArrowWidthUnitScale( const QgsMapUnitScale& s );
/** Get current arrow start width. Only meaningfull for single headed arrows */
double arrowStartWidth() const;
/** Set the arrow start width */
void setArrowStartWidth( double w );
/** Get the unit for the arrow start width */
QgsSymbolV2::OutputUnit arrowStartWidthUnit() const;
/** Set the unit for the arrow start width */
void setArrowStartWidthUnit( QgsSymbolV2::OutputUnit u );
/** Get the scale for the arrow start width */
QgsMapUnitScale arrowStartWidthUnitScale() const;
/** Set the scale for the arrow start width */
void setArrowStartWidthUnitScale( const QgsMapUnitScale& s );
/** Get the current arrow head size */
double headSize() const;
/** Set the arrow head size */
void setHeadSize( double s );
/** Get the unit for the head size */
QgsSymbolV2::OutputUnit headSizeUnit() const;
/** Set the unit for the head size */
void setHeadSizeUnit( QgsSymbolV2::OutputUnit u );
/** Get the scale for the head size */
QgsMapUnitScale headSizeUnitScale() const;
/** Set the scale for the head size */
void setHeadSizeUnitScale( const QgsMapUnitScale& s );
/** Return whether it is a curved arrow or a straight one */
bool isCurved() const;
/** Set whether it is a curved arrow or a straight one */
void setIsCurved( bool isCurved );
/** Possible head types */
enum HeadType
{
HeadSingle, //< One single head at the end
HeadReversed, //< One single head at the beginning
HeadDouble //< Two heads
};
/** Get the current head type */
HeadType headType() const;
/** Set the head type */
void setHeadType( HeadType t );
/**
* Should be reimplemented by subclasses to return a string map that
* contains the configuration information for the symbol layer. This
* is used to serialize a symbol layer perstistently.
*/
QgsStringMap properties() const;
/**
* Returns a string that represents this layer type. Used for serialization.
* Should match with the string used to register this symbol layer in the registry.
*/
QString layerType() const;
/** Prepare the rendering */
void startRender( QgsSymbolV2RenderContext& context );
/** End of the rendering */
void stopRender( QgsSymbolV2RenderContext& context );
/** Main drawing method */
void renderPolyline( const QPolygonF& points, QgsSymbolV2RenderContext& context );
private:
QgsArrowSymbolLayer( const QgsArrowSymbolLayer& );
};

View File

@ -198,6 +198,7 @@
%Include symbology-ng/characterwidget.sip
%Include symbology-ng/qgs25drendererwidget.sip
%Include symbology-ng/qgsarrowsymbollayerwidget.sip
%Include symbology-ng/qgsbrushstylecombobox.sip
%Include symbology-ng/qgscategorizedsymbolrendererv2widget.sip
%Include symbology-ng/qgscolorrampcombobox.sip

View File

@ -0,0 +1,22 @@
class QgsArrowSymbolLayerWidget: QgsSymbolLayerV2Widget
{
%TypeHeaderCode
#include <qgsarrowsymbollayerwidget.h>
%End
public:
/** Constructor
* @param layer the layer where this symbol layer is applied
* @param parent the parent widget
*/
QgsArrowSymbolLayerWidget( const QgsVectorLayer* layer, QWidget* parent /TransferThis/ = 0 );
/** Static creation method
* @param layer the layer where this symbol layer is applied
*/
static QgsSymbolLayerV2Widget* create( const QgsVectorLayer* layer ) /Factory/;
/** Set the symbol layer */
virtual void setSymbolLayer( QgsSymbolLayerV2* layer );
/** Get the current symbol layer */
virtual QgsSymbolLayerV2* symbolLayer();
};

View File

@ -16,6 +16,7 @@ SET(QGIS_CORE_SRCS
gps/tok.c
symbology-ng/qgs25drenderer.cpp
symbology-ng/qgsarrowsymbollayer.cpp
symbology-ng/qgscategorizedsymbolrendererv2.cpp
symbology-ng/qgscolorbrewerpalette.cpp
symbology-ng/qgscptcityarchive.cpp

View File

@ -0,0 +1,594 @@
/***************************************************************************
qgsarrowsymbollayer.cpp
---------------------
begin : January 2016
copyright : (C) 2016 by Hugo Mercier
email : hugo dot mercier at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsarrowsymbollayer.h"
QgsArrowSymbolLayer::QgsArrowSymbolLayer()
: QgsLineSymbolLayerV2()
, mArrowWidth( 1.0 )
, mArrowWidthUnit( QgsSymbolV2::MM )
, mArrowStartWidth( 1.0 )
, mArrowStartWidthUnit( QgsSymbolV2::MM )
, mHeadSize( 1.5 )
, mHeadSizeUnit( QgsSymbolV2::MM )
, mHeadType( HeadSingle )
, mIsCurved( true )
{
/* default values */
setOffset( 0.0 );
setOffsetUnit( QgsSymbolV2::MM );
mSymbol.reset( static_cast<QgsFillSymbolV2*>( QgsFillSymbolV2::createSimple( QgsStringMap() ) ) );
}
bool QgsArrowSymbolLayer::setSubSymbol( QgsSymbolV2* symbol )
{
if ( symbol && symbol->type() == QgsSymbolV2::Fill )
{
mSymbol.reset( static_cast<QgsFillSymbolV2*>( symbol ) );
return true;
}
return false;
}
QgsSymbolLayerV2* QgsArrowSymbolLayer::create( const QgsStringMap& props )
{
QgsArrowSymbolLayer* l = new QgsArrowSymbolLayer();
if ( props.contains( "arrow_width" ) )
l->setArrowWidth( props["arrow_width"].toDouble() );
if ( props.contains( "arrow_width_unit" ) )
l->setArrowWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["arrow_width_unit"] ) );
if ( props.contains( "arrow_width_unit_scale" ) )
l->setArrowWidthUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["arrow_width_unit_scale"] ) );
if ( props.contains( "arrow_start_width" ) )
l->setArrowStartWidth( props["arrow_start_width"].toDouble() );
if ( props.contains( "arrow_start_width_unit" ) )
l->setArrowStartWidthUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["arrow_start_width_unit"] ) );
if ( props.contains( "arrow_start_width_unit_scale" ) )
l->setArrowStartWidthUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["arrow_start_width_unit_scale"] ) );
if ( props.contains( "is_curved" ) )
l->setIsCurved( props["is_curved"].toInt() == 1 );
if ( props.contains( "head_size" ) )
l->setHeadSize( props["head_size"].toDouble() );
if ( props.contains( "head_size_unit" ) )
l->setHeadSizeUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["head_size_unit"] ) );
if ( props.contains( "head_size_unit_scale" ) )
l->setHeadSizeUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["head_size_unit_scale"] ) );
if ( props.contains( "head_type" ) )
l->setHeadType( static_cast<HeadType>( props["head_type"].toInt() ) );
if ( props.contains( "offset" ) )
l->setOffset( props["offset"].toDouble() );
if ( props.contains( "offset_unit" ) )
l->setOffsetUnit( QgsSymbolLayerV2Utils::decodeOutputUnit( props["offset_unit"] ) );
if ( props.contains( "offset_unit_scale" ) )
l->setOffsetMapUnitScale( QgsSymbolLayerV2Utils::decodeMapUnitScale( props["offset_unit_scale"] ) );
l->restoreDataDefinedProperties( props );
l->setSubSymbol( QgsFillSymbolV2::createSimple( props ) );
return l;
}
QgsArrowSymbolLayer* QgsArrowSymbolLayer::clone() const
{
QgsArrowSymbolLayer* l = static_cast<QgsArrowSymbolLayer*>( create( properties() ) );
l->setSubSymbol( mSymbol->clone() );
copyDataDefinedProperties( l );
copyPaintEffect( l );
return l;
}
QString QgsArrowSymbolLayer::layerType() const
{
return "ArrowLine";
}
QgsStringMap QgsArrowSymbolLayer::properties() const
{
QgsStringMap map;
map["arrow_width"] = QString::number( arrowWidth() );
map["arrow_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( arrowWidthUnit() );
map["arrow_width_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( arrowWidthUnitScale() );
map["arrow_start_width"] = QString::number( arrowStartWidth() );
map["arrow_start_width_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( arrowStartWidthUnit() );
map["arrow_start_width_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( arrowStartWidthUnitScale() );
map["is_curved"] = QString::number( isCurved() ? 1 : 0 );
map["head_size"] = QString::number( headSize() );
map["head_size_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( headSizeUnit() );
map["head_size_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( headSizeUnitScale() );
map["head_type"] = QString::number( headType() );
map["offset"] = QString::number( offset() );
map["offset_unit"] = QgsSymbolLayerV2Utils::encodeOutputUnit( offsetUnit() );
map["offset_unit_scale"] = QgsSymbolLayerV2Utils::encodeMapUnitScale( offsetMapUnitScale() );
saveDataDefinedProperties( map );
return map;
}
QSet<QString> QgsArrowSymbolLayer::usedAttributes() const
{
QSet<QString> attributes = QgsLineSymbolLayerV2::usedAttributes();
attributes.unite( mSymbol->usedAttributes() );
return attributes;
}
void QgsArrowSymbolLayer::startRender( QgsSymbolV2RenderContext& context )
{
mScaledArrowWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), arrowWidth(), arrowWidthUnit(), arrowWidthUnitScale() );
mScaledArrowStartWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), arrowStartWidth(), arrowStartWidthUnit(), arrowStartWidthUnitScale() );
mScaledHeadSize = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), headSize(), headSizeUnit(), headSizeUnitScale() );
mScaledOffset = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), offset(), offsetUnit(), offsetMapUnitScale() );
mComputedHeadType = headType();
mSymbol->startRender( context.renderContext() );
}
void QgsArrowSymbolLayer::stopRender( QgsSymbolV2RenderContext& context )
{
mSymbol->stopRender( context.renderContext() );
}
inline qreal euclidian_distance( const QPointF& po, const QPointF& pd )
{
return sqrt(( po.x() - pd.x() ) * ( po.x() - pd.x() ) + ( po.y() - pd.y() ) * ( po.y() - pd.y() ) );
}
QPolygonF straightArrow( QPointF po, QPointF pd, qreal startWidth, qreal width, qreal headSize, QgsArrowSymbolLayer::HeadType headType, qreal offset )
{
QPolygonF polygon; // implicitly shared
// vector length
qreal length = euclidian_distance( po, pd );
// shift points if there is not enough room for the head(s)
if (( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headSize ) )
{
po = pd - ( pd - po ) / length * headSize;
length = headSize;
}
else if (( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headSize ) )
{
pd = po + ( pd - po ) / length * headSize;
length = headSize;
}
else if (( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2 * headSize ) )
{
QPointF v = ( pd - po ) / length * headSize;
QPointF npo = ( po + pd ) / 2.0 - v;
QPointF npd = ( po + pd ) / 2.0 + v;
po = npo;
pd = npd;
length = 2 * headSize;
}
qreal bodyLength = length - headSize;
// unit vector
QPointF unitVec = ( pd - po ) / length;
// perpendicular vector
QPointF perpVec( -unitVec.y(), unitVec.x() );
// set offset
po += perpVec * offset;
pd += perpVec * offset;
if ( headType == QgsArrowSymbolLayer::HeadDouble )
{
// first head
polygon << po;
polygon << po + unitVec * headSize + perpVec * headSize;
polygon << po + unitVec * headSize + perpVec * ( width * 0.5 );
polygon << po + unitVec * bodyLength + perpVec * ( width * 0.5 );
// second head
polygon << po + unitVec * bodyLength + perpVec * headSize;
polygon << pd;
polygon << po + unitVec * bodyLength - perpVec * headSize;
polygon << po + unitVec * bodyLength - perpVec * ( width * 0.5 );
// end of the first head
polygon << po + unitVec * headSize - perpVec * ( width * 0.5 );
polygon << po + unitVec * headSize - perpVec * headSize;
}
else if ( headType == QgsArrowSymbolLayer::HeadSingle )
{
polygon << po - perpVec * ( startWidth * 0.5 );
polygon << po + perpVec * ( startWidth * 0.5 );
polygon << po + unitVec * bodyLength + perpVec * ( width * 0.5 );
polygon << po + unitVec * bodyLength + perpVec * headSize;
polygon << pd;
polygon << po + unitVec * bodyLength - perpVec * headSize;
polygon << po + unitVec * bodyLength - perpVec * ( width * 0.5 );
}
else if ( headType == QgsArrowSymbolLayer::HeadReversed )
{
// first head
polygon << po;
polygon << po + unitVec * headSize + perpVec * headSize;
polygon << po + unitVec * headSize + perpVec * ( width * 0.5 );
polygon << pd + perpVec * ( startWidth * 0.5 );
polygon << pd - perpVec * ( startWidth * 0.5 );
polygon << po + unitVec * headSize - perpVec * ( width * 0.5 );
polygon << po + unitVec * headSize - perpVec * headSize;
}
// close the polygon
polygon << polygon.first();
return polygon;
}
// Make sure a given angle is between 0 and 2 pi
inline qreal clampAngle( qreal a )
{
if ( a > 2 * M_PI )
return a - 2 * M_PI;
if ( a < 0.0 )
return a + 2 * M_PI;
return a;
}
/**
* Compute the circumscribed circle from three points
* @return false if the three points are colinear
*/
bool pointsToCircle( const QPointF& a, const QPointF& b, const QPointF& c, QPointF& center, qreal& radius )
{
qreal cx, cy;
// AB and BC vectors
QPointF ab = b - a;
QPointF bc = c - b;
// AB and BC middles
QPointF ab2 = ( a + b ) / 2.0;
QPointF bc2 = ( b + c ) / 2.0;
// Aligned points
if ( fabs( ab.x() * bc.y() - ab.y() * bc.x() ) < 0.001 ) // Empirical threshold for nearly aligned points
return false;
// in case AB is horizontal
if ( ab.y() == 0 )
{
cx = ab2.x();
cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
}
//# BC horizontal
else if ( bc.y() == 0 )
{
cx = bc2.x();
cy = ab2.y() - ( cx - ab2.x() ) * ab.x() / ab.y();
}
// Otherwise
else
{
cx = ( bc2.y() - ab2.y() + bc.x() * bc2.x() / bc.y() - ab.x() * ab2.x() / ab.y() ) / ( bc.x() / bc.y() - ab.x() / ab.y() );
cy = bc2.y() - ( cx - bc2.x() ) * bc.x() / bc.y();
}
// Radius
radius = sqrt(( a.x() - cx ) * ( a.x() - cx ) + ( a.y() - cy ) * ( a.y() - cy ) );
// Center
center.setX( cx );
center.setY( cy );
return true;
}
QPointF circlePoint( const QPointF& center, qreal radius, qreal angle )
{
// Y is oriented downward
return QPointF( cos( -angle ) * radius + center.x(), sin( -angle ) * radius + center.y() );
}
void pathArcTo( QPainterPath& path, const QPointF& circleCenter, qreal circleRadius, qreal angle_o, qreal angle_d, int direction )
{
QRectF circleRect( circleCenter - QPointF( circleRadius, circleRadius ), circleCenter + QPointF( circleRadius, circleRadius ) );
if ( direction == 1 )
{
if ( angle_o < angle_d )
path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
else
path.arcTo( circleRect, angle_o / M_PI * 180.0, 360.0 - ( angle_o - angle_d ) / M_PI * 180.0 );
}
else
{
if ( angle_o < angle_d )
path.arcTo( circleRect, angle_o / M_PI * 180.0, - ( 360.0 - ( angle_d - angle_o ) / M_PI * 180.0 ) );
else
path.arcTo( circleRect, angle_o / M_PI * 180.0, ( angle_d - angle_o ) / M_PI * 180.0 );
}
}
// Draw a "spiral" arc defined by circle arcs around a center, a start and an end radius
void spiralArcTo( QPainterPath& path, const QPointF& center, qreal startAngle, qreal startRadius, qreal endAngle, qreal endRadius, int direction )
{
// start point
QPointF A = circlePoint( center, startRadius, startAngle );
// end point
QPointF B = circlePoint( center, endRadius, endAngle );
// middle points
qreal deltaAngle;
deltaAngle = endAngle - startAngle;
if ( direction * deltaAngle < 0.0 )
deltaAngle = deltaAngle + direction * 2 * M_PI;
QPointF I1 = circlePoint( center, 0.75 * startRadius + 0.25 * endRadius, startAngle + 0.25 * deltaAngle );
QPointF I2 = circlePoint( center, 0.50 * startRadius + 0.50 * endRadius, startAngle + 0.50 * deltaAngle );
QPointF I3 = circlePoint( center, 0.25 * startRadius + 0.75 * endRadius, startAngle + 0.75 * deltaAngle );
qreal cRadius;
QPointF cCenter;
// first circle arc
if ( ! pointsToCircle( A, I1, I2, cCenter, cRadius ) )
{
// aligned points => draw a straight line
path.lineTo( I2 );
}
else
{
// angles in the new circle
qreal a1 = atan2( cCenter.y() - A.y(), A.x() - cCenter.x() );
qreal a2 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
pathArcTo( path, cCenter, cRadius, a1, a2, direction );
}
// second circle arc
if ( ! pointsToCircle( I2, I3, B, cCenter, cRadius ) )
{
// aligned points => draw a straight line
path.lineTo( B );
}
else
{
// angles in the new circle
qreal a1 = atan2( cCenter.y() - I2.y(), I2.x() - cCenter.x() );
qreal a2 = atan2( cCenter.y() - B.y(), B.x() - cCenter.x() );
pathArcTo( path, cCenter, cRadius, a1, a2, direction );
}
}
QPolygonF curvedArrow( QPointF po, QPointF pm, QPointF pd, qreal startWidth, qreal width, qreal headSize, QgsArrowSymbolLayer::HeadType headType, qreal offset )
{
qreal circleRadius;
QPointF circleCenter;
if ( ! pointsToCircle( po, pm, pd, circleCenter, circleRadius ) || circleRadius > 10000.0 )
{
// aligned points => draw a straight arrow
return straightArrow( po, pd, startWidth, width, headSize, headType, offset );
}
// angles of each point
qreal angle_o = clampAngle( atan2( circleCenter.y() - po.y(), po.x() - circleCenter.x() ) );
qreal angle_m = clampAngle( atan2( circleCenter.y() - pm.y(), pm.x() - circleCenter.x() ) );
qreal angle_d = clampAngle( atan2( circleCenter.y() - pd.y(), pd.x() - circleCenter.x() ) );
// arc direction : 1 = counter-clockwise, -1 = clockwise
int direction = clampAngle( angle_m - angle_o ) < clampAngle( angle_m - angle_d ) ? 1 : -1;
qreal deltaAngle = angle_d - angle_o;
if ( direction * deltaAngle < 0.0 )
deltaAngle = deltaAngle + direction * 2 * M_PI;
qreal length = euclidian_distance( po, pd );
// for close points and deltaAngle < 180, draw a straight line
if ( fabs( deltaAngle ) < M_PI && ((( headType == QgsArrowSymbolLayer::HeadSingle ) && ( length < headSize ) ) ||
(( headType == QgsArrowSymbolLayer::HeadReversed ) && ( length < headSize ) ) ||
(( headType == QgsArrowSymbolLayer::HeadDouble ) && ( length < 2*headSize ) ) ) )
{
return straightArrow( po, pd, startWidth, width, headSize, headType, offset );
}
// ajust coordinates to include offset
circleRadius += offset;
po = circlePoint( circleCenter, circleRadius, angle_o );
pm = circlePoint( circleCenter, circleRadius, angle_m );
pd = circlePoint( circleCenter, circleRadius, angle_d );
qreal headAngle = direction * atan( headSize / circleRadius );
QPainterPath path;
if ( headType == QgsArrowSymbolLayer::HeadDouble )
{
// the first head
path.moveTo( po );
path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_o + headAngle ) );
pathArcTo( path, circleCenter, circleRadius + direction * width / 2, angle_o + headAngle, angle_d - headAngle, direction );
// the second head
path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_d - headAngle ) );
path.lineTo( pd );
path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_d - headAngle ) );
pathArcTo( path, circleCenter, circleRadius - direction * width / 2, angle_d - headAngle, angle_o + headAngle, -direction );
// the end of the first head
path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_o + headAngle ) );
path.lineTo( po );
}
else if ( headType == QgsArrowSymbolLayer::HeadSingle )
{
path.moveTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
spiralArcTo( path, circleCenter, angle_o, circleRadius + direction * startWidth / 2, angle_d - headAngle, circleRadius + direction * width / 2, direction );
// the arrow head
path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_d - headAngle ) );
path.lineTo( pd );
path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_d - headAngle ) );
spiralArcTo( path, circleCenter, angle_d - headAngle, circleRadius - direction * width / 2, angle_o, circleRadius - direction * startWidth / 2, -direction );
path.lineTo( circlePoint( circleCenter, circleRadius + direction * startWidth / 2, angle_o ) );
}
else if ( headType == QgsArrowSymbolLayer::HeadReversed )
{
path.moveTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
spiralArcTo( path, circleCenter, angle_o + headAngle, circleRadius + direction * width / 2, angle_d, circleRadius + direction * startWidth / 2, direction );
path.lineTo( circlePoint( circleCenter, circleRadius - direction * startWidth / 2, angle_d ) );
spiralArcTo( path, circleCenter, angle_d, circleRadius - direction * startWidth / 2, angle_o + headAngle, circleRadius - direction * width / 2, - direction );
path.lineTo( circlePoint( circleCenter, circleRadius - direction * headSize, angle_o + headAngle ) );
path.lineTo( po );
path.lineTo( circlePoint( circleCenter, circleRadius + direction * headSize, angle_o + headAngle ) );
path.lineTo( circlePoint( circleCenter, circleRadius + direction * width / 2, angle_o + headAngle ) );
}
return path.toSubpathPolygons()[0];
}
void QgsArrowSymbolLayer::_resolveDataDefined( QgsSymbolV2RenderContext& context )
{
if ( !hasDataDefinedProperties() )
return; // shortcut if case there is no data defined properties at all
bool ok;
if ( hasDataDefinedProperty( "arrow_width" ) )
{
context.setOriginalValueVariable( arrowWidth() );
double w = evaluateDataDefinedProperty( "arrow_width", context, QVariant(), &ok ).toDouble();
if ( ok )
{
mScaledArrowWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), w, arrowWidthUnit(), arrowWidthUnitScale() );
}
}
if ( hasDataDefinedProperty( "arrow_start_width" ) )
{
context.setOriginalValueVariable( arrowStartWidth() );
double w = evaluateDataDefinedProperty( "arrow_start_width", context, QVariant(), &ok ).toDouble();
if ( ok )
{
mScaledArrowStartWidth = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), w, arrowStartWidthUnit(), arrowStartWidthUnitScale() );
}
}
if ( hasDataDefinedProperty( "head_size" ) )
{
context.setOriginalValueVariable( headSize() );
double w = evaluateDataDefinedProperty( "head_size", context, QVariant(), &ok ).toDouble();
if ( ok )
{
mScaledHeadSize = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), w, headSizeUnit(), headSizeUnitScale() );
}
}
if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET ) )
{
context.setOriginalValueVariable( offset() );
double w = evaluateDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET, context, QVariant(), &ok ).toDouble();
if ( ok )
{
mScaledOffset = QgsSymbolLayerV2Utils::convertToPainterUnits( context.renderContext(), w, offsetUnit(), offsetMapUnitScale() );
}
}
if ( hasDataDefinedProperty( "head_type" ) )
{
context.setOriginalValueVariable( headType() );
int h = evaluateDataDefinedProperty( "head_type", context, QVariant(), &ok ).toInt();
if ( ok )
{
mComputedHeadType = static_cast<HeadType>( h );
}
}
}
void QgsArrowSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbolV2RenderContext& context )
{
Q_UNUSED( points );
if ( !context.renderContext().painter() )
{
return;
}
if ( isCurved() )
{
for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
{
_resolveDataDefined( context );
if ( points.size() - pIdx >= 3 )
{
// origin point
QPointF po( points.at( pIdx ) );
// middle point
QPointF pm( points.at( pIdx + 1 ) );
// destination point
QPointF pd( points.at( pIdx + 2 ) );
QPolygonF poly = curvedArrow( po, pm, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadSize, mComputedHeadType, mScaledOffset );
mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext() );
}
// straight arrow
else if ( points.size() - pIdx == 2 )
{
// origin point
QPointF po( points.at( pIdx ) );
// destination point
QPointF pd( points.at( pIdx + 1 ) );
QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadSize, mComputedHeadType, mScaledOffset );
mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext() );
}
}
}
else
{
// only straight arrows
for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
{
_resolveDataDefined( context );
// origin point
QPointF po( points.at( pIdx ) );
// destination point
QPointF pd( points.at( pIdx + 1 ) );
QPolygonF poly = straightArrow( po, pd, mScaledArrowStartWidth, mScaledArrowWidth, mScaledHeadSize, mComputedHeadType, mScaledOffset );
mSymbol->renderPolygon( poly, /* rings */ nullptr, context.feature(), context.renderContext() );
}
}
}

View File

@ -0,0 +1,159 @@
/***************************************************************************
qgsarrowsymbollayer.h
---------------------
begin : January 2016
copyright : (C) 2016 by Hugo Mercier
email : hugo dot mercier at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSARROWSYMBOLLAYERV2_H
#define QGSARROWSYMBOLLAYERV2_H
#include "qgssymbollayerv2.h"
/**
* This class is used for representing lines as arrows.
*/
class CORE_EXPORT QgsArrowSymbolLayer : public QgsLineSymbolLayerV2
{
public:
/** Simple constructor */
QgsArrowSymbolLayer();
/**
* Create a new QgsArrowSymbolLayer
*
* @param properties A property map to deserialize saved information from properties()
*
* @return A new QgsArrowSymbolLayer
*/
static QgsSymbolLayerV2* create( const QgsStringMap& properties );
/** Virtual constructor */
virtual QgsArrowSymbolLayer* clone() const override;
/** Get the sub symbol used for filling */
virtual QgsSymbolV2* subSymbol() override { return mSymbol.data(); }
/** Set the sub symbol used for filling. Takes ownership. */
virtual bool setSubSymbol( QgsSymbolV2* symbol ) override;
/** Return a list of attributes required to render this feature */
virtual QSet<QString> usedAttributes() const override;
/** Get current arrow width */
double arrowWidth() const { return mArrowWidth; }
/** Set the arrow width */
void setArrowWidth( double w ) { mArrowWidth = w; }
/** Get the unit for the arrow width */
QgsSymbolV2::OutputUnit arrowWidthUnit() const { return mArrowWidthUnit; }
/** Set the unit for the arrow width */
void setArrowWidthUnit( QgsSymbolV2::OutputUnit u ) { mArrowWidthUnit = u; }
/** Get the scale for the arrow width */
QgsMapUnitScale arrowWidthUnitScale() const { return mArrowWidthUnitScale; }
/** Set the scale for the arrow width */
void setArrowWidthUnitScale( const QgsMapUnitScale& s ) { mArrowWidthUnitScale = s; }
/** Get current arrow start width. Only meaningfull for single headed arrows */
double arrowStartWidth() const { return mArrowStartWidth; }
/** Set the arrow start width */
void setArrowStartWidth( double w ) { mArrowStartWidth = w; }
/** Get the unit for the arrow start width */
QgsSymbolV2::OutputUnit arrowStartWidthUnit() const { return mArrowStartWidthUnit; }
/** Set the unit for the arrow start width */
void setArrowStartWidthUnit( QgsSymbolV2::OutputUnit u ) { mArrowStartWidthUnit = u; }
/** Get the scale for the arrow start width */
QgsMapUnitScale arrowStartWidthUnitScale() const { return mArrowStartWidthUnitScale; }
/** Set the scale for the arrow start width */
void setArrowStartWidthUnitScale( const QgsMapUnitScale& s ) { mArrowStartWidthUnitScale = s; }
/** Get the current arrow head size */
double headSize() const { return mHeadSize; }
/** Set the arrow head size */
void setHeadSize( double s ) { mHeadSize = s; }
/** Get the unit for the head size */
QgsSymbolV2::OutputUnit headSizeUnit() const { return mHeadSizeUnit; }
/** Set the unit for the head size */
void setHeadSizeUnit( QgsSymbolV2::OutputUnit u ) { mHeadSizeUnit = u; }
/** Get the scale for the head size */
QgsMapUnitScale headSizeUnitScale() const { return mHeadSizeUnitScale; }
/** Set the scale for the head size */
void setHeadSizeUnitScale( const QgsMapUnitScale& s ) { mHeadSizeUnitScale = s; }
/** Return whether it is a curved arrow or a straight one */
bool isCurved() const { return mIsCurved; }
/** Set whether it is a curved arrow or a straight one */
void setIsCurved( bool isCurved ) { mIsCurved = isCurved; }
/** Possible head types */
enum HeadType
{
HeadSingle, //< One single head at the end
HeadReversed, //< One single head at the beginning
HeadDouble //< Two heads
};
/** Get the current head type */
HeadType headType() const { return mHeadType; }
/** Set the head type */
void setHeadType( HeadType t ) { mHeadType = t; }
/**
* Should be reimplemented by subclasses to return a string map that
* contains the configuration information for the symbol layer. This
* is used to serialize a symbol layer perstistently.
*/
QgsStringMap properties() const override;
/**
* Returns a string that represents this layer type. Used for serialization.
* Should match with the string used to register this symbol layer in the registry.
*/
QString layerType() const override;
/** Prepare the rendering */
void startRender( QgsSymbolV2RenderContext& context ) override;
/** End of the rendering */
void stopRender( QgsSymbolV2RenderContext& context ) override;
/** Main drawing method */
void renderPolyline( const QPolygonF& points, QgsSymbolV2RenderContext& context ) override;
private:
/** Filling sub symbol */
QScopedPointer<QgsFillSymbolV2> mSymbol;
double mArrowWidth;
QgsSymbolV2::OutputUnit mArrowWidthUnit;
QgsMapUnitScale mArrowWidthUnitScale;
double mArrowStartWidth;
QgsSymbolV2::OutputUnit mArrowStartWidthUnit;
QgsMapUnitScale mArrowStartWidthUnitScale;
double mHeadSize;
QgsSymbolV2::OutputUnit mHeadSizeUnit;
QgsMapUnitScale mHeadSizeUnitScale;
HeadType mHeadType;
bool mIsCurved;
double mScaledArrowWidth;
double mScaledArrowStartWidth;
double mScaledHeadSize;
double mScaledOffset;
HeadType mComputedHeadType;
void _resolveDataDefined( QgsSymbolV2RenderContext& );
};
#endif

View File

@ -15,6 +15,7 @@
#include "qgssymbollayerv2registry.h"
#include "qgsarrowsymbollayer.h"
#include "qgsellipsesymbollayerv2.h"
#include "qgsmarkersymbollayerv2.h"
#include "qgslinesymbollayerv2.h"
@ -29,6 +30,7 @@ QgsSymbolLayerV2Registry::QgsSymbolLayerV2Registry()
QgsSimpleLineSymbolLayerV2::create, QgsSimpleLineSymbolLayerV2::createFromSld ) );
addSymbolLayerType( new QgsSymbolLayerV2Metadata( "MarkerLine", QObject::tr( "Marker line" ), QgsSymbolV2::Line,
QgsMarkerLineSymbolLayerV2::create, QgsMarkerLineSymbolLayerV2::createFromSld ) );
addSymbolLayerType( new QgsSymbolLayerV2Metadata( "ArrowLine", QObject::tr( "Arrow" ), QgsSymbolV2::Line, QgsArrowSymbolLayer::create ) );
addSymbolLayerType( new QgsSymbolLayerV2Metadata( "SimpleMarker", QObject::tr( "Simple marker" ), QgsSymbolV2::Marker,
QgsSimpleMarkerSymbolLayerV2::create, QgsSimpleMarkerSymbolLayerV2::createFromSld ) );

View File

@ -10,6 +10,7 @@ SET(QGIS_GUI_SRCS
symbology-ng/qgs25drendererwidget.cpp
symbology-ng/characterwidget.cpp
symbology-ng/qgsarrowsymbollayerwidget.cpp
symbology-ng/qgsbrushstylecombobox.cpp
symbology-ng/qgscategorizedsymbolrendererv2widget.cpp
symbology-ng/qgscolorrampcombobox.cpp
@ -421,6 +422,7 @@ SET(QGIS_GUI_MOC_HDRS
symbology-ng/qgs25drendererwidget.h
symbology-ng/characterwidget.h
symbology-ng/qgsarrowsymbollayerwidget.h
symbology-ng/qgsbrushstylecombobox.h
symbology-ng/qgscategorizedsymbolrendererv2widget.h
symbology-ng/qgscolorrampcombobox.h

View File

@ -0,0 +1,174 @@
/***************************************************************************
qgsarrowsymbollayerwidget.cpp
---------------------
begin : February 2016
copyright : (C) 2016 by Hugo Mercier / Oslandia
email : hugo dot mercier at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsarrowsymbollayerwidget.h"
#include "qgsarrowsymbollayer.h"
#include "qgsvectorlayer.h"
#include <QColorDialog>
QgsArrowSymbolLayerWidget::QgsArrowSymbolLayerWidget( const QgsVectorLayer* vl, QWidget* parent )
: QgsSymbolLayerV2Widget( parent, vl )
, mLayer( nullptr )
{
setupUi( this );
mArrowWidthUnitWidget->setUnits( QgsSymbolV2::OutputUnitList() << QgsSymbolV2::MM << QgsSymbolV2::MapUnit << QgsSymbolV2::Pixel );
mArrowStartWidthUnitWidget->setUnits( QgsSymbolV2::OutputUnitList() << QgsSymbolV2::MM << QgsSymbolV2::MapUnit << QgsSymbolV2::Pixel );
mHeadSizeUnitWidget->setUnits( QgsSymbolV2::OutputUnitList() << QgsSymbolV2::MM << QgsSymbolV2::MapUnit << QgsSymbolV2::Pixel );
mOffsetUnitWidget->setUnits( QgsSymbolV2::OutputUnitList() << QgsSymbolV2::MM << QgsSymbolV2::MapUnit << QgsSymbolV2::Pixel );
mOffsetSpin->setClearValue( 0.0 );
}
void QgsArrowSymbolLayerWidget::setSymbolLayer( QgsSymbolLayerV2* layer )
{
if ( !layer || layer->layerType() != "ArrowLine" )
{
return;
}
mLayer = static_cast<QgsArrowSymbolLayer*>( layer );
mArrowWidthSpin->setValue( mLayer->arrowWidth() );
mArrowWidthUnitWidget->setUnit( mLayer->arrowWidthUnit() );
mArrowWidthUnitWidget->setMapUnitScale( mLayer->arrowWidthUnitScale() );
mArrowStartWidthSpin->setValue( mLayer->arrowStartWidth() );
mArrowStartWidthUnitWidget->setUnit( mLayer->arrowStartWidthUnit() );
mArrowStartWidthUnitWidget->setMapUnitScale( mLayer->arrowStartWidthUnitScale() );
mHeadSizeSpin->setValue( mLayer->headSize() );
mHeadSizeUnitWidget->setUnit( mLayer->headSizeUnit() );
mHeadSizeUnitWidget->setMapUnitScale( mLayer->headSizeUnitScale() );
mHeadTypeCombo->setCurrentIndex( mLayer->headType() );
mOffsetSpin->setValue( mLayer->offset() );
mOffsetUnitWidget->setUnit( mLayer->offsetUnit() );
mOffsetUnitWidget->setMapUnitScale( mLayer->offsetMapUnitScale() );
mCurvedArrowChck->setChecked( mLayer->isCurved() );
registerDataDefinedButton( mArrowWidthDDBtn, "arrow_width", QgsDataDefinedButton::Double, QgsDataDefinedButton::doubleDesc() );
registerDataDefinedButton( mArrowStartWidthDDBtn, "arrow_start_width", QgsDataDefinedButton::Double, QgsDataDefinedButton::doubleDesc() );
registerDataDefinedButton( mHeadSizeDDBtn, "head_size", QgsDataDefinedButton::Double, QgsDataDefinedButton::doubleDesc() );
registerDataDefinedButton( mHeadTypeDDBtn, "head_type", QgsDataDefinedButton::Int, QgsDataDefinedButton::intDesc() );
registerDataDefinedButton( mOffsetDDBtn, "offset", QgsDataDefinedButton::String, QgsDataDefinedButton::doubleDesc() );
}
QgsSymbolLayerV2* QgsArrowSymbolLayerWidget::symbolLayer()
{
return mLayer;
}
void QgsArrowSymbolLayerWidget::on_mArrowWidthSpin_valueChanged( double d )
{
if ( !mLayer )
return;
mLayer->setArrowWidth( d );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mArrowStartWidthSpin_valueChanged( double d )
{
if ( !mLayer )
return;
mLayer->setArrowStartWidth( d );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mHeadSizeSpin_valueChanged( double d )
{
if ( !mLayer )
return;
mLayer->setHeadSize( d );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mArrowWidthUnitWidget_changed()
{
if ( !mLayer )
return;
mLayer->setArrowWidthUnit( mArrowWidthUnitWidget->unit() );
mLayer->setArrowWidthUnitScale( mArrowWidthUnitWidget->getMapUnitScale() );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mArrowStartWidthUnitWidget_changed()
{
if ( !mLayer )
return;
mLayer->setArrowStartWidthUnit( mArrowStartWidthUnitWidget->unit() );
mLayer->setArrowStartWidthUnitScale( mArrowStartWidthUnitWidget->getMapUnitScale() );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mHeadSizeUnitWidget_changed()
{
if ( !mLayer )
return;
mLayer->setHeadSizeUnit( mHeadSizeUnitWidget->unit() );
mLayer->setHeadSizeUnitScale( mHeadSizeUnitWidget->getMapUnitScale() );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mHeadTypeCombo_currentIndexChanged( int idx )
{
if ( !mLayer )
return;
QgsArrowSymbolLayer::HeadType t = static_cast<QgsArrowSymbolLayer::HeadType>( idx );
mLayer->setHeadType( t );
bool isSingle = t == QgsArrowSymbolLayer::HeadSingle || t == QgsArrowSymbolLayer::HeadReversed;
mArrowStartWidthDDBtn->setEnabled( isSingle );
mArrowStartWidthSpin->setEnabled( isSingle );
mArrowStartWidthUnitWidget->setEnabled( isSingle );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mOffsetSpin_valueChanged( double d )
{
if ( !mLayer )
return;
mLayer->setOffset( d );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mOffsetUnitWidget_changed()
{
if ( !mLayer )
return;
mLayer->setOffsetUnit( mOffsetUnitWidget->unit() );
mLayer->setOffsetMapUnitScale( mOffsetUnitWidget->getMapUnitScale() );
emit changed();
}
void QgsArrowSymbolLayerWidget::on_mCurvedArrowChck_stateChanged( int state )
{
if ( ! mLayer )
return;
mLayer->setIsCurved( state == Qt::Checked );
emit changed();
}

View File

@ -0,0 +1,65 @@
/***************************************************************************
qgsarrowsymbollayerwidget.h
---------------------
begin : February 2016
copyright : (C) 2016 by Hugo Mercier / Oslandia
email : hugo dot mercier at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSARROWSYMBOLLAYERWIDGET_H
#define QGSARROWSYMBOLLAYERWIDGET_H
#include "ui_qgsarrowsymbollayerwidgetbase.h"
#include "qgssymbollayerv2widget.h"
class QgsArrowSymbolLayer;
class GUI_EXPORT QgsArrowSymbolLayerWidget: public QgsSymbolLayerV2Widget, private Ui::QgsArrowSymbolLayerWidgetBase
{
Q_OBJECT
public:
/** Constructor
* @param layer the layer where this symbol layer is applied
* @param parent the parent widget
*/
QgsArrowSymbolLayerWidget( const QgsVectorLayer* layer, QWidget* parent = nullptr );
/** Static creation method
* @param layer the layer where this symbol layer is applied
*/
static QgsSymbolLayerV2Widget* create( const QgsVectorLayer* layer ) { return new QgsArrowSymbolLayerWidget( layer ); }
/** Set the symbol layer */
virtual void setSymbolLayer( QgsSymbolLayerV2* layer ) override;
/** Get the current symbol layer */
virtual QgsSymbolLayerV2* symbolLayer() override;
private:
QgsArrowSymbolLayer* mLayer;
private slots:
void on_mArrowWidthSpin_valueChanged( double d );
void on_mArrowWidthUnitWidget_changed();
void on_mArrowStartWidthSpin_valueChanged( double d );
void on_mArrowStartWidthUnitWidget_changed();
void on_mHeadSizeSpin_valueChanged( double d );
void on_mHeadSizeUnitWidget_changed();
void on_mHeadTypeCombo_currentIndexChanged( int );
void on_mOffsetSpin_valueChanged( double d );
void on_mOffsetUnitWidget_changed();
void on_mCurvedArrowChck_stateChanged( int );
};
#endif

View File

@ -28,6 +28,7 @@
#include "qgslogger.h"
#include "qgssymbollayerv2widget.h"
#include "qgsarrowsymbollayerwidget.h"
#include "qgsellipsesymbollayerv2widget.h"
#include "qgsvectorfieldsymbollayerwidget.h"
#include "qgssymbolv2.h" //for the unit
@ -60,6 +61,7 @@ static void _initWidgetFunctions()
_initWidgetFunction( "SimpleLine", QgsSimpleLineSymbolLayerV2Widget::create );
_initWidgetFunction( "MarkerLine", QgsMarkerLineSymbolLayerV2Widget::create );
_initWidgetFunction( "ArrowLine", QgsArrowSymbolLayerWidget::create );
_initWidgetFunction( "SimpleMarker", QgsSimpleMarkerSymbolLayerV2Widget::create );
_initWidgetFunction( "SvgMarker", QgsSvgMarkerSymbolLayerV2Widget::create );

View File

@ -0,0 +1,295 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsArrowSymbolLayerWidgetBase</class>
<widget class="QWidget" name="QgsArrowSymbolLayerWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>899</width>
<height>471</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Symbols clip the geometry to the current canvas extent by default. This could result in undesired renderings for this kind of symbol layer. Make sure to check the corresponding &amp;quot;Clip features to canvas extent&amp;quot; advanced option of the symbol.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="10" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="7" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QgsDoubleSpinBox" name="mHeadSizeSpin">
<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>999999999.999999046325684</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mHeadSizeDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mHeadSizeUnitWidget" native="true"/>
</item>
</layout>
</item>
<item row="9" column="1" colspan="2">
<widget class="QCheckBox" name="mCurvedArrowChck">
<property name="text">
<string>Curved arrows</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item row="8" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QgsDoubleSpinBox" name="mOffsetSpin">
<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>-99999999.989999994635582</double>
</property>
<property name="maximum">
<double>99999999.989999994635582</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mOffsetDDBtn">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mOffsetUnitWidget" native="true">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
</widget>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Head size</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_9">
<item>
<widget class="QgsDoubleSpinBox" name="mArrowStartWidthSpin">
<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>999999999.999999046325684</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mArrowStartWidthDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mArrowStartWidthUnitWidget" native="true"/>
</item>
</layout>
</item>
<item row="0" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QComboBox" name="mHeadTypeCombo">
<item>
<property name="text">
<string>Single</string>
</property>
</item>
<item>
<property name="text">
<string>Single, reversed</string>
</property>
</item>
<item>
<property name="text">
<string>Double</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mHeadTypeDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Offset</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Arrow width at start</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Head type</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Arrow width</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QgsDoubleSpinBox" name="mArrowWidthSpin">
<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>999999999.999999046325684</double>
</property>
<property name="singleStep">
<double>0.200000000000000</double>
</property>
<property name="showClearButton" stdset="0">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QgsDataDefinedButton" name="mArrowWidthDDBtn">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="QgsUnitSelectionWidget" name="mArrowWidthUnitWidget" native="true"/>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsDataDefinedButton</class>
<extends>QToolButton</extends>
<header>qgsdatadefinedbutton.h</header>
</customwidget>
<customwidget>
<class>QgsDoubleSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>qgsdoublespinbox.h</header>
</customwidget>
<customwidget>
<class>QgsUnitSelectionWidget</class>
<extends>QWidget</extends>
<header>qgsunitselectionwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mArrowWidthDDBtn</tabstop>
<tabstop>mOffsetSpin</tabstop>
<tabstop>mOffsetUnitWidget</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -63,6 +63,7 @@ ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py)
ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py)
ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py)
ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py)
ADD_PYTHON_TEST(PyQgsArrowSymbolLayer test_qgsarrowsymbollayer.py)
ADD_PYTHON_TEST(PyQgsSymbolExpressionVariables test_qgssymbolexpressionvariables.py)
ADD_PYTHON_TEST(PyQgsSyntacticSugar test_syntactic_sugar.py)
ADD_PYTHON_TEST(PyQgsSymbolV2 test_qgssymbolv2.py)

View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
test_qgsarrowsymbollayer.py
---------------------
Date : March 2016
Copyright : (C) 2016 by Hugo Mercier
Email : hugo dot mercier at oslandia dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Hugo Mercier'
__date__ = 'March 2016'
__copyright__ = '(C) 2016, Hugo Mercier'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
import qgis # NOQA
import os
from PyQt.QtCore import QSize
from PyQt.QtGui import QColor
from qgis.core import (
QgsVectorLayer,
QgsSingleSymbolRendererV2,
QgsLineSymbolV2,
QgsFillSymbolV2,
QgsMapLayerRegistry,
QgsRectangle,
QgsArrowSymbolLayer,
QgsSymbolV2,
QgsMultiRenderChecker
)
from qgis.testing import start_app, unittest
from qgis.testing.mocked import get_iface
from utilities import unitTestDataPath
# Convenience instances in case you may need them
# not used in this test
start_app()
TEST_DATA_DIR = unitTestDataPath()
class TestQgsArrowSymbolLayer(unittest.TestCase):
def setUp(self):
self.iface = get_iface()
lines_shp = os.path.join(TEST_DATA_DIR, 'lines.shp')
self.lines_layer = QgsVectorLayer(lines_shp, 'Lines', 'ogr')
QgsMapLayerRegistry.instance().addMapLayer(self.lines_layer)
# Create style
sym2 = QgsLineSymbolV2.createSimple({'color': '#fdbf6f'})
self.lines_layer.setRendererV2(QgsSingleSymbolRendererV2(sym2))
self.mapsettings = self.iface.mapCanvas().mapSettings()
self.mapsettings.setOutputSize(QSize(400, 400))
self.mapsettings.setOutputDpi(96)
self.mapsettings.setExtent(QgsRectangle(-113, 28, -91, 40))
self.mapsettings.setBackgroundColor(QColor("white"))
def tearDown(self):
QgsMapLayerRegistry.instance().removeAllMapLayers()
def test_1(self):
sym = self.lines_layer.rendererV2().symbol()
sym_layer = QgsArrowSymbolLayer.create({'arrow_width': '5', 'head_size': '6.5'})
fill_sym = QgsFillSymbolV2.createSimple({'color': '#8bcfff', 'outline_color': '#000000', 'outline_style': 'solid', 'outline_width': '1'})
sym_layer.setSubSymbol(fill_sym)
sym.changeSymbolLayer(0, sym_layer)
rendered_layers = [self.lines_layer.id()]
self.mapsettings.setLayers(rendered_layers)
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(self.mapsettings)
renderchecker.setControlName('expected_arrowsymbollayer_1')
self.assertTrue(renderchecker.runTest('arrowsymbollayer_1'))
def test_2(self):
sym = self.lines_layer.rendererV2().symbol()
# double headed
sym_layer = QgsArrowSymbolLayer.create({'arrow_width': '5', 'head_size': '6.5', 'head_type': '2'})
fill_sym = QgsFillSymbolV2.createSimple({'color': '#8bcfff', 'outline_color': '#000000', 'outline_style': 'solid', 'outline_width': '1'})
sym_layer.setSubSymbol(fill_sym)
sym.changeSymbolLayer(0, sym_layer)
rendered_layers = [self.lines_layer.id()]
self.mapsettings.setLayers(rendered_layers)
renderchecker = QgsMultiRenderChecker()
renderchecker.setMapSettings(self.mapsettings)
renderchecker.setControlName('expected_arrowsymbollayer_2')
self.assertTrue(renderchecker.runTest('arrowsymbollayer_2'))
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB