Skip to content

Commit f7cd3f9

Browse files
committedApr 23, 2023
Bring feature order and rendering order together in a useful way
There are two ways of taking "order" into account when rendering: 1. You can control the feature rendering order which essentially does an "order by" of your layer data. 2. You can enable symbol levels which defines which styles are rendered in which order. This commit addresses a problem when trying to bring (1) and (2) together. When both are enabled, the rendering order (2) trumps the order given by (1), essentially ignoring the order from (1). With this commit rendering instead starts "from scratch" every time the "order by" attributes have a new value. This allows, for instance, to render complex roads networks correctly. Ordering via (1) is used to draw roads going over bridges etc. in the correct order while ordering via (2) is used to render road styles correctly (thicker dark line, then thinner light line to get a roads with casing). The patch works by putting all features that need to be rendered not in a single QHash (Symbol->Feature) but a QList of those hashes. Every time at least one of the attributes used for the "order by" changes, a new element is added to that list. The patch is rather simple, it just looks a bit larger due to the extra indentation needed. I thought about making this configurable, but I don't see any use case where it makes sense to have (1) and (2) enabled where you don't want this behaviour. Fixes #42428 See #17276
1 parent 2458023 commit f7cd3f9

File tree

1 file changed

+85
-41
lines changed

1 file changed

+85
-41
lines changed
 

‎src/core/vector/qgsvectorlayerrenderer.cpp

