mirror of
				https://github.com/qgis/QGIS.git
				synced 2025-11-04 00:04:25 -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
 | 
			
		||||
Renders a line in the ``context`` between ``point1`` and ``point2``
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    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``
 | 
			
		||||
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
 | 
			
		||||
%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
 | 
			
		||||
 | 
			
		||||
    void setSelected( bool selected );
 | 
			
		||||
 | 
			
		||||
@ -804,10 +804,10 @@ QString QgsInterpolatedLineSymbolLayer::layerType() const {return QStringLiteral
 | 
			
		||||
void QgsInterpolatedLineSymbolLayer::startRender( QgsSymbolRenderContext &context )
 | 
			
		||||
{
 | 
			
		||||
  // find out attribute index from name
 | 
			
		||||
  mStartWidthAttributeIndex = mFields.lookupField( mStartWidthExpressionString );
 | 
			
		||||
  mEndWidthAttributeIndex = mFields.lookupField( mEndWidthExpressionString );
 | 
			
		||||
  mStartColorAttributeIndex = mFields.lookupField( mStartColorExpressionString );
 | 
			
		||||
  mEndColorAttributeIndex = mFields.lookupField( mEndColorExpressionString );
 | 
			
		||||
  mStartWidthAttributeIndex = -1; //mFields.lookupField( mStartWidthExpressionString );
 | 
			
		||||
  mEndWidthAttributeIndex = -1; //mFields.lookupField( mEndWidthExpressionString );
 | 
			
		||||
  mStartColorAttributeIndex = -1; //mFields.lookupField( mStartColorExpressionString );
 | 
			
		||||
  mEndColorAttributeIndex = -1; //mFields.lookupField( mEndColorExpressionString );
 | 
			
		||||
 | 
			
		||||
  if ( mStartWidthAttributeIndex == -1 )
 | 
			
		||||
  {
 | 
			
		||||
@ -932,8 +932,6 @@ void QgsInterpolatedLineSymbolLayer::drawPreviewIcon( QgsSymbolRenderContext &co
 | 
			
		||||
{
 | 
			
		||||
  QgsGeometry geometry = context.patchShape() ? context.patchShape()->geometry()
 | 
			
		||||
                         : QgsStyle::defaultStyle()->defaultPatch( Qgis::SymbolType::Line, size ).geometry();
 | 
			
		||||
  QgsFeature feature;
 | 
			
		||||
  feature.setGeometry( geometry );
 | 
			
		||||
 | 
			
		||||
  startRender( context );
 | 
			
		||||
  mStartWidthAttributeIndex = -1;
 | 
			
		||||
@ -1118,74 +1116,38 @@ QgsColorRampShader QgsInterpolatedLineSymbolLayer::createColorRampShaderFromProp
 | 
			
		||||
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 endValWidth = 0;
 | 
			
		||||
  double variationPerMapUnitWidth = 0;
 | 
			
		||||
  double startValColor = 0;
 | 
			
		||||
  double endValColor = 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 val2WidthVariant;
 | 
			
		||||
  QVariant val1ColorVariant;
 | 
			
		||||
@ -1196,19 +1158,19 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
 | 
			
		||||
  {
 | 
			
		||||
    if ( mStartWidthExpression )
 | 
			
		||||
    {
 | 
			
		||||
      val1WidthVariant = mStartWidthExpression->evaluate( &expressionContext );
 | 
			
		||||
      val1WidthVariant = mStartWidthExpression->evaluate( &context.expressionContext() );
 | 
			
		||||
      ok |= mStartWidthExpression->hasEvalError();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
 | 
			
		||||
    //else
 | 
			
		||||
    //  val1WidthVariant = mFeature.attribute( mStartWidthAttributeIndex );
 | 
			
		||||
 | 
			
		||||
    if ( mEndWithExpression )
 | 
			
		||||
    {
 | 
			
		||||
      val2WidthVariant = mEndWithExpression->evaluate( &expressionContext );
 | 
			
		||||
      val2WidthVariant = mEndWithExpression->evaluate( &context.expressionContext() );
 | 
			
		||||
      ok |= mEndWithExpression->hasEvalError();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
 | 
			
		||||
    //else
 | 
			
		||||
    //  val2WidthVariant = mFeature.attribute( mEndWidthAttributeIndex );
 | 
			
		||||
 | 
			
		||||
    if ( !ok )
 | 
			
		||||
      return;
 | 
			
		||||
@ -1217,7 +1179,7 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
 | 
			
		||||
    if ( !ok )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    endValWidth = val2WidthVariant.toDouble( &ok );
 | 
			
		||||
    const double endValWidth = val2WidthVariant.toDouble( &ok );
 | 
			
		||||
    if ( !ok )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
@ -1228,48 +1190,67 @@ void QgsInterpolatedLineSymbolLayer::renderPolyline( const QPolygonF &points, Qg
 | 
			
		||||
  {
 | 
			
		||||
    if ( mStartColorExpression )
 | 
			
		||||
    {
 | 
			
		||||
      val1ColorVariant = mStartColorExpression->evaluate( &expressionContext );
 | 
			
		||||
      val1ColorVariant = mStartColorExpression->evaluate( &context.expressionContext() );
 | 
			
		||||
      ok |= mStartColorExpression->hasEvalError();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
 | 
			
		||||
    // else
 | 
			
		||||
    //   val1ColorVariant = mFeature.attribute( mStartColorAttributeIndex );
 | 
			
		||||
 | 
			
		||||
    if ( mEndColorExpression )
 | 
			
		||||
    {
 | 
			
		||||
      val2ColorVariant = mEndColorExpression->evaluate( &expressionContext );
 | 
			
		||||
      val2ColorVariant = mEndColorExpression->evaluate( &context.expressionContext() );
 | 
			
		||||
      ok |= mEndColorExpression->hasEvalError();
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
      val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
 | 
			
		||||
    //  else
 | 
			
		||||
    //    val2ColorVariant = mFeature.attribute( mEndColorAttributeIndex );
 | 
			
		||||
 | 
			
		||||
    startValColor = val1ColorVariant.toDouble( &ok );
 | 
			
		||||
    if ( !ok )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    endValColor = val2ColorVariant.toDouble( &ok );
 | 
			
		||||
    const double endValColor = val2ColorVariant.toDouble( &ok );
 | 
			
		||||
    if ( !ok )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    variationPerMapUnitColor = ( endValColor - startValColor ) / totalLength;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for ( const QgsPolylineXY &poly : std::as_const( lineStrings ) )
 | 
			
		||||
  for ( const QPolygonF &poly : parts )
 | 
			
		||||
  {
 | 
			
		||||
    double lengthFromStart = 0;
 | 
			
		||||
    for ( int i = 1; i < poly.count(); ++i )
 | 
			
		||||
    {
 | 
			
		||||
      QgsPointXY p1 = poly.at( i - 1 );
 | 
			
		||||
      QgsPointXY p2 = poly.at( i );
 | 
			
		||||
      const QPointF p1 = poly.at( i - 1 );
 | 
			
		||||
      const QPointF p2 = poly.at( i );
 | 
			
		||||
 | 
			
		||||
      double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
 | 
			
		||||
      double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
 | 
			
		||||
      lengthFromStart += p1.distance( p2 );
 | 
			
		||||
      double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
 | 
			
		||||
      double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
 | 
			
		||||
      mLineRender.render( v1c, v2c, v1w, v2w, p1, p2, renderContext );
 | 
			
		||||
      const double v1c = startValColor + variationPerMapUnitColor * lengthFromStart;
 | 
			
		||||
      const double v1w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
 | 
			
		||||
      lengthFromStart += std::sqrt( ( p1.x() - p2.x() ) * ( p1.x() - p2.x() ) + ( p1.y() - p2.y() ) * ( p1.y() - p2.y() ) );
 | 
			
		||||
      const double v2c = startValColor + variationPerMapUnitColor * lengthFromStart;
 | 
			
		||||
      const double v2w = startValWidth + variationPerMapUnitWidth * lengthFromStart;
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
@ -231,6 +231,9 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
     *
 | 
			
		||||
     * 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;
 | 
			
		||||
 | 
			
		||||
@ -238,10 +241,21 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
 | 
			
		||||
     * 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
 | 
			
		||||
     *
 | 
			
		||||
     * 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
 | 
			
		||||
     */
 | 
			
		||||
    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
 | 
			
		||||
     *
 | 
			
		||||
@ -257,11 +271,6 @@ class CORE_EXPORT QgsInterpolatedLineRenderer
 | 
			
		||||
    void adjustLine( double value, double value1, double value2, double &width, double &adjusting ) const;
 | 
			
		||||
    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;
 | 
			
		||||
};
 | 
			
		||||
@ -340,6 +349,7 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    QgsInterpolatedLineRenderer mLineRender;
 | 
			
		||||
 | 
			
		||||
    QString mStartWidthExpressionString;
 | 
			
		||||
    QString mEndWidthExpressionString;
 | 
			
		||||
    QString mStartColorExpressionString;
 | 
			
		||||
@ -354,7 +364,11 @@ class CORE_EXPORT QgsInterpolatedLineSymbolLayer : public QgsLineSymbolLayer
 | 
			
		||||
    std::unique_ptr<QgsExpression> mEndWithExpression;
 | 
			
		||||
    std::unique_ptr<QgsExpression> mStartColorExpression;
 | 
			
		||||
    std::unique_ptr<QgsExpression> mEndColorExpression;
 | 
			
		||||
    QgsFeature mFeature;
 | 
			
		||||
 | 
			
		||||
    QVector< QPolygonF > mLineParts;
 | 
			
		||||
    bool mRenderingFeature = false;
 | 
			
		||||
 | 
			
		||||
    void render( const QVector< QPolygonF > &parts, QgsRenderContext &context );
 | 
			
		||||
 | 
			
		||||
    QVariant colorRampShaderProperties() const;
 | 
			
		||||
    static QgsColorRampShader createColorRampShaderFromProperties( const QVariant &properties );
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user