Skip to content

Commit

Permalink
[needs-docs] When running in label engine v2 mode, discard any candid…
Browse files Browse the repository at this point in the history
…ates where

they collide with an obstacle feature of greater weight when compared
to the label's priority

Previously, obstacle weight was used ONLY to rank a features' label
candidates relative to each other, but was never used to actually prune candidates
completely. This meant that the labeling obstacle functionality was
confusing and frustrating for users to work with -- because despite
setting layers as the maximum possible blocking weight, you'd still
see labels being placed over these features (e.g. where the labeling
engine had no other choice).

Now, (when a project is set to v2 labeling engine mode), labels will
NEVER be placed over obstacles of greater weight. This means that
labels will potentially be omitted if the only choice is to place
them over a high weighting obstacle. But ultimately, that's much
more understandable for users -- they've manually set a particular
layer to a high obstacle factor, so we should respect that and
never place labels on these features.

In the end, this change makes the labeling placement much simpler
to understand for users, and should give power users a much
nicer experience all round.

Funded by the QGIS grants program
  • Loading branch information
nyalldawson committed Dec 3, 2019
1 parent bee6ab8 commit 19b8d43
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 5 deletions.
26 changes: 23 additions & 3 deletions src/core/pal/costcalculator.cpp
Expand Up @@ -35,7 +35,7 @@ bool CostCalculator::candidateSortShrink( const std::unique_ptr< LabelPosition >
return c1->cost() > c2->cost();
}

void CostCalculator::addObstacleCostPenalty( LabelPosition *lp, FeaturePart *obstacle )
void CostCalculator::addObstacleCostPenalty( LabelPosition *lp, FeaturePart *obstacle, Pal *pal )
{
int n = 0;
double dist;
Expand Down Expand Up @@ -83,11 +83,30 @@ void CostCalculator::addObstacleCostPenalty( LabelPosition *lp, FeaturePart *obs
break;
}

//scale cost by obstacle's factor
double obstacleCost = obstacle->obstacleFactor() * double( n );
if ( n > 0 )
lp->setConflictsWithObstacle( true );

//scale cost by obstacle's factor
double obstacleCost = obstacle->obstacleFactor() * double( n );
switch ( pal->placementVersion() )
{
case QgsLabelingEngineSettings::PlacementEngineVersion1:
break;

case QgsLabelingEngineSettings::PlacementEngineVersion2:
{
// obstacle factor is from 0 -> 2, label priority is from 1 -> 0. argh!
const double priority = 2 * ( 1 - lp->feature->calculatePriority() );
const double obstaclePriority = obstacle->obstacleFactor();

// if feature priority is < obstaclePriorty, there's a hard conflict...
if ( n > 0 && ( priority < obstaclePriority && !qgsDoubleNear( priority, obstaclePriority, 0.001 ) ) )
{
lp->setHasHardObstacleConflict( true );
}
break;
}
}

// label cost is penalized
lp->setCost( lp->cost() + obstacleCost );
Expand Down Expand Up @@ -185,6 +204,7 @@ std::size_t CostCalculator::finalizeCandidatesCosts( Feats *feat, std::size_t ma
}
while ( stop == 0 && discrim < feat->candidates.back()->cost() + 2.0 );

// THIS LOOKS SUSPICIOUS -- it clamps all costs to a fixed value??
if ( discrim > 1.5 )
{
for ( std::size_t k = 0; k < stop; k++ )
Expand Down
3 changes: 2 additions & 1 deletion src/core/pal/costcalculator.h
Expand Up @@ -29,6 +29,7 @@ namespace pal
{
class Feats;
class LabelPosition;
class Pal;

/**
* \ingroup core
Expand All @@ -37,7 +38,7 @@ namespace pal
{
public:
//! Increase candidate's cost according to its collision with passed feature
static void addObstacleCostPenalty( LabelPosition *lp, pal::FeaturePart *obstacle );
static void addObstacleCostPenalty( LabelPosition *lp, pal::FeaturePart *obstacle, Pal *pal );

//! Calculates the costs for polygon label candidates
static void setPolygonCandidatesCost( std::size_t nblp, std::vector<std::unique_ptr<pal::LabelPosition> > &lPos, RTree<pal::FeaturePart *, double, 2, double> *obstacles, double bbx[4], double bby[4] );
Expand Down
9 changes: 8 additions & 1 deletion src/core/pal/labelposition.cpp
Expand Up @@ -407,6 +407,13 @@ void LabelPosition::setConflictsWithObstacle( bool conflicts )
nextPart->setConflictsWithObstacle( conflicts );
}

void LabelPosition::setHasHardObstacleConflict( bool conflicts )
{
mHasHardConflict = conflicts;
if ( nextPart )
nextPart->setHasHardObstacleConflict( conflicts );
}

bool LabelPosition::polygonObstacleCallback( FeaturePart *obstacle, void *ctx )
{
PolygonCostCalculator *pCost = reinterpret_cast< PolygonCostCalculator * >( ctx );
Expand Down Expand Up @@ -453,7 +460,7 @@ bool LabelPosition::pruneCallback( LabelPosition *candidatePosition, void *ctx )
return true;
}

CostCalculator::addObstacleCostPenalty( candidatePosition, obstaclePart );
CostCalculator::addObstacleCostPenalty( candidatePosition, obstaclePart, ( reinterpret_cast< PruneCtx * >( ctx ) )->pal );

return true;
}
Expand Down
17 changes: 17 additions & 0 deletions src/core/pal/labelposition.h
Expand Up @@ -217,6 +217,22 @@ namespace pal
*/
bool conflictsWithObstacle() const { return mHasObstacleConflict; }

/**
* Sets whether the position is marked as having a hard conflict with an obstacle feature.
* A hard conflict means that the placement should (usually) not be considered, because the candidate
* conflicts with a obstacle of sufficient weight.
* \see hasHardObstacleConflict()
*/
void setHasHardObstacleConflict( bool conflicts );

/**
* Returns whether the position is marked as having a hard conflict with an obstacle feature.
* A hard conflict means that the placement should (usually) not be considered, because the candidate
* conflicts with a obstacle of sufficient weight.
* \see setHasHardObstacleConflict()
*/
bool hasHardObstacleConflict() const { return mHasHardConflict; }

//! Make sure the cost is less than 1
void validateCost();

Expand Down Expand Up @@ -329,6 +345,7 @@ namespace pal
private:
double mCost;
bool mHasObstacleConflict;
bool mHasHardConflict = false;
int mUpsideDownCharCount;

/**
Expand Down
21 changes: 21 additions & 0 deletions src/core/pal/pal.cpp
Expand Up @@ -380,6 +380,27 @@ std::unique_ptr<Problem> Pal::extract( const QgsRectangle &extent, const QgsGeom
feat->candidates.pop_back();
}

switch ( mPlacementVersion )
{
case QgsLabelingEngineSettings::PlacementEngineVersion1:
break;

case QgsLabelingEngineSettings::PlacementEngineVersion2:
{
// v2 placement rips out candidates where the candidate cost is too high when compared to
// their inactive cost
feat->candidates.erase( std::remove_if( feat->candidates.begin(), feat->candidates.end(), [ & ]( std::unique_ptr< LabelPosition > &candidate )
{
if ( candidate->hasHardObstacleConflict() )
{
feat->candidates.back()->removeFromIndex( prob->mAllCandidatesIndex );
return true;
}
return false;
} ), feat->candidates.end() );
}
}

if ( isCanceled() )
return nullptr;

Expand Down

0 comments on commit 19b8d43

Please sign in to comment.