Skip to content

Commit 9e68b45

Browse files
committedSep 21, 2021
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
1 parent cc240e1 commit 9e68b45

File tree

3 files changed

+99
-90
lines changed

3 files changed

+99
-90
lines changed
 

‎python/core/auto_generated/symbology/qgsinterpolatedlinerenderer.sip.in

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,14 +260,28 @@ Returns the stroke color used to render
260260
%Docstring
261261
Renders a line in the ``context`` between ``point1`` and ``point2``
262262
with color and width that vary depending on ``value1`` and ``value2``
263+
264+
This method assumes that ``point1`` and ``point2`` are in map units. See :py:func:`~QgsInterpolatedLineRenderer.renderInDeviceCoordinates` for an equivalent
265+
method which renders lines in painter coordinates.
263266
%End
264267

265268
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
266269
%Docstring
267270
Renders a line in the ``context`` between ``point1`` and ``point2``
268271
with color that varies depending on ``valueColor1`` and ``valueColor2`` and and width that varies between ``valueWidth1`` and ``valueWidth2``
269272

273+
This method assumes that ``point1`` and ``point2`` are in map units. See :py:func:`~QgsInterpolatedLineRenderer.renderInDeviceCoordinates` for an equivalent
274+
method which renders lines in painter coordinates.
275+
270276
.. versionadded:: 3.20
277+
%End
278+
279+
void renderInDeviceCoordinates( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, QPointF point1, QPointF point2, QgsRenderContext &context ) const;
280+
%Docstring
281+
Renders a line in the ``context`` between ``point1`` and ``point2`` in device (painter) coordinates
282+
with color that varies depending on ``valueColor1`` and ``valueColor2`` and and width that varies between ``valueWidth1`` and ``valueWidth2``.
283+
284+
.. versionadded:: 3.22
271285
%End
272286

273287
void setSelected( bool selected );

‎src/core/symbology/qgsinterpolatedlinerenderer.cpp

