Skip to content

Commit

Permalink
Merge pull request #3 from qgis/master
Browse files Browse the repository at this point in the history
Merge from QGIS
  • Loading branch information
longhuan2018 committed Dec 30, 2019
2 parents 6ba9d0d + 578f32a commit 34f5c9b
Show file tree
Hide file tree
Showing 33 changed files with 1,322 additions and 548 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Grid Buffer
grid_tools
QgsProcessingParameterRasterLayer|FEATURES|Features Grid|None|False
QgsProcessingParameterNumber|DIST|Distance|QgsProcessingParameterNumber.Integer|1000|False|None|None
QgsProcessingParameterEnum|BUFFERTYPE|Buffer Distance|[0] Fixed;[1] Cell value
QgsProcessingParameterNumber|DISTANCE|Distance|QgsProcessingParameterNumber.Double|1000.0|False|0.0|None
QgsProcessingParameterEnum|TYPE|Buffer Distance|[0] fixed;[1] cell's value
QgsProcessingParameterRasterDestination|BUFFER|Buffer Grid
2 changes: 1 addition & 1 deletion src/app/labeling/qgslabelengineconfigdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ QgsLabelEngineConfigWidget::QgsLabelEngineConfigWidget( QWidget *parent )
} );

spinCandLine->setClearValue( 5 );
spinCandPolygon->setClearValue( 10 );
spinCandPolygon->setClearValue( 2.5 );

// candidate numbers
spinCandLine->setValue( engineSettings.maximumLineCandidatesPerCm() );
Expand Down
2 changes: 1 addition & 1 deletion src/core/labeling/qgslabelingenginesettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void QgsLabelingEngineSettings::readSettingsFromProject( QgsProject *prj )
bool saved = false;
mSearchMethod = static_cast< Search >( prj->readNumEntry( QStringLiteral( "PAL" ), QStringLiteral( "/SearchMethod" ), static_cast< int >( Chain ), &saved ) );
mMaxLineCandidatesPerCm = prj->readDoubleEntry( QStringLiteral( "PAL" ), QStringLiteral( "/CandidatesLinePerCM" ), 5, &saved );
mMaxPolygonCandidatesPerCmSquared = prj->readDoubleEntry( QStringLiteral( "PAL" ), QStringLiteral( "/CandidatesPolygonPerCM" ), 10, &saved );
mMaxPolygonCandidatesPerCmSquared = prj->readDoubleEntry( QStringLiteral( "PAL" ), QStringLiteral( "/CandidatesPolygonPerCM" ), 2.5, &saved );

mFlags = nullptr;
if ( prj->readBoolEntry( QStringLiteral( "PAL" ), QStringLiteral( "/ShowingCandidates" ), false, &saved ) ) mFlags |= DrawCandidates;
Expand Down
2 changes: 1 addition & 1 deletion src/core/labeling/qgslabelingenginesettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ class CORE_EXPORT QgsLabelingEngineSettings

// maximum density of line/polygon candidates per mm
double mMaxLineCandidatesPerCm = 5;
double mMaxPolygonCandidatesPerCmSquared = 10;
double mMaxPolygonCandidatesPerCmSquared = 2.5;

QColor mUnplacedLabelColor = QColor( 255, 0, 0 );

Expand Down
185 changes: 94 additions & 91 deletions src/core/pal/costcalculator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ bool CostCalculator::candidateSortGrow( const std::unique_ptr< LabelPosition > &
return c1->cost() < c2->cost();
}

bool CostCalculator::candidateSortShrink( const std::unique_ptr< LabelPosition > &c1, const std::unique_ptr< LabelPosition > &c2 )
{
return c1->cost() > c2->cost();
}

