Skip to content

Commit

Permalink
[pal] Refine logic for costing polygon candidates
Browse files Browse the repository at this point in the history
Instead of just considering the "candidate furthest from the polygon
rings" as the best, also consider that candidates closer to the
overall polygon centroid are better than those further from the centroid.

I.e. if two candidates are similarish distances from a ring, pick the
one closer to the centroid instead of the one further from the centroid
(even if that further one is a tiny bit more distant from a ring)
  • Loading branch information
nyalldawson committed Dec 29, 2019
1 parent 77b3d99 commit c924ce5
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 11 deletions.
67 changes: 56 additions & 11 deletions src/core/pal/costcalculator.cpp
Expand Up @@ -111,32 +111,72 @@ void CostCalculator::calculateCandidatePolygonRingDistanceCosts( std::vector< st
{
// first we calculate the ring distance cost for all candidates for this feature. We then use the range
// of distance costs to calculate a standardised scaling for the costs
QHash< LabelPosition *, double > polygonRingDistanceCosts;
double minCandidateRingDistanceCost = std::numeric_limits< double >::max();
double maxCandidateRingDistanceCost = std::numeric_limits< double >::lowest();
QHash< LabelPosition *, double > polygonRingDistances;
double minCandidateRingDistance = std::numeric_limits< double >::max();
double maxCandidateRingDistance = std::numeric_limits< double >::lowest();
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
const double candidatePolygonRingDistanceCost = calculatePolygonRingDistance( pos.get(), bbx, bby );
const double candidatePolygonRingDistance = calculatePolygonRingDistance( pos.get(), bbx, bby );

minCandidateRingDistanceCost = std::min( minCandidateRingDistanceCost, candidatePolygonRingDistanceCost );
maxCandidateRingDistanceCost = std::max( maxCandidateRingDistanceCost, candidatePolygonRingDistanceCost );
minCandidateRingDistance = std::min( minCandidateRingDistance, candidatePolygonRingDistance );
maxCandidateRingDistance = std::max( maxCandidateRingDistance, candidatePolygonRingDistance );

polygonRingDistanceCosts.insert( pos.get(), candidatePolygonRingDistanceCost );
polygonRingDistances.insert( pos.get(), candidatePolygonRingDistance );
}

// define the cost's range, if range is too small, just ignore the ring distance cost
const double costRange = maxCandidateRingDistanceCost - minCandidateRingDistanceCost;
const double costRange = maxCandidateRingDistance - minCandidateRingDistance;
if ( costRange <= EPSILON )
return;

const double normalizer = 0.0020 / costRange;

// adjust cost => the best is 0.0001, the worst is 0.0021
// adjust cost => the best is 0, the worst is 0.002
// others are set proportionally between best and worst
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
const double originalPolygonRingDistanceCost = polygonRingDistanceCosts.value( pos.get() );
pos->setCost( pos->cost() + 0.0021 - ( originalPolygonRingDistanceCost - minCandidateRingDistanceCost ) * normalizer );
const double polygonRingDistanceCost = polygonRingDistances.value( pos.get() );
pos->setCost( pos->cost() + 0.002 - ( polygonRingDistanceCost - minCandidateRingDistance ) * normalizer );
}
}

void CostCalculator::calculateCandidatePolygonCentroidDistanceCosts( pal::FeaturePart *feature, std::vector<std::unique_ptr<LabelPosition> > &lPos )
{
double cx, cy;
feature->getCentroid( cx, cy );

// first we calculate the centroid distance cost for all candidates for this feature. We then use the range
// of distance costs to calculate a standardised scaling for the costs
QHash< LabelPosition *, double > polygonCentroidDistances;
double minCandidateCentroidDistance = std::numeric_limits< double >::max();
double maxCandidateCentroidDistance = std::numeric_limits< double >::lowest();
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
const double lPosX = ( pos->x[0] + pos->x[2] ) / 2.0;
const double lPosY = ( pos->y[0] + pos->y[2] ) / 2.0;

const double candidatePolygonCentroidDistance = std::sqrt( ( cx - lPosX ) * ( cx - lPosX ) + ( cy - lPosY ) * ( cy - lPosY ) );

minCandidateCentroidDistance = std::min( minCandidateCentroidDistance, candidatePolygonCentroidDistance );
maxCandidateCentroidDistance = std::max( maxCandidateCentroidDistance, candidatePolygonCentroidDistance );

polygonCentroidDistances.insert( pos.get(), candidatePolygonCentroidDistance );
}

// define the cost's range, if range is too small, just ignore the ring distance cost
const double costRange = maxCandidateCentroidDistance - minCandidateCentroidDistance;
if ( costRange <= EPSILON )
return;

const double normalizer = 0.001 / costRange;

// adjust cost => the closest is 0, the furthest is 0.001
// others are set proportionally between best and worst
// NOTE: centroid cost range may need adjusting with respect to ring distance range!
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
const double polygonCentroidDistance = polygonCentroidDistances.value( pos.get() );
pos->setCost( pos->cost() + ( polygonCentroidDistance - minCandidateCentroidDistance ) * normalizer );
}
}

Expand Down Expand Up @@ -207,7 +247,12 @@ void CostCalculator::finalizeCandidatesCosts( Feats *feat, double bbx[4], double
{
int arrangement = feat->feature->layer()->arrangement();
if ( arrangement == QgsPalLayerSettings::Free || arrangement == QgsPalLayerSettings::Horizontal )
{
// prefer positions closer to the pole of inaccessibilities
calculateCandidatePolygonRingDistanceCosts( feat->candidates, bbx, bby );
// ...of these, prefer positions closer to the overall polygon centroid
calculateCandidatePolygonCentroidDistanceCosts( feat->feature, feat->candidates );
}
}

// add size penalty (small lines/polygons get higher cost)
Expand Down
7 changes: 7 additions & 0 deletions src/core/pal/costcalculator.h
Expand Up @@ -46,6 +46,13 @@ namespace pal
*/
static void calculateCandidatePolygonRingDistanceCosts( std::vector<std::unique_ptr<pal::LabelPosition> > &lPos, double bbx[4], double bby[4] );

/**
* Updates the costs for polygon label candidates by considering the distance between the
* candidates and the polygon centroid (i.e. given labels at similar distances from polygon rings,
* prefer labels closer to the centroid).
*/
static void calculateCandidatePolygonCentroidDistanceCosts( pal::FeaturePart *feature, std::vector<std::unique_ptr<pal::LabelPosition> > &lPos );

//! Calculates the distance between a label candidate and the closest ring for a polygon feature
static double calculatePolygonRingDistance( LabelPosition *candidate, double bbx[4], double bby[4] );

Expand Down

0 comments on commit c924ce5

Please sign in to comment.