Lines changed: 65 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -804,10 +804,10 @@ QString QgsInterpolatedLineSymbolLayer::layerType() const {return QStringLiteral
804804
void QgsInterpolatedLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
805805
{
806806
// find out attribute index from name
807-
mStartWidthAttributeIndex = mFields.lookupField( mStartWidthExpressionString );
808-
mEndWidthAttributeIndex = mFields.lookupField( mEndWidthExpressionString );
809-
mStartColorAttributeIndex = mFields.lookupField( mStartColorExpressionString );
810-
mEndColorAttributeIndex = mFields.lookupField( mEndColorExpressionString );
807+
mStartWidthAttributeIndex = -1; //mFields.lookupField( mStartWidthExpressionString );
808+
mEndWidthAttributeIndex = -1; //mFields.lookupField( mEndWidthExpressionString );
809+
mStartColorAttributeIndex = -1; //mFields.lookupField( mStartColorExpressionString );
810+
mEndColorAttributeIndex = -1; //mFields.lookupField( mEndColorExpressionString );
811811

812812
if ( mStartWidthAttributeIndex == -1 )
813813
{
@@ -932,8 +932,6 @@ void QgsInterpolatedLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &co
932932
{
933933
QgsGeometry geometry = context.patchShape() ? context.patchShape()->geometry()
934934
: QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, size ).geometry();
935-
QgsFeature feature;
936-
feature.setGeometry( geometry );
937935

938936
startRender( context );
939937
mStartWidthAttributeIndex = -1;
@@ -1118,74 +1116,38 @@ QgsColorRampShader QgsInterpolatedLineSymbolLayer::createColorRampShaderFromProp
11181116
QgsInterpolatedLineSymbolLayer::QgsInterpolatedLineSymbolLayer(): QgsLineSymbolLayer( true ) {}
11191117

11201118

1121-
void QgsInterpolatedLineSymbolLayer::startFeatureRender( const QgsFeature &feature, QgsRenderContext & )
1119+
void QgsInterpolatedLineSymbolLayer::startFeatureRender( const QgsFeature &, QgsRenderContext & )
11221120
{
1123-
mFeature = feature;
1121+
mRenderingFeature = true;
1122+
mLineParts.clear();
11241123
}
11251124

1126-
void QgsInterpolatedLineSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRenderContext & )
1125+
void QgsInterpolatedLineSymbolLayer::stopFeatureRender( const QgsFeature &, QgsRenderContext &context )
11271126
{
1128-
mFeature = QgsFeature();
1127+
mRenderingFeature = false;
1128+
1129+
if ( mLineParts.empty() )
1130+
return;
1131+
1132+
render( mLineParts, context );
1133+
mLineParts.clear();
11291134
}
11301135

1131-
void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
1136+
void QgsInterpolatedLineSymbolLayer::render( const QVector< QPolygonF > &parts, QgsRenderContext &context )
11321137
{
1133-
Q_UNUSED( points ); //this symbol layer need to used all the feature geometry, not clipped/simplified geometry
1138+
const double totalLength = std::accumulate( parts.begin(), parts.end(), 0.0, []( double total, const QPolygonF & part )
1139+
{
1140+
return total + QgsSymbolLayerUtils::polylineLength( part );
1141+
} );
11341142

1135-
QVector<QgsPolylineXY> lineStrings;
1143+
if ( qgsDoubleNear( totalLength, 0 ) )
1144+
return;
11361145

11371146
double startValWidth = 0;
1138-
double endValWidth = 0;
11391147
double variationPerMapUnitWidth = 0;
11401148
double startValColor = 0;
1141-
double endValColor = 0;
11421149
double variationPerMapUnitColor = 0;
11431150

1144-
QgsRenderContext renderContext = context.renderContext();
1145-
1146-
QgsGeometry geom = mFeature.geometry();
1147-
1148-
mLineRender.setSelected( context.selected() );
1149-
1150-
if ( geom.isEmpty() )
1151-
return;
1152-
1153-
switch ( QgsWkbTypes::flatType( geom.wkbType() ) )
1154-
{
1155-
case QgsWkbTypes::Unknown:
1156-
case QgsWkbTypes::Point:
1157-
case QgsWkbTypes::Polygon:
1158-
case QgsWkbTypes::Triangle:
1159-
case QgsWkbTypes::MultiPoint:
1160-
case QgsWkbTypes::MultiPolygon:
1161-
case QgsWkbTypes::GeometryCollection:
1162-
case QgsWkbTypes::CurvePolygon:
1163-
case QgsWkbTypes::MultiSurface:
1164-
case QgsWkbTypes::NoGeometry:
1165-
return;
1166-
break;
1167-
case QgsWkbTypes::LineString:
1168-
case QgsWkbTypes::CircularString:
1169-
case QgsWkbTypes::CompoundCurve:
1170-
lineStrings.append( geom.asPolyline() );
1171-
break;
1172-
case QgsWkbTypes::MultiCurve:
1173-
case QgsWkbTypes::MultiLineString:
1174-
lineStrings = geom.asMultiPolyline();
1175-
break;
1176-
default:
1177-
return;
1178-
break;
1179-
}
1180-
1181-
QgsExpressionContext expressionContext = renderContext.expressionContext();
1182-
expressionContext.setFeature( mFeature );
1183-
1184-
double totalLength = geom.length();
1185-
1186-
if ( totalLength == 0 )
1187-
return;
1188-
11891151
QVariant val1WidthVariant;
11901152
QVariant val2WidthVariant;
11911153
QVariant val1ColorVariant;
@@ -1196,19 +1158,19 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
11961158
{
11971159
if ( mStartWidthExpression )
11981160
{
1199-
val1WidthVariant = mStartWidthExpression->evaluate( &expressionContext );
1161+
val1WidthVariant = mStartWidthExpression->evaluate( &context.expressionContext() );
12001162
ok |= mStartWidthExpression->hasEvalError();
12011163
}
1202-
else
1203-
val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
1164+
//else
1165+
// val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
12041166

12051167
if ( mEndWithExpression )
12061168
{
1207-
val2WidthVariant = mEndWithExpression->evaluate( &expressionContext );
1169+
val2WidthVariant = mEndWithExpression->evaluate( &context.expressionContext() );
12081170
ok |= mEndWithExpression->hasEvalError();
12091171
}
1210-
else
1211-
val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
1172+
//else
1173+
// val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
12121174

12131175
if ( !ok )
12141176
return;
@@ -1217,7 +1179,7 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
12171179
if ( !ok )
12181180
return;
12191181

1220-
endValWidth = val2WidthVariant.toDouble( &ok );
1182+
const double endValWidth = val2WidthVariant.toDouble( &ok );
12211183
if ( !ok )
12221184
return;
12231185

@@ -1228,48 +1190,67 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
12281190
{
12291191
if ( mStartColorExpression )
12301192
{
1231-
val1ColorVariant = mStartColorExpression->evaluate( &expressionContext );
1193+
val1ColorVariant = mStartColorExpression->evaluate( &context.expressionContext() );
12321194
ok |= mStartColorExpression->hasEvalError();
12331195
}
1234-
else
1235-
val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
1196+
// else
1197+
// val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
12361198

12371199
if ( mEndColorExpression )
12381200
{
1239-
val2ColorVariant = mEndColorExpression->evaluate( &expressionContext );
1201+
val2ColorVariant = mEndColorExpression->evaluate( &context.expressionContext() );
12401202
ok |= mEndColorExpression->hasEvalError();
12411203
}
1242-
else
1243-
val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
1204+
// else
1205+
// val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
12441206

12451207
startValColor = val1ColorVariant.toDouble( &ok );
12461208
if ( !ok )
12471209
return;
12481210

1249-
endValColor = val2ColorVariant.toDouble( &ok );
1211+
const double endValColor = val2ColorVariant.toDouble( &ok );
12501212
if ( !ok )
12511213
return;
12521214

12531215
variationPerMapUnitColor = ( endValColor - startValColor ) / totalLength;
12541216
}
12551217

1256-
for ( const QgsPolylineXY &poly : std::as_const( lineStrings ) )
1218+
for ( const QPolygonF &poly : parts )
12571219
{
12581220
double lengthFromStart = 0;
12591221
for ( int i = 1; i < poly.count(); ++i )
12601222
{
1261-
QgsPointXY p1 = poly.at( i - 1 );
1262-
QgsPointXY p2 = poly.at( i );
1263-
1264-
double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
1265-
double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1266-
lengthFromStart += p1.distance( p2 );
1267-
double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
1268-
double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1269-
mLineRender.render( v1c, v2c, v1w, v2w, p1, p2, renderContext );
1223+
const QPointF p1 = poly.at( i - 1 );
1224+
const QPointF p2 = poly.at( i );
1225+
1226+
const double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
1227+
const double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1228+
lengthFromStart += std::sqrt( ( p1.x() - p2.x() ) * ( p1.x() - p2.x() ) + ( p1.y() - p2.y() ) * ( p1.y() - p2.y() ) );
1229+
const double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
1230+
const double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
1231+
mLineRender.renderInDeviceCoordinates( v1c, v2c, v1w, v2w, p1, p2, context );
12701232
}
12711233
}
1234+
}
12721235

1236+
void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, QgsSymbolRenderContext &context )
1237+
{
1238+
mLineRender.setSelected( context.selected() );
1239+
1240+
if ( points.empty() )
1241+
return;
1242+
1243+
if ( mRenderingFeature )
1244+
{
1245+
// in the middle of rendering a possibly multi-part feature, so we collect all the parts and defer the actual rendering
1246+
// until after we've received the final part
1247+
mLineParts.append( points );
1248+
}
1249+
else
1250+
{
1251+
// not rendering a feature, so we can just render the polyline immediately
1252+
render( { points }, context.renderContext() );
1253+
}
12731254
}
12741255

12751256
bool QgsInterpolatedLineSymbolLayer::isCompatibleWithSymbol( QgsSymbol *symbol ) const

‎src/core/symbology/qgsinterpolatedlinerenderer.h

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,31 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
231231
/**
232232
* Renders a line in the \a context between \a point1 and \a point2
233233
* with color and width that vary depending on \a value1 and \a value2
234+
*
235+
* This method assumes that \a point1 and \a point2 are in map units. See renderInDeviceCoordinates() for an equivalent
236+
* method which renders lines in painter coordinates.
234237
*/
235238
void render( double value1, double value2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
236239

237240
/**
238241
* Renders a line in the \a context between \a point1 and \a point2
239242
* with color that varies depending on \a valueColor1 and \a valueColor2 and and width that varies between \a valueWidth1 and \a valueWidth2
240243
*
244+
* This method assumes that \a point1 and \a point2 are in map units. See renderInDeviceCoordinates() for an equivalent
245+
* method which renders lines in painter coordinates.
246+
*
241247
* \since QGIS 3.20
242248
*/
243249
void render( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QgsPointXY &point1, const QgsPointXY &point2, QgsRenderContext &context ) const;
244250

251+
/**
252+
* Renders a line in the \a context between \a point1 and \a point2 in device (painter) coordinates
253+
* with color that varies depending on \a valueColor1 and \a valueColor2 and and width that varies between \a valueWidth1 and \a valueWidth2.
254+
*
255+
* \since QGIS 3.22
256+
*/
257+
void renderInDeviceCoordinates( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, QPointF point1, QPointF point2, QgsRenderContext &context ) const;
258+
245259
/**
246260
* Sets if the rendering must be done as the element is selected
247261
*
@@ -257,11 +271,6 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
257271
void adjustLine( double value, double value1, double value2, double &width, double &adjusting ) const;
258272
bool mSelected = false;
259273

260-
/**
261-
* Renders a line in the \a context between \a point1 and \a point2 in device coordinates
262-
* with color and width that vary depending on \a value1 and \a value2
263-
*/
264-
void renderInDeviceCoordinate( double valueColor1, double valueColor2, double valueWidth1, double valueWidth2, const QPointF &p1, const QPointF &p2, QgsRenderContext &context ) const;
265274

266275
friend class QgsInterpolatedLineSymbolLayer;
267276
};
@@ -340,6 +349,7 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
340349
#endif
341350

342351
QgsInterpolatedLineRenderer mLineRender;
352+
343353
QString mStartWidthExpressionString;
344354
QString mEndWidthExpressionString;
345355
QString mStartColorExpressionString;
@@ -354,7 +364,11 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
354364
std::unique_ptr<QgsExpression> mEndWithExpression;
355365
std::unique_ptr<QgsExpression> mStartColorExpression;
356366
std::unique_ptr<QgsExpression> mEndColorExpression;
357-
QgsFeature mFeature;
367+
368+
QVector< QPolygonF > mLineParts;
369+
bool mRenderingFeature = false;
370+
371+
void render( const QVector< QPolygonF > &parts, QgsRenderContext &context );
358372

359373
QVariant colorRampShaderProperties() const;
360374
static QgsColorRampShader createColorRampShaderFromProperties( const QVariant &properties );

0 commit comments

Comments
 (0)
Please sign in to comment.