void CostCalculator::addObstacleCostPenalty( LabelPosition *lp, FeaturePart *obstacle, Pal *pal )
{
int n = 0;
Expand Down Expand Up @@ -112,107 +107,118 @@ void CostCalculator::addObstacleCostPenalty( LabelPosition *lp, FeaturePart *obs
lp->setCost( lp->cost() + obstacleCost );
}

void CostCalculator::setPolygonCandidatesCost( std::size_t nblp, std::vector< std::unique_ptr< LabelPosition > > &lPos, PalRtree<FeaturePart> *obstacles, double bbx[4], double bby[4] )
void CostCalculator::calculateCandidatePolygonRingDistanceCosts( std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4] )
{
double normalizer;
// compute raw cost
for ( std::size_t i = 0; i < nblp; ++i )
setCandidateCostFromPolygon( lPos[ i ].get(), obstacles, bbx, bby );
// 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 > polygonRingDistances;
double minCandidateRingDistance = std::numeric_limits< double >::max();
double maxCandidateRingDistance = std::numeric_limits< double >::lowest();
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
const double candidatePolygonRingDistance = calculatePolygonRingDistance( pos.get(), bbx, bby );

// lPos with big values came first (value = min distance from label to Polygon's Perimeter)
// IMPORTANT - only want to sort first nblp positions. The rest have not had the cost
// calculated so will have nonsense values
std::sort( lPos.begin(), lPos.begin() + nblp, candidateSortShrink );
minCandidateRingDistance = std::min( minCandidateRingDistance, candidatePolygonRingDistance );
maxCandidateRingDistance = std::max( maxCandidateRingDistance, candidatePolygonRingDistance );

// define the value's range
double cost_max = lPos.front()->cost();
double cost_min = lPos.back()->cost();
polygonRingDistances.insert( pos.get(), candidatePolygonRingDistance );
}

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

if ( cost_max > EPSILON )
const double normalizer = 0.0020 / costRange;

// 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 )
{
normalizer = 0.0020 / cost_max;
const double polygonRingDistanceCost = polygonRingDistances.value( pos.get() );
pos->setCost( pos->cost() + 0.002 - ( polygonRingDistanceCost - minCandidateRingDistance ) * normalizer );
}
else
}

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 )
{
normalizer = 1;
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 );
}

// adjust cost => the best is 0.0001, the worst is 0.0021
// 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
for ( std::size_t i = 0; i < nblp; ++i )
// NOTE: centroid cost range may need adjusting with respect to ring distance range!
for ( std::unique_ptr< LabelPosition > &pos : lPos )
{
LabelPosition *pos = lPos[ i ].get();
//if (cost_max - cost_min < EPSILON)
if ( cost_max > EPSILON )
{
pos->setCost( 0.0021 - ( pos->cost() - cost_min ) * normalizer );
}
else
{
//pos->cost = 0.0001 + (pos->cost - cost_min) * normalizer;
pos->setCost( 0.0001 );
}
const double polygonCentroidDistance = polygonCentroidDistances.value( pos.get() );
pos->setCost( pos->cost() + ( polygonCentroidDistance - minCandidateCentroidDistance ) * normalizer );
}
}

