mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-19 00:02:48 -04:00
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:
parent
81744ecf90
commit
34b7ebc2b5
@ -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
|
||||
|
||||
|
111
python/core/symbology-ng/qgsarrowsymbollayer.sip
Normal file
111
python/core/symbology-ng/qgsarrowsymbollayer.sip
Normal 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& );
|
||||
};
|
@ -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
|
||||
|
22
python/gui/symbology-ng/qgsarrowsymbollayerwidget.sip
Normal file
22
python/gui/symbology-ng/qgsarrowsymbollayerwidget.sip
Normal 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();
|
||||
};
|
@ -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
|
||||
|
594
src/core/symbology-ng/qgsarrowsymbollayer.cpp
Normal file
594
src/core/symbology-ng/qgsarrowsymbollayer.cpp
Normal 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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
159
src/core/symbology-ng/qgsarrowsymbollayer.h
Normal file
159
src/core/symbology-ng/qgsarrowsymbollayer.h
Normal 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
|
||||
|
||||
|
@ -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 ) );
|
||||
|
@ -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
|
||||
|
174
src/gui/symbology-ng/qgsarrowsymbollayerwidget.cpp
Normal file
174
src/gui/symbology-ng/qgsarrowsymbollayerwidget.cpp
Normal 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();
|
||||
}
|
||||
|
65
src/gui/symbology-ng/qgsarrowsymbollayerwidget.h
Normal file
65
src/gui/symbology-ng/qgsarrowsymbollayerwidget.h
Normal 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
|
@ -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 );
|
||||
|
295
src/ui/symbollayer/qgsarrowsymbollayerwidgetbase.ui
Normal file
295
src/ui/symbollayer/qgsarrowsymbollayerwidgetbase.ui
Normal 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><html><head/><body><p>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 &quot;Clip features to canvas extent&quot; advanced option of the symbol.</p></body></html></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>
|
@ -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)
|
||||
|
109
tests/src/python/test_qgsarrowsymbollayer.py
Normal file
109
tests/src/python/test_qgsarrowsymbollayer.py
Normal 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()
|
BIN
tests/testdata/control_images/expected_arrowsymbollayer_1/expected_arrowsymbollayer_1.png
vendored
Normal file
BIN
tests/testdata/control_images/expected_arrowsymbollayer_1/expected_arrowsymbollayer_1.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
BIN
tests/testdata/control_images/expected_arrowsymbollayer_2/expected_arrowsymbollayer_2.png
vendored
Normal file
BIN
tests/testdata/control_images/expected_arrowsymbollayer_2/expected_arrowsymbollayer_2.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
Loading…
x
Reference in New Issue
Block a user