Lines changed: 85 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,28 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureRenderer *renderer, Q
544544
{
545545
const bool isMainRenderer = renderer == mRenderer;
546546

547-
QHash< QgsSymbol *, QList<QgsFeature> > features; // key = symbol, value = array of features
547+
// We need to figure out in which order all the features should be rendered.
548+
// Ordering is based on (a) a "level" which is determined by the configured
549+
// feature rendering order" and (b) the symbol level. The "level" is
550+
// determined by the values of the attributes defined in the feature
551+
// rendering order settings. Each time the attribute(s) have a new distinct
552+
// value, a new empty QHash is added to the "features" list. This QHash is
553+
// then filled by mappings from the symbol to a list of all the features
554+
// that should be rendered by that symbol.
555+
//
556+
// If orderBy is not enabled, this list will only ever contain a single
557+
// element.
558+
QList<QHash< QgsSymbol *, QList<QgsFeature> >> features;
559+
560+
// We have at least one "level" for the features.
561+
features.push_back( {} );
562+
563+
QSet<int> orderByAttributeIdx;
564+
if ( renderer->orderByEnabled() )
565+
{
566+
orderByAttributeIdx = renderer->orderBy().usedAttributeIndices( mSource->fields() );
567+
}
568+
548569
QgsRenderContext &context = *renderContext();
549570

550571
QgsSingleSymbolRenderer *selRenderer = nullptr;
@@ -572,6 +593,7 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureRenderer *renderer, Q
572593

573594
// 1. fetch features
574595
QgsFeature fet;
596+
std::vector<QVariant> prevValues; // previous values of ORDER BY attributes
575597
while ( fit.nextFeature( fet ) )
576598
{
577599
if ( context.renderingStopped() )
@@ -595,13 +617,33 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureRenderer *renderer, Q
595617
continue;
596618
}
597619

620+
if ( renderer->orderByEnabled() )
621+
{
622+
std::vector<QVariant> currentValues;
623+
for ( auto const idx : orderByAttributeIdx )
624+
{
625+
currentValues.push_back( fet.attribute( idx ) );
626+
}
627+
if ( prevValues.empty() )
628+
{
629+
prevValues = std::move( currentValues );
630+
}
631+
else if ( currentValues != prevValues )
632+
{
633+
// Current values of ORDER BY attributes are different than previous
634+
// values of these attributes. Start a new level.
635+
prevValues = std::move( currentValues );
636+
features.push_back( {} );
637+
}
638+
}
639+
598640
if ( !context.testFlag( Qgis::RenderContextFlag::SkipSymbolRendering ) )
599641
{
600-
if ( !features.contains( sym ) )
642+
if ( !features.back().contains( sym ) )
601643
{
602-
features.insert( sym, QList<QgsFeature>() );
644+
features.back().insert( sym, QList<QgsFeature>() );
603645
}
604-
features[sym].append( fet );
646+
features.back()[sym].append( fet );
605647
}
606648

607649
// new labeling engine
@@ -637,7 +679,7 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureRenderer *renderer, Q
637679

638680
scopePopper.reset();
639681

640-
if ( features.empty() )
682+
if ( features.back().empty() )
641683
{
642684
// nothing to draw
643685
stopRenderer( renderer, selRenderer );
@@ -666,52 +708,54 @@ void QgsVectorLayerRenderer::drawRendererLevels( QgsFeatureRenderer *renderer, Q
666708
context.setFeatureClipGeometry( mClipFeatureGeom );
667709

668710
// 2. draw features in correct order
669-
for ( int l = 0; l < levels.count(); l++ )
711+
for ( auto &featureLists : features )
670712
{
671-
QgsSymbolLevel &level = levels[l];
672-
for ( int i = 0; i < level.count(); i++ )
713+
for ( int l = 0; l < levels.count(); l++ )
673714
{
674-
QgsSymbolLevelItem &item = level[i];
675-
if ( !features.contains( item.symbol() ) )
715+
const QgsSymbolLevel &level = levels[l];
716+
for ( int i = 0; i < level.count(); i++ )
676717
{
677-
QgsDebugMsg( QStringLiteral( "level item's symbol not found!" ) );
678-
continue;
679-
}
680-
int layer = item.layer();
681-
QList<QgsFeature> &lst = features[item.symbol()];
682-
QList<QgsFeature>::iterator fit;
683-
for ( fit = lst.begin(); fit != lst.end(); ++fit )
684-
{
685-
if ( context.renderingStopped() )
718+
const QgsSymbolLevelItem &item = level[i];
719+
if ( !featureLists.contains( item.symbol() ) )
686720
{
687-
stopRenderer( renderer, selRenderer );
688-
return;
721+
QgsDebugMsg( QStringLiteral( "level item's symbol not found!" ) );
722+
continue;
689723
}
724+
const int layer = item.layer();
725+
const QList<QgsFeature> &lst = featureLists[item.symbol()];
726+
for ( auto fit = lst.begin(); fit != lst.end(); ++fit )
727+
{
728+
if ( context.renderingStopped() )
729+
{
730+
stopRenderer( renderer, selRenderer );
731+
return;
732+
}
690733

691-
bool sel = isMainRenderer && context.showSelection() && mSelectedFeatureIds.contains( fit->id() );
692-
// maybe vertex markers should be drawn only during the last pass...
693-
bool drawMarker = isMainRenderer && ( mDrawVertexMarkers && context.drawEditingInformation() && ( !mVertexMarkerOnlyForSelection || sel ) );
694-
695-
if ( ! mNoSetLayerExpressionContext )
696-
context.expressionContext().setFeature( *fit );
734+
const bool sel = isMainRenderer && context.showSelection() && mSelectedFeatureIds.contains( fit->id() );
735+
// maybe vertex markers should be drawn only during the last pass...
736+
const bool drawMarker = isMainRenderer && ( mDrawVertexMarkers && context.drawEditingInformation() && ( !mVertexMarkerOnlyForSelection || sel ) );
697737

698-
try
699-
{
700-
renderer->renderFeature( *fit, context, layer, sel, drawMarker );
738+
if ( ! mNoSetLayerExpressionContext )
739+
context.expressionContext().setFeature( *fit );
701740

702-
// as soon as first feature is rendered, we can start showing layer updates.
703-
// but if we are blocking render updates (so that a previously cached image is being shown), we wait
704-
// at most e.g. 3 seconds before we start forcing progressive updates.
705-
if ( !mBlockRenderUpdates || mElapsedTimer.elapsed() > MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
741+
try
706742
{
707-
mReadyToCompose = true;
743+
renderer->renderFeature( *fit, context, layer, sel, drawMarker );
744+
745+
// as soon as first feature is rendered, we can start showing layer updates.
746+
// but if we are blocking render updates (so that a previously cached image is being shown), we wait
747+
// at most e.g. 3 seconds before we start forcing progressive updates.
748+
if ( !mBlockRenderUpdates || mElapsedTimer.elapsed() > MAX_TIME_TO_USE_CACHED_PREVIEW_IMAGE )
749+
{
750+
mReadyToCompose = true;
751+
}
752+
}
753+
catch ( const QgsCsException &cse )
754+
{
755+
Q_UNUSED( cse )
756+
QgsDebugMsg( QStringLiteral( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" )
757+
.arg( fet.id() ).arg( cse.what() ) );
708758
}
709-
}
710-
catch ( const QgsCsException &cse )
711-
{
712-
Q_UNUSED( cse )
713-
QgsDebugMsg( QStringLiteral( "Failed to transform a point while drawing a feature with ID '%1'. Ignoring this feature. %2" )
714-
.arg( fet.id() ).arg( cse.what() ) );
715759
}
716760
}
717761
}

0 commit comments

Comments
 (0)
Please sign in to comment.