void CostCalculator::setCandidateCostFromPolygon( LabelPosition *lp, PalRtree<FeaturePart> *obstacles, double bbx[4], double bby[4] )
double CostCalculator::calculatePolygonRingDistance( LabelPosition *candidate, double bbx[4], double bby[4] )
{
// NOTE: PolygonCostCalculator calculates the min distance between the CENTER of the
// candidate and various polygon boundaries

// TODO 1: Consider whether distance calculation should use min distance to the candidate rectangle
// instead of just the center
PolygonCostCalculator pCost( lp );
CandidatePolygonRingDistanceCalculator ringDistanceCalculator( candidate );

// first, check max distance to outside of polygon
// TODO 2: there's a bug here -- if a candidate's center falls outside the polygon, then a larger
// distance to the polygon boundary is being considered as the best placement. That's clearly wrong --
// if any part of label candidate sits outside the polygon, we should first prefer candidates which sit
// entirely WITHIN the polygon, or failing that, candidates which are CLOSER to the polygon boundary, not further from it!
pCost.update( lp->feature );
ringDistanceCalculator.addRing( candidate->feature );

// prefer candidates further from the outside of map rather then those close to the outside of the map
// TODO 3: should be using the actual map boundary here, not the bounding box
PointSet extent( 4, bbx, bby );
pCost.update( &extent );
ringDistanceCalculator.addRing( &extent );

// prefer candidates which further from interior rings (holes) of the polygon
obstacles->intersects( lp->feature->boundingBox(), [&pCost]( const FeaturePart * obstacle )->bool
for ( int i = 0; i < candidate->feature->getNumSelfObstacles(); ++i )
{
LabelPosition *lp = pCost.getLabel();

// we only care about obstacles which are polygon holes, AND only holes which belong to this same feature
// because:
// 1. holes for other features are a good place to put labels for this feature
// 2. we handle obstacle avoidance for all candidate types elsewhere -- here we are solely concerned with
// ranking the relative candidates for a single feature while considering that feature's shape alone.
if ( ( obstacle == lp->feature ) || ( !obstacle->getHoleOf() ) || ( obstacle->getHoleOf() && obstacle->getHoleOf() != lp->feature ) )
{
return true;
}

pCost.update( obstacle );

return true;
} );
ringDistanceCalculator.addRing( candidate->feature->getSelfObstacle( i ) );
}

// TODO 4: probably a bug here -- by calling setCost here we discard all existing candidate costs,
// e.g. those determined via conflicts with obstacles
lp->setCost( pCost.getCost() );
return ringDistanceCalculator.minimumDistance();
}

