mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-13 00:07:27 -05:00
Interpolated line renderer: don't assume that a feature is available
Instead we should ALWAYS use the provided points for rendering symbol layers, or the symbol layer will be broken in various circumstances (e.g. when used outside of a vector layer) Fixes #45028
This commit is contained in:
parent
cc240e1ca2
commit
9e68b4542b
@ -260,6 +260,9 @@ Returns the stroke color used to render
|
|||||||
%Docstring
|
%Docstring
|
||||||
Renders a line in the ``context`` between ``point1`` and ``point2``
|
Renders a line in the ``context`` between ``point1`` and ``point2``
|
||||||
with color and width that vary depending on ``value1`` and ``value2``
|
with color and width that vary depending on ``value1`` and ``value2``
|
||||||
|
|
||||||
|
This method assumes that ``point1`` and ``point2`` are in map units. See :py:func:`~QgsInterpolatedLineRenderer.renderInDeviceCoordinates` for an equivalent
|
||||||
|
method which renders lines in painter coordinates.
|
||||||
%End
|
%End
|
||||||
|
|
||||||
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
||||||
@ -267,7 +270,18 @@ with color and width that vary depending on ``value1`` and ``value2``
|
|||||||
Renders a line in the ``context`` between ``point1`` and ``point2``
|
Renders a line in the ``context`` between ``point1`` and ``point2``
|
||||||
with color that varies depending on ``valueColor1`` and ``valueColor2`` and and width that varies between ``valueWidth1`` and ``valueWidth2``
|
with color that varies depending on ``valueColor1`` and ``valueColor2`` and and width that varies between ``valueWidth1`` and ``valueWidth2``
|
||||||
|
|
||||||
|
This method assumes that ``point1`` and ``point2`` are in map units. See :py:func:`~QgsInterpolatedLineRenderer.renderInDeviceCoordinates` for an equivalent
|
||||||
|
method which renders lines in painter coordinates.
|
||||||
|
|
||||||
.. versionadded:: 3.20
|
.. versionadded:: 3.20
|
||||||
|
%End
|
||||||
|
|
||||||
|
void renderInDeviceCoordinates( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, QPointF point1, QPointF point2, QgsRenderContext &context ) const;
|
||||||
|
%Docstring
|
||||||
|
Renders a line in the ``context`` between ``point1`` and ``point2`` in device (painter) coordinates
|
||||||
|
with color that varies depending on ``valueColor1`` and ``valueColor2`` and and width that varies between ``valueWidth1`` and ``valueWidth2``.
|
||||||
|
|
||||||
|
.. versionadded:: 3.22
|
||||||
%End
|
%End
|
||||||
|
|
||||||
void setSelected( bool selected );
|
void setSelected( bool selected );
|
||||||
|
|||||||
@ -804,10 +804,10 @@ QString QgsInterpolatedLineSymbolLayer::layerType() const {return QStringLiteral
|
|||||||
void QgsInterpolatedLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
|
void QgsInterpolatedLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
|
||||||
{
|
{
|
||||||
// find out attribute index from name
|
// find out attribute index from name
|
||||||
mStartWidthAttributeIndex = mFields.lookupField( mStartWidthExpressionString );
|
mStartWidthAttributeIndex = -1; //mFields.lookupField( mStartWidthExpressionString );
|
||||||
mEndWidthAttributeIndex = mFields.lookupField( mEndWidthExpressionString );
|
mEndWidthAttributeIndex = -1; //mFields.lookupField( mEndWidthExpressionString );
|
||||||
mStartColorAttributeIndex = mFields.lookupField( mStartColorExpressionString );
|
mStartColorAttributeIndex = -1; //mFields.lookupField( mStartColorExpressionString );
|
||||||
mEndColorAttributeIndex = mFields.lookupField( mEndColorExpressionString );
|
mEndColorAttributeIndex = -1; //mFields.lookupField( mEndColorExpressionString );
|
||||||
|
|
||||||
if ( mStartWidthAttributeIndex == -1 )
|
if ( mStartWidthAttributeIndex == -1 )
|
||||||
{
|
{
|
||||||
@ -932,8 +932,6 @@ void QgsInterpolatedLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &co
|
|||||||
{
|
{
|
||||||
QgsGeometry geometry = context.patchShape() ? context.patchShape()->geometry()
|
QgsGeometry geometry = context.patchShape() ? context.patchShape()->geometry()
|
||||||
: QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, size ).geometry();
|
: QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, size ).geometry();
|
||||||
QgsFeature feature;
|
|
||||||
feature.setGeometry( geometry );
|
|
||||||
|
|
||||||
startRender( context );
|
startRender( context );
|
||||||
mStartWidthAttributeIndex = -1;
|
mStartWidthAttributeIndex = -1;
|
||||||
@ -1118,74 +1116,38 @@ QgsColorRampShader QgsInterpolatedLineSymbolLayer::createColorRampShaderFromProp
|
|||||||
QgsInterpolatedLineSymbolLayer::QgsInterpolatedLineSymbolLayer(): QgsLineSymbolLayer( true ) {}
|
QgsInterpolatedLineSymbolLayer::QgsInterpolatedLineSymbolLayer(): QgsLineSymbolLayer( true ) {}
|
||||||
|
|
||||||
|
|
||||||
void QgsInterpolatedLineSymbolLayer::startFeatureRender( const QgsFeature &feature, QgsRenderContext & )
|
void QgsInterpolatedLineSymbolLayer::startFeatureRender( const QgsFeature &, QgsRenderContext & )
|
||||||
{
|
{
|
||||||
mFeature = feature;
|
mRenderingFeature = true;
|
||||||
|
mLineParts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsInterpolatedLineSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRenderContext & )
|
void QgsInterpolatedLineSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRenderContext &context )
|
||||||
{
|
{
|
||||||
mFeature = QgsFeature();
|
mRenderingFeature = false;
|
||||||
|
|
||||||
|
if ( mLineParts.empty() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
render( mLineParts, context );
|
||||||
|
mLineParts.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
|
void QgsInterpolatedLineSymbolLayer::render( const QVector< QPolygonF > &parts, QgsRenderContext &context )
|
||||||
{
|
{
|
||||||
Q_UNUSED( points ); //this symbol layer need to used all the feature geometry, not clipped/simplified geometry
|
const double totalLength = std::accumulate( parts.begin(), parts.end(), 0.0, []( double total, const QPolygonF & part )
|
||||||
|
{
|
||||||
|
return total + QgsSymbolLayerUtils::polylineLength( part );
|
||||||
|
} );
|
||||||
|
|
||||||
QVector<QgsPolylineXY> lineStrings;
|
if ( qgsDoubleNear( totalLength, 0 ) )
|
||||||
|
return;
|
||||||
|
|
||||||
double startValWidth = 0;
|
double startValWidth = 0;
|
||||||
double endValWidth = 0;
|
|
||||||
double variationPerMapUnitWidth = 0;
|
double variationPerMapUnitWidth = 0;
|
||||||
double startValColor = 0;
|
double startValColor = 0;
|
||||||
double endValColor = 0;
|
|
||||||
double variationPerMapUnitColor = 0;
|
double variationPerMapUnitColor = 0;
|
||||||
|
|
||||||
QgsRenderContext renderContext = context.renderContext();
|
|
||||||
|
|
||||||
QgsGeometry geom = mFeature.geometry();
|
|
||||||
|
|
||||||
mLineRender.setSelected( context.selected() );
|
|
||||||
|
|
||||||
if ( geom.isEmpty() )
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch ( QgsWkbTypes::flatType( geom.wkbType() ) )
|
|
||||||
{
|
|
||||||
case QgsWkbTypes::Unknown:
|
|
||||||
case QgsWkbTypes::Point:
|
|
||||||
case QgsWkbTypes::Polygon:
|
|
||||||
case QgsWkbTypes::Triangle:
|
|
||||||
case QgsWkbTypes::MultiPoint:
|
|
||||||
case QgsWkbTypes::MultiPolygon:
|
|
||||||
case QgsWkbTypes::GeometryCollection:
|
|
||||||
case QgsWkbTypes::CurvePolygon:
|
|
||||||
case QgsWkbTypes::MultiSurface:
|
|
||||||
case QgsWkbTypes::NoGeometry:
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
case QgsWkbTypes::LineString:
|
|
||||||
case QgsWkbTypes::CircularString:
|
|
||||||
case QgsWkbTypes::CompoundCurve:
|
|
||||||
lineStrings.append( geom.asPolyline() );
|
|
||||||
break;
|
|
||||||
case QgsWkbTypes::MultiCurve:
|
|
||||||
case QgsWkbTypes::MultiLineString:
|
|
||||||
lineStrings = geom.asMultiPolyline();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QgsExpressionContext expressionContext = renderContext.expressionContext();
|
|
||||||
expressionContext.setFeature( mFeature );
|
|
||||||
|
|
||||||
double totalLength = geom.length();
|
|
||||||
|
|
||||||
if ( totalLength == 0 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
QVariant val1WidthVariant;
|
QVariant val1WidthVariant;
|
||||||
QVariant val2WidthVariant;
|
QVariant val2WidthVariant;
|
||||||
QVariant val1ColorVariant;
|
QVariant val1ColorVariant;
|
||||||
@ -1196,19 +1158,19 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
|
|||||||
{
|
{
|
||||||
if ( mStartWidthExpression )
|
if ( mStartWidthExpression )
|
||||||
{
|
{
|
||||||
val1WidthVariant = mStartWidthExpression->evaluate( &expressionContext );
|
val1WidthVariant = mStartWidthExpression->evaluate( &context.expressionContext() );
|
||||||
ok |= mStartWidthExpression->hasEvalError();
|
ok |= mStartWidthExpression->hasEvalError();
|
||||||
}
|
}
|
||||||
else
|
//else
|
||||||
val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
|
// val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
|
||||||
|
|
||||||
if ( mEndWithExpression )
|
if ( mEndWithExpression )
|
||||||
{
|
{
|
||||||
val2WidthVariant = mEndWithExpression->evaluate( &expressionContext );
|
val2WidthVariant = mEndWithExpression->evaluate( &context.expressionContext() );
|
||||||
ok |= mEndWithExpression->hasEvalError();
|
ok |= mEndWithExpression->hasEvalError();
|
||||||
}
|
}
|
||||||
else
|
//else
|
||||||
val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
|
// val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
|
||||||
|
|
||||||
if ( !ok )
|
if ( !ok )
|
||||||
return;
|
return;
|
||||||
@ -1217,7 +1179,7 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
|
|||||||
if ( !ok )
|
if ( !ok )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
endValWidth = val2WidthVariant.toDouble( &ok );
|
const double endValWidth = val2WidthVariant.toDouble( &ok );
|
||||||
if ( !ok )
|
if ( !ok )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -1228,48 +1190,67 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
|
|||||||
{
|
{
|
||||||
if ( mStartColorExpression )
|
if ( mStartColorExpression )
|
||||||
{
|
{
|
||||||
val1ColorVariant = mStartColorExpression->evaluate( &expressionContext );
|
val1ColorVariant = mStartColorExpression->evaluate( &context.expressionContext() );
|
||||||
ok |= mStartColorExpression->hasEvalError();
|
ok |= mStartColorExpression->hasEvalError();
|
||||||
}
|
}
|
||||||
else
|
// else
|
||||||
val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
|
// val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
|
||||||
|
|
||||||
if ( mEndColorExpression )
|
if ( mEndColorExpression )
|
||||||
{
|
{
|
||||||
val2ColorVariant = mEndColorExpression->evaluate( &expressionContext );
|
val2ColorVariant = mEndColorExpression->evaluate( &context.expressionContext() );
|
||||||
ok |= mEndColorExpression->hasEvalError();
|
ok |= mEndColorExpression->hasEvalError();
|
||||||
}
|
}
|
||||||
else
|
// else
|
||||||
val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
|
// val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
|
||||||
|
|
||||||
startValColor = val1ColorVariant.toDouble( &ok );
|
startValColor = val1ColorVariant.toDouble( &ok );
|
||||||
if ( !ok )
|
if ( !ok )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
endValColor = val2ColorVariant.toDouble( &ok );
|
const double endValColor = val2ColorVariant.toDouble( &ok );
|
||||||
if ( !ok )
|
if ( !ok )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
variationPerMapUnitColor = ( endValColor - startValColor ) / totalLength;
|
variationPerMapUnitColor = ( endValColor - startValColor ) / totalLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ( const QgsPolylineXY &poly : std::as_const( lineStrings ) )
|
for ( const QPolygonF &poly : parts )
|
||||||
{
|
{
|
||||||
double lengthFromStart = 0;
|
double lengthFromStart = 0;
|
||||||
for ( int i = 1; i < poly.count(); ++i )
|
for ( int i = 1; i < poly.count(); ++i )
|
||||||
{
|
{
|
||||||
QgsPointXY p1 = poly.at( i - 1 );
|
const QPointF p1 = poly.at( i - 1 );
|
||||||
QgsPointXY p2 = poly.at( i );
|
const QPointF p2 = poly.at( i );
|
||||||
|
|
||||||
double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
|
const double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
|
||||||
double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
|
const double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
|
||||||
lengthFromStart += p1.distance( p2 );
|
lengthFromStart += std::sqrt( ( p1.x() - p2.x() ) * ( p1.x() - p2.x() ) + ( p1.y() - p2.y() ) * ( p1.y() - p2.y() ) );
|
||||||
double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
|
const double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
|
||||||
double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
|
const double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
|
||||||
mLineRender.render( v1c, v2c, v1w, v2w, p1, p2, renderContext );
|
mLineRender.renderInDeviceCoordinates( v1c, v2c, v1w, v2w, p1, p2, context );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
|
||||||
|
{
|
||||||
|
mLineRender.setSelected( context.selected() );
|
||||||
|
|
||||||
|
if ( points.empty() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( mRenderingFeature )
|
||||||
|
{
|
||||||
|
// in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
|
||||||
|
// until after we've received the final part
|
||||||
|
mLineParts.append( points );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// not rendering a feature, so we can just render the polyline immediately
|
||||||
|
render( { points }, context.renderContext() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QgsInterpolatedLineSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol ) const
|
bool QgsInterpolatedLineSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol ) const
|
||||||
|
|||||||
@ -231,6 +231,9 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
|
|||||||
/**
|
/**
|
||||||
* Renders a line in the \a context between \a point1 and \a point2
|
* Renders a line in the \a context between \a point1 and \a point2
|
||||||
* with color and width that vary depending on \a value1 and \a value2
|
* with color and width that vary depending on \a value1 and \a value2
|
||||||
|
*
|
||||||
|
* This method assumes that \a point1 and \a point2 are in map units. See renderInDeviceCoordinates() for an equivalent
|
||||||
|
* method which renders lines in painter coordinates.
|
||||||
*/
|
*/
|
||||||
void render( double value1, double value2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
void render( double value1, double value2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
||||||
|
|
||||||
@ -238,10 +241,21 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
|
|||||||
* Renders a line in the \a context between \a point1 and \a point2
|
* Renders a line in the \a context between \a point1 and \a point2
|
||||||
* with color that varies depending on \a valueColor1 and \a valueColor2 and and width that varies between \a valueWidth1 and \a valueWidth2
|
* with color that varies depending on \a valueColor1 and \a valueColor2 and and width that varies between \a valueWidth1 and \a valueWidth2
|
||||||
*
|
*
|
||||||
|
* This method assumes that \a point1 and \a point2 are in map units. See renderInDeviceCoordinates() for an equivalent
|
||||||
|
* method which renders lines in painter coordinates.
|
||||||
|
*
|
||||||
* \since QGIS 3.20
|
* \since QGIS 3.20
|
||||||
*/
|
*/
|
||||||
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a line in the \a context between \a point1 and \a point2 in device (painter) coordinates
|
||||||
|
* with color that varies depending on \a valueColor1 and \a valueColor2 and and width that varies between \a valueWidth1 and \a valueWidth2.
|
||||||
|
*
|
||||||
|
* \since QGIS 3.22
|
||||||
|
*/
|
||||||
|
void renderInDeviceCoordinates( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, QPointF point1, QPointF point2, QgsRenderContext &context ) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets if the rendering must be done as the element is selected
|
* Sets if the rendering must be done as the element is selected
|
||||||
*
|
*
|
||||||
@ -257,11 +271,6 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
|
|||||||
void adjustLine( double value, double value1, double value2, double &width, double &adjusting ) const;
|
void adjustLine( double value, double value1, double value2, double &width, double &adjusting ) const;
|
||||||
bool mSelected = false;
|
bool mSelected = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders a line in the \a context between \a point1 and \a point2 in device coordinates
|
|
||||||
* with color and width that vary depending on \a value1 and \a value2
|
|
||||||
*/
|
|
||||||
void renderInDeviceCoordinate( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QPointF &p1, const QPointF &p2, QgsRenderContext &context ) const;
|
|
||||||
|
|
||||||
friend class QgsInterpolatedLineSymbolLayer;
|
friend class QgsInterpolatedLineSymbolLayer;
|
||||||
};
|
};
|
||||||
@ -340,6 +349,7 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QgsInterpolatedLineRenderer mLineRender;
|
QgsInterpolatedLineRenderer mLineRender;
|
||||||
|
|
||||||
QString mStartWidthExpressionString;
|
QString mStartWidthExpressionString;
|
||||||
QString mEndWidthExpressionString;
|
QString mEndWidthExpressionString;
|
||||||
QString mStartColorExpressionString;
|
QString mStartColorExpressionString;
|
||||||
@ -354,7 +364,11 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
|
|||||||
std::unique_ptr<QgsExpression> mEndWithExpression;
|
std::unique_ptr<QgsExpression> mEndWithExpression;
|
||||||
std::unique_ptr<QgsExpression> mStartColorExpression;
|
std::unique_ptr<QgsExpression> mStartColorExpression;
|
||||||
std::unique_ptr<QgsExpression> mEndColorExpression;
|
std::unique_ptr<QgsExpression> mEndColorExpression;
|
||||||
QgsFeature mFeature;
|
|
||||||
|
QVector< QPolygonF > mLineParts;
|
||||||
|
bool mRenderingFeature = false;
|
||||||
|
|
||||||
|
void render( const QVector< QPolygonF > &parts, QgsRenderContext &context );
|
||||||
|
|
||||||
QVariant colorRampShaderProperties() const;
|
QVariant colorRampShaderProperties() const;
|
||||||
static QgsColorRampShader createColorRampShaderFromProperties( const QVariant &properties );
|
static QgsColorRampShader createColorRampShaderFromProperties( const QVariant &properties );
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user