QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
costcalculator.cpp
Go to the documentation of this file.
1/***************************************************************************
2 costcalculator.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 by Martin Dobias
6 email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "costcalculator.h"
17
18#include <cfloat>
19#include <cmath>
20
21#include "feature.h"
22#include "labelposition.h"
23#include "layer.h"
24#include "pal.h"
26#include "util.h"
27
28using namespace pal;
29
30bool CostCalculator::candidateSortGrow( const std::unique_ptr< LabelPosition > &c1, const std::unique_ptr< LabelPosition > &c2 )
31{
32 return c1->cost() < c2->cost();
33}
34
36{
37 int n = 0;
38 double dist;
39 const double distlabel = lp->feature->getLabelDistance();
40
41 switch ( obstacle->getGeosType() )
42 {
43 case GEOS_POINT:
44
45 dist = lp->getDistanceToPoint( obstacle->x[0], obstacle->y[0], true );
46 if ( dist < 0 )
47 n = 2;
48 else if ( dist < distlabel )
49 //note this never happens at the moment - points are not obstacles if they don't fall
50 //within the label
51 n = 1;
52 else
53 n = 0;
54 break;
55
56 case GEOS_LINESTRING:
57
58 // Is one of label's borders crossing the line ?
59 n = ( lp->crossesLine( obstacle ) ? 1 : 0 );
60 break;
61
62 case GEOS_POLYGON:
63 // behavior depends on obstacle avoid type
64 switch ( obstacle->layer()->obstacleType() )
65 {
67 // n ranges from 0 -> 12
68 n = lp->polygonIntersectionCost( obstacle );
69 break;
71 // penalty may need tweaking, given that interior mode ranges up to 12
72 n = ( lp->crossesBoundary( obstacle ) ? 6 : 0 );
73 break;
75 // n is either 0 or 12
76 n = ( lp->intersectsWithPolygon( obstacle ) ? 12 : 0 );
77 break;
78 }
79
80 break;
81 }
82
83 //scale cost by obstacle's factor
84 const double obstacleCost = obstacle->obstacleSettings().factor() * double( n );
85 if ( n > 0 )
86 lp->setConflictsWithObstacle( true );
87
88 switch ( pal->placementVersion() )
89 {
91 break;
92
94 {
95 // obstacle factor is from 0 -> 2, label priority is from 1 -> 0. argh!
96 const double priority = 2 * ( 1 - lp->feature->calculatePriority() );
97 const double obstaclePriority = obstacle->obstacleSettings().factor();
98
99 // if feature priority is < obstaclePriority, there's a hard conflict...
100 if ( n > 0 && ( priority < obstaclePriority && !qgsDoubleNear( priority, obstaclePriority, 0.001 ) ) )
101 {
102 lp->setHasHardObstacleConflict( true );
103 }
104 break;
105 }
106 }
107
108 // label cost is penalized
109 lp->setCost( lp->cost() + obstacleCost );
110}
111
112void CostCalculator::calculateCandidatePolygonRingDistanceCosts( std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4] )
113{
114 // first we calculate the ring distance cost for all candidates for this feature. We then use the range
115 // of distance costs to calculate a standardised scaling for the costs
116 QHash< LabelPosition *, double > polygonRingDistances;
117 double minCandidateRingDistance = std::numeric_limits< double >::max();
118 double maxCandidateRingDistance = std::numeric_limits< double >::lowest();
119 for ( const std::unique_ptr< LabelPosition > &pos : lPos )
120 {
121 const double candidatePolygonRingDistance = calculatePolygonRingDistance( pos.get(), bbx, bby );
122
123 minCandidateRingDistance = std::min( minCandidateRingDistance, candidatePolygonRingDistance );
124 maxCandidateRingDistance = std::max( maxCandidateRingDistance, candidatePolygonRingDistance );
125
126 polygonRingDistances.insert( pos.get(), candidatePolygonRingDistance );
127 }
128
129 // define the cost's range, if range is too small, just ignore the ring distance cost
130 const double costRange = maxCandidateRingDistance - minCandidateRingDistance;
131 if ( costRange <= EPSILON )
132 return;
133
134 const double normalizer = 0.0020 / costRange;
135
136 // adjust cost => the best is 0, the worst is 0.002
137 // others are set proportionally between best and worst
138 for ( std::unique_ptr< LabelPosition > &pos : lPos )
139 {
140 const double polygonRingDistanceCost = polygonRingDistances.value( pos.get() );
141 pos->setCost( pos->cost() + 0.002 - ( polygonRingDistanceCost - minCandidateRingDistance ) * normalizer );
142 }
143}
144
145void CostCalculator::calculateCandidatePolygonCentroidDistanceCosts( pal::FeaturePart *feature, std::vector<std::unique_ptr<LabelPosition> > &lPos )
146{
147 double cx, cy;
148 feature->getCentroid( cx, cy );
149
150 // first we calculate the centroid distance cost for all candidates for this feature. We then use the range
151 // of distance costs to calculate a standardised scaling for the costs
152 QHash< LabelPosition *, double > polygonCentroidDistances;
153 double minCandidateCentroidDistance = std::numeric_limits< double >::max();
154 double maxCandidateCentroidDistance = std::numeric_limits< double >::lowest();
155 for ( std::unique_ptr< LabelPosition > &pos : lPos )
156 {
157 const double lPosX = ( pos->x[0] + pos->x[2] ) / 2.0;
158 const double lPosY = ( pos->y[0] + pos->y[2] ) / 2.0;
159
160 const double candidatePolygonCentroidDistance = QgsGeometryUtilsBase::distance2D( cx, cy, lPosX, lPosY );
161
162 minCandidateCentroidDistance = std::min( minCandidateCentroidDistance, candidatePolygonCentroidDistance );
163 maxCandidateCentroidDistance = std::max( maxCandidateCentroidDistance, candidatePolygonCentroidDistance );
164
165 polygonCentroidDistances.insert( pos.get(), candidatePolygonCentroidDistance );
166 }
167
168 // define the cost's range, if range is too small, just ignore the ring distance cost
169 const double costRange = maxCandidateCentroidDistance - minCandidateCentroidDistance;
170 if ( costRange <= EPSILON )
171 return;
172
173 const double normalizer = 0.001 / costRange;
174
175 // adjust cost => the closest is 0, the furthest is 0.001
176 // others are set proportionally between best and worst
177 // NOTE: centroid cost range may need adjusting with respect to ring distance range!
178 for ( std::unique_ptr< LabelPosition > &pos : lPos )
179 {
180 const double polygonCentroidDistance = polygonCentroidDistances.value( pos.get() );
181 pos->setCost( pos->cost() + ( polygonCentroidDistance - minCandidateCentroidDistance ) * normalizer );
182 }
183}
184
185double CostCalculator::calculatePolygonRingDistance( LabelPosition *candidate, double bbx[4], double bby[4] )
186{
187 // TODO 1: Consider whether distance calculation should use min distance to the candidate rectangle
188 // instead of just the center
189 CandidatePolygonRingDistanceCalculator ringDistanceCalculator( candidate );
190
191 // first, check max distance to outside of polygon
192 // TODO 2: there's a bug here -- if a candidate's center falls outside the polygon, then a larger
193 // distance to the polygon boundary is being considered as the best placement. That's clearly wrong --
194 // if any part of label candidate sits outside the polygon, we should first prefer candidates which sit
195 // entirely WITHIN the polygon, or failing that, candidates which are CLOSER to the polygon boundary, not further from it!
196 ringDistanceCalculator.addRing( candidate->feature );
197
198 // prefer candidates further from the outside of map rather then those close to the outside of the map
199 // TODO 3: should be using the actual map boundary here, not the bounding box
200 const PointSet extent( 4, bbx, bby );
201 ringDistanceCalculator.addRing( &extent );
202
203 // prefer candidates which further from interior rings (holes) of the polygon
204 for ( int i = 0; i < candidate->feature->getNumSelfObstacles(); ++i )
205 {
206 ringDistanceCalculator.addRing( candidate->feature->getSelfObstacle( i ) );
207 }
208
209 return ringDistanceCalculator.minimumDistance();
210}
211
212void CostCalculator::finalizeCandidatesCosts( Feats *feat, double bbx[4], double bby[4] )
213{
214 // sort candidates list, best label to worst
215 std::sort( feat->candidates.begin(), feat->candidates.end(), candidateSortGrow );
216
217 // Original nonsense comment from pal library:
218 // "try to exclude all conflitual labels (good ones have cost < 1 by pruning)"
219 // my interpretation: it appears this scans through the candidates and chooses some threshold
220 // based on the candidate cost, after which all remaining candidates are simply discarded
221 // my suspicion: I don't think this is needed (or wanted) here, and is reflective of the fact
222 // that obstacle costs are too low vs inferior candidate placement costs (i.e. without this,
223 // an "around point" label would rather be placed over an obstacle then be placed in a position > 6 o'clock
224 double discrim = 0.0;
225 std::size_t stop = 0;
226 do
227 {
228 discrim += 1.0;
229 for ( stop = 0; stop < feat->candidates.size() && feat->candidates[ stop ]->cost() < discrim; stop++ )
230 ;
231 }
232 while ( stop == 0 && discrim < feat->candidates.back()->cost() + 2.0 );
233
234 // THIS LOOKS SUSPICIOUS -- it clamps all costs to a fixed value??
235 if ( discrim > 1.5 )
236 {
237 for ( std::size_t k = 0; k < stop; k++ )
238 feat->candidates[ k ]->setCost( 0.0021 );
239 }
240
241 if ( feat->candidates.size() > stop )
242 {
243 feat->candidates.resize( stop );
244 }
245
246 // Sets costs for candidates of polygon
247
248 if ( feat->feature->getGeosType() == GEOS_POLYGON )
249 {
250 const Qgis::LabelPlacement arrangement = feat->feature->layer()->arrangement();
251 if ( arrangement == Qgis::LabelPlacement::Free || arrangement == Qgis::LabelPlacement::Horizontal )
252 {
253 // prefer positions closer to the pole of inaccessibilities
255 // ...of these, prefer positions closer to the overall polygon centroid
257 }
258 }
259
260 // add size penalty (small lines/polygons get higher cost)
261 feat->feature->addSizePenalty( feat->candidates, bbx, bby );
262}
263
265 : mPx( ( candidate->x[0] + candidate->x[2] ) / 2.0 )
266 , mPy( ( candidate->y[0] + candidate->y[2] ) / 2.0 )
267{
268}
269
271{
272 const double d = ring->minDistanceToPoint( mPx, mPy );
273 if ( d < mMinDistance )
274 {
275 mMinDistance = d;
276 }
277}
278
280{
281 return mMinDistance;
282}
LabelPlacement
Placement modes which determine how label candidates are generated for a feature.
Definition qgis.h:1194
@ Free
Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the pol...
Definition qgis.h:1200
@ Horizontal
Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only...
Definition qgis.h:1199
@ Version2
Version 2 (default for new projects since QGIS 3.12).
Definition qgis.h:2874
@ Version1
Version 1, matches placement from QGIS <= 3.10.1.
Definition qgis.h:2873
static double distance2D(double x1, double y1, double x2, double y2)
Returns the 2D distance between (x1, y1) and (x2, y2).
double factor() const
Returns the obstacle factor, where 1.0 = default, < 1.0 more likely to be covered by labels,...
@ PolygonInterior
Avoid placing labels over interior of polygon (prefer placing labels totally outside or just slightly...
@ PolygonWhole
Avoid placing labels over ANY part of polygon. Where PolygonInterior will prefer to place labels with...
@ PolygonBoundary
Avoid placing labels over boundary of polygon (prefer placing outside or completely inside polygon).
Calculates distance from a label candidate to the nearest polygon ring.
void addRing(const pal::PointSet *ring)
Adds a ring to the calculation, updating the minimumDistance() value if the rings is closer to the ca...
double minimumDistance() const
Returns the minimum distance between the candidate and all added rings.
CandidatePolygonRingDistanceCalculator(LabelPosition *candidate)
Constructor for PolygonRingDistanceCalculator, for the specified label candidate.
static double calculatePolygonRingDistance(LabelPosition *candidate, double bbx[4], double bby[4])
Calculates the distance between a label candidate and the closest ring for a polygon feature.
static void addObstacleCostPenalty(pal::LabelPosition *lp, pal::FeaturePart *obstacle, Pal *pal)
Increase candidate's cost according to its collision with passed feature.
static void finalizeCandidatesCosts(Feats *feat, double bbx[4], double bby[4])
Sort candidates by costs, skip the worse ones, evaluate polygon candidates.
static bool candidateSortGrow(const std::unique_ptr< pal::LabelPosition > &c1, const std::unique_ptr< pal::LabelPosition > &c2)
Sorts label candidates in ascending order of cost.
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...
static void calculateCandidatePolygonCentroidDistanceCosts(pal::FeaturePart *feature, std::vector< std::unique_ptr< pal::LabelPosition > > &lPos)
Updates the costs for polygon label candidates by considering the distance between the candidates and...
For usage in problem solving algorithm.
Definition util.h:58
std::vector< std::unique_ptr< LabelPosition > > candidates
Definition util.h:66
FeaturePart * feature
Definition util.h:63
Represents a part of a label feature.
Definition feature.h:60
double getLabelDistance() const
Returns the distance from the anchor point to the label.
Definition feature.h:274
int getNumSelfObstacles() const
Gets number of holes (inner rings) - they are considered as obstacles.
Definition feature.h:300
const QgsLabelObstacleSettings & obstacleSettings() const
Returns the feature's obstacle settings.
Definition feature.h:294
Layer * layer()
Returns the layer that feature belongs to.
Definition feature.cpp:162
FeaturePart * getSelfObstacle(int i)
Gets hole (inner ring) - considered as obstacle.
Definition feature.h:302
void addSizePenalty(std::vector< std::unique_ptr< LabelPosition > > &lPos, double bbx[4], double bby[4]) const
Increases the cost of the label candidates for this feature, based on the size of the feature.
Definition feature.cpp:2347
double calculatePriority() const
Calculates the priority for the feature.
Definition feature.cpp:2481
LabelPosition is a candidate feature label position.
bool intersectsWithPolygon(PointSet *polygon) const
Returns true if any intersection between polygon and position exists.
void setCost(double newCost)
Sets the candidate label position's geographical cost.
bool crossesLine(PointSet *line) const
Returns true if this label crosses the specified line.
FeaturePart * feature
double cost() const
Returns the candidate label position's geographical cost.
void setConflictsWithObstacle(bool conflicts)
Sets whether the position is marked as conflicting with an obstacle feature.
void setHasHardObstacleConflict(bool conflicts)
Sets whether the position is marked as having a hard conflict with an obstacle feature.
bool crossesBoundary(PointSet *polygon) const
Returns true if this label crosses the boundary of the specified polygon.
double getDistanceToPoint(double xp, double yp, bool useOuterBounds) const
Gets distance from this label to a point.
int polygonIntersectionCost(PointSet *polygon) const
Returns cost of position intersection with polygon (testing area of intersection and center).
Qgis::LabelPlacement arrangement() const
Returns the layer's arrangement policy.
Definition layer.h:169
QgsLabelObstacleSettings::ObstacleType obstacleType() const
Returns the obstacle type, which controls how features within the layer act as obstacles for labels.
Definition layer.h:221
Main Pal labeling class.
Definition pal.h:84
The underlying raw pal geometry class.
Definition pointset.h:78
std::vector< double > y
Definition pointset.h:232
void getCentroid(double &px, double &py, bool forceInside=false) const
Definition pointset.cpp:920
std::vector< double > x
Definition pointset.h:231
int getGeosType() const
Definition pointset.h:159
double minDistanceToPoint(double px, double py, double *rx=nullptr, double *ry=nullptr) const
Returns the squared minimum distance between the point set geometry and the point (px,...
Definition pointset.cpp:863
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6607
#define EPSILON
Definition util.h:82