std::size_t CostCalculator::finalizeCandidatesCosts( Feats *feat, std::size_t max_p, PalRtree<FeaturePart> *obstacles, double bbx[4], double bby[4] )
void CostCalculator::finalizeCandidatesCosts( Feats *feat, double bbx[4], double bby[4] )
{
// If candidates list is smaller than expected
if ( max_p == 0 || max_p > feat->candidates.size() )
max_p = feat->candidates.size();
//
// sort candidates list, best label to worst
std::sort( feat->candidates.begin(), feat->candidates.end(), candidateSortGrow );

// try to exclude all conflitual labels (good ones have cost < 1 by pruning)
// Original nonsense comment from pal library:
// "try to exclude all conflitual labels (good ones have cost < 1 by pruning)"
// my interpretation: it appears this scans through the candidates and chooses some threshold
// based on the candidate cost, after which all remaining candidates are simply discarded
// my suspicion: I don't think this is needed (or wanted) here, and is reflective of the fact
// that obstacle costs are too low vs inferior candidate placement costs (i.e. without this,
// an "around point" label would rather be placed over an obstacle then be placed in a position > 6 o'clock
double discrim = 0.0;
std::size_t stop = 0;
do
Expand All @@ -230,48 +236,45 @@ std::size_t CostCalculator::finalizeCandidatesCosts( Feats *feat, std::size_t ma
feat->candidates[ k ]->setCost( 0.0021 );
}

if ( max_p > stop )
max_p = stop;
if ( feat->candidates.size() > stop )
{
feat->candidates.resize( stop );
}

// Sets costs for candidates of polygon

if ( feat->feature->getGeosType() == GEOS_POLYGON )
{
int arrangement = feat->feature->layer()->arrangement();
if ( arrangement == QgsPalLayerSettings::Free || arrangement == QgsPalLayerSettings::Horizontal )
setPolygonCandidatesCost( stop, feat->candidates, obstacles, bbx, bby );
{
// 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)
feat->feature->addSizePenalty( max_p, feat->candidates, bbx, bby );

return max_p;
feat->feature->addSizePenalty( feat->candidates, bbx, bby );
}

PolygonCostCalculator::PolygonCostCalculator( LabelPosition *lp ) : lp( lp )
CandidatePolygonRingDistanceCalculator::CandidatePolygonRingDistanceCalculator( LabelPosition *candidate )
: mPx( ( candidate->x[0] + candidate->x[2] ) / 2.0 )
, mPy( ( candidate->y[0] + candidate->y[2] ) / 2.0 )
{
px = ( lp->x[0] + lp->x[2] ) / 2.0;
py = ( lp->y[0] + lp->y[2] ) / 2.0;

dist = std::numeric_limits<double>::max();
ok = false;
}

void PolygonCostCalculator::update( const pal::PointSet *pset )
void CandidatePolygonRingDistanceCalculator::addRing( const pal::PointSet *ring )
{
double d = pset->minDistanceToPoint( px, py );
if ( d < dist )
double d = ring->minDistanceToPoint( mPx, mPy );
if ( d < mMinDistance )
{
dist = d;
mMinDistance = d;
}
}

LabelPosition *PolygonCostCalculator::getLabel()
{
return lp;
}

double PolygonCostCalculator::getCost()
double CandidatePolygonRingDistanceCalculator::minimumDistance() const
{
return ( 4 * dist );
return mMinDistance;
}
58 changes: 32 additions & 26 deletions src/core/pal/costcalculator.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,57 +40,63 @@ namespace pal
//! Increase candidate's cost according to its collision with passed feature
static void addObstacleCostPenalty( pal::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, PalRtree< FeaturePart > *obstacles, double bbx[4], double bby[4] );
/**
* Updates the costs for polygon label candidates by considering the distance between the
* candidates and the nearest polygon ring (i.e. prefer labels closer to the pole of inaccessibility).
*/
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 );

//! Sets cost to the smallest distance between lPos's centroid and a polygon stored in geometry field
static void setCandidateCostFromPolygon( LabelPosition *lp, PalRtree< FeaturePart > *obstacles, double bbx[4], double bby[4] );
//! 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] );

//! Sort candidates by costs, skip the worse ones, evaluate polygon candidates
static std::size_t finalizeCandidatesCosts( Feats *feat, std::size_t max_p, PalRtree< FeaturePart > *obstacles, double bbx[4], double bby[4] );
static void finalizeCandidatesCosts( Feats *feat, double bbx[4], double bby[4] );

/**
* Sorts label candidates in ascending order of cost
*/
static bool candidateSortGrow( const std::unique_ptr<pal::LabelPosition> &c1, const std::unique_ptr<pal::LabelPosition> &c2 );

/**
* Sorts label candidates in descending order of cost
*/
static bool candidateSortShrink( const std::unique_ptr<pal::LabelPosition> &c1, const std::unique_ptr<pal::LabelPosition> &c2 );
};

/**
* \ingroup core
* \brief Data structure to compute polygon's candidates costs
*
* Eight segments from center of candidate to (rpx,rpy) points (0°, 45°, 90°, ..., 315°)
* dist store the shortest square distance from the center to an object
* ok[i] is the to TRUE whether the corresponding dist[i] is set
*
* \brief Calculates distance from a label candidate to nearest polygon ring.
* \note not available in Python bindings
* \since QGIS 3.12
*/
class PolygonCostCalculator
class CandidatePolygonRingDistanceCalculator
{

public:
explicit PolygonCostCalculator( LabelPosition *lp );

/**
* Updates cost.
* Constructor for PolygonRingDistanceCalculator, for the specified label \a candidate.
*/
void update( const pal::PointSet *pset );
explicit CandidatePolygonRingDistanceCalculator( LabelPosition *candidate );

double getCost();
/**
* Adds a \a ring to the calculation, updating the minimumDistance() value if
* the rings is closer to the candidate then previously added rings.
*/
void addRing( const pal::PointSet *ring );

LabelPosition *getLabel();
/**
* Returns the minimum distance between the candidate and all added rings.
*/
double minimumDistance() const;

private:

LabelPosition *lp = nullptr;
double px, py;
double dist;
bool ok;
double mPx;
double mPy;
double mMinDistance = std::numeric_limits<double>::max();
};
}

Expand Down
Loading

0 comments on commit 34f5c9b

Please sign in to comment.