Skip to content

Commit 9e965f4

Browse files
committed
Fix "label only inside polygon" mode when used with perimeter placement
The option was not working with perimeter placements as perimeter placements alter the label feature geometry to be a boundary linestring - hence no labels where ever inside this boundary. Accordingly this refactors how the force label inside polygon option functions. Now QgsLabelFeatures can have a permissible zone geometry set, such that any label candidates outside of this permissible zone are discarded. This approach is more flexible as it could also be used for more labeling options in future, eg discarding label candidates which are too far from a centroid or something. Sponsored by Andreas Neumann (cherry-picked from c234d80)
1 parent 5ac4d0e commit 9e965f4

13 files changed

+194
-97
lines changed

src/core/pal/feature.cpp

+53-27
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
227227
}
228228
}
229229

230-
int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosition*>& lPos, double angle, PointSet *mapShape )
230+
int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosition*>& lPos, double angle )
231231
{
232232
int nbp = 1;
233233

@@ -294,9 +294,9 @@ int FeaturePart::createCandidatesOverPoint( double x, double y, QList< LabelPosi
294294
double lx = x + xdiff;
295295
double ly = y + ydiff;
296296

297-
if ( mapShape && type == GEOS_POLYGON && mLF->layer()->fitInPolygonOnly() )
297+
if ( mLF->permissibleZonePrepared() )
298298
{
299-
if ( !mapShape->containsLabelCandidate( lx, ly, labelW, labelH, angle ) )
299+
if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), lx, ly, labelW, labelH, angle ) )
300300
{
301301
return 0;
302302
}
@@ -419,18 +419,20 @@ int FeaturePart::createCandidatesAtOrderedPositionsOverPoint( double x, double y
419419
double labelX = referenceX + deltaX;
420420
double labelY = referenceY + deltaY;
421421

422-
lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
423-
424-
//TODO - tweak
425-
cost += 0.001;
422+
if ( ! mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
423+
{
424+
lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
425+
//TODO - tweak
426+
cost += 0.001;
427+
}
426428

427429
++i;
428430
}
429431

430432
return lPos.count();
431433
}
432434

433-
int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPosition* >& lPos, double angle, PointSet *mapShape )
435+
int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPosition* >& lPos, double angle )
434436
{
435437
double labelWidth = getLabelWidth();
436438
double labelHeight = getLabelHeight();
@@ -547,9 +549,9 @@ int FeaturePart::createCandidatesAroundPoint( double x, double y, QList< LabelPo
547549
cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
548550

549551

550-
if ( mapShape && type == GEOS_POLYGON && mLF->layer()->fitInPolygonOnly() )
552+
if ( mLF->permissibleZonePrepared() )
551553
{
552-
if ( !mapShape->containsLabelCandidate( labelX, labelY, labelWidth, labelHeight, angle ) )
554+
if ( !GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), labelX, labelY, labelWidth, labelHeight, angle ) )
553555
{
554556
continue;
555557
}
@@ -698,17 +700,17 @@ int FeaturePart::createCandidatesAlongLine( QList< LabelPosition* >& lPos, Point
698700

699701
if ( aboveLine )
700702
{
701-
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
703+
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
702704
positions.append( new LabelPosition( i, bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
703705
}
704706
if ( belowLine )
705707
{
706-
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
708+
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
707709
positions.append( new LabelPosition( i, bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
708710
}
709711
if ( flags & FLAG_ON_LINE )
710712
{
711-
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
713+
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
712714
positions.append( new LabelPosition( i, bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
713715
}
714716
}
@@ -879,6 +881,7 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
879881
delete slp;
880882
return nullptr;
881883
}
884+
882885
// Shift the character downwards since the draw position is specified at the baseline
883886
// and we're calculating the mean line here
884887
double dist = 0.9 * li->label_height / 2;
@@ -1047,14 +1050,36 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
10471050

10481051
// average angle is calculated with respect to periodicity of angles
10491052
double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1050-
// displacement
1051-
if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
1052-
positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 ) );
1053-
if ( flags & FLAG_ON_LINE )
1054-
positions.append( _createCurvedCandidate( slp, angle_avg, 0 ) );
1055-
if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
1056-
positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() ) );
1053+
// displacement - we loop through 3 times, generating above, online then below line placements successively
1054+
for ( int i = 0; i <= 2; ++i )
1055+
{
1056+
LabelPosition* p = nullptr;
1057+
if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1058+
p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1059+
if ( i == 1 && flags & FLAG_ON_LINE )
1060+
p = _createCurvedCandidate( slp, angle_avg, 0 );
1061+
if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1062+
p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1063+
1064+
if ( p && mLF->permissibleZonePrepared() )
1065+
{
1066+
bool within = true;
1067+
LabelPosition* currentPos = p;
1068+
while ( within && currentPos )
1069+
{
1070+
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1071+
currentPos = currentPos->getNextPart();
1072+
}
1073+
if ( !within )
1074+
{
1075+
delete p;
1076+
p = nullptr;
1077+
}
1078+
}
10571079

1080+
if ( p )
1081+
positions.append( p );
1082+
}
10581083
// delete original candidate
10591084
delete slp;
10601085
}
@@ -1144,7 +1169,7 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
11441169

11451170
//fit in polygon only mode slows down calculation a lot, so if it's enabled
11461171
//then use a smaller limit for number of iterations
1147-
int maxTry = mLF->layer()->fitInPolygonOnly() ? 7 : 10;
1172+
int maxTry = mLF->permissibleZonePrepared() ? 7 : 10;
11481173

11491174
do
11501175
{
@@ -1158,10 +1183,11 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
11581183
continue;
11591184
}
11601185

1161-
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal && mLF->layer()->fitInPolygonOnly() )
1186+
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal && mLF->permissibleZonePrepared() )
11621187
{
11631188
//check width/height of bbox is sufficient for label
1164-
if ( box->length < labelWidth || box->width < labelHeight )
1189+
if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1190+
mLF->permissibleZone().boundingBox().height() < labelHeight )
11651191
{
11661192
//no way label can fit in this box, skip it
11671193
continue;
@@ -1249,8 +1275,8 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
12491275
rx += box->x[0];
12501276
ry += box->y[0];
12511277

1252-
bool candidateAcceptable = ( mLF->layer()->fitInPolygonOnly()
1253-
? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1278+
bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1279+
? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
12541280
: mapShape->containsPoint( rx, ry ) );
12551281
if ( candidateAcceptable )
12561282
{
@@ -1337,9 +1363,9 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
13371363
double cx, cy;
13381364
mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
13391365
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::OverPoint )
1340-
createCandidatesOverPoint( cx, cy, lPos, angle, mapShape );
1366+
createCandidatesOverPoint( cx, cy, lPos, angle );
13411367
else
1342-
createCandidatesAroundPoint( cx, cy, lPos, angle, mapShape );
1368+
createCandidatesAroundPoint( cx, cy, lPos, angle );
13431369
break;
13441370
case QgsPalLayerSettings::Line:
13451371
createCandidatesAlongLine( lPos, mapShape );

src/core/pal/feature.h

+2-4
Original file line numberDiff line numberDiff line change
@@ -131,20 +131,18 @@ namespace pal
131131
* @param y y coordinate of the point
132132
* @param lPos pointer to an array of candidates, will be filled by generated candidates
133133
* @param angle orientation of the label
134-
* @param mapShape optional geometry of source polygon
135134
* @returns the number of generated candidates
136135
*/
137-
int createCandidatesAroundPoint( double x, double y, QList<LabelPosition *> &lPos, double angle, PointSet *mapShape = nullptr );
136+
int createCandidatesAroundPoint( double x, double y, QList<LabelPosition *> &lPos, double angle );
138137

139138
/** Generate one candidate over or offset the specified point.
140139
* @param x x coordinate of the point
141140
* @param y y coordinate of the point
142141
* @param lPos pointer to an array of candidates, will be filled by generated candidate
143142
* @param angle orientation of the label
144-
* @param mapShape optional geometry of source polygon
145143
* @returns the number of generated candidates (always 1)
146144
*/
147-
int createCandidatesOverPoint( double x, double y, QList<LabelPosition *> &lPos, double angle, PointSet *mapShape = nullptr );
145+
int createCandidatesOverPoint( double x, double y, QList<LabelPosition *> &lPos, double angle );
148146

149147
/** Generates candidates following a prioritised list of predefined positions around a point.
150148
* @param x x coordinate of the point

src/core/pal/geomfunction.cpp

+53
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "feature.h"
3232
#include "util.h"
3333
#include "qgis.h"
34+
#include "pal.h"
35+
#include "qgsmessagelog.h"
3436

3537
using namespace pal;
3638

@@ -315,6 +317,57 @@ int GeomFunction::reorderPolygon( int nbPoints, double *x, double *y )
315317
return 0;
316318
}
317319

320+
bool GeomFunction::containsCandidate( const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha )
321+
{
322+
if ( !geom )
323+
return false;
324+
325+
GEOSContextHandle_t geosctxt = geosContext();
326+
GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
327+
328+
GEOSCoordSeq_setX_r( geosctxt, coord, 0, x );
329+
GEOSCoordSeq_setY_r( geosctxt, coord, 0, y );
330+
if ( !qgsDoubleNear( alpha, 0.0 ) )
331+
{
332+
double beta = alpha + ( M_PI / 2 );
333+
double dx1 = cos( alpha ) * width;
334+
double dy1 = sin( alpha ) * width;
335+
double dx2 = cos( beta ) * height;
336+
double dy2 = sin( beta ) * height;
337+
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + dx1 );
338+
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y + dy1 );
339+
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + dx1 + dx2 );
340+
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + dy1 + dy2 );
341+
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x + dx2 );
342+
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + dy2 );
343+
}
344+
else
345+
{
346+
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + width );
347+
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y );
348+
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + width );
349+
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + height );
350+
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x );
351+
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + height );
352+
}
353+
//close ring
354+
GEOSCoordSeq_setX_r( geosctxt, coord, 4, x );
355+
GEOSCoordSeq_setY_r( geosctxt, coord, 4, y );
356+
357+
try
358+
{
359+
GEOSGeometry* bboxGeos = GEOSGeom_createLinearRing_r( geosctxt, coord );
360+
bool result = ( GEOSPreparedContainsProperly_r( geosctxt, geom, bboxGeos ) == 1 );
361+
GEOSGeom_destroy_r( geosctxt, bboxGeos );
362+
return result;
363+
}
364+
catch ( GEOSException &e )
365+
{
366+
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
367+
return false;
368+
}
369+
}
370+
318371
void GeomFunction::findLineCircleIntersection( double cx, double cy, double radius,
319372
double x1, double y1, double x2, double y2,
320373
double& xRes, double& yRes )

src/core/pal/geomfunction.h

+12
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#define PAL_GEOM_FUNCTION
3232

3333
#include "util.h"
34+
#include "qgsgeos.h"
3435

3536
namespace pal
3637
{
@@ -98,6 +99,17 @@ namespace pal
9899
//! Reorder points to have cross prod ((x,y)[i], (x,y)[i+1), point) > 0 when point is outside
99100
static int reorderPolygon( int nbPoints, double *x, double *y );
100101

102+
/** Returns true if a GEOS prepared geometry totally contains a label candidate.
103+
* @param geom GEOS prepared geometry
104+
* @param x candidate x
105+
* @param y candidate y
106+
* @param width candidate width
107+
* @param height candidate height
108+
* @param alpha candidate angle
109+
* @returns true if candidate is totally contained
110+
*/
111+
static bool containsCandidate( const GEOSPreparedGeometry* geom, double x, double y, double width, double height, double alpha );
112+
101113
};
102114
} //namespace
103115

src/core/pal/layer.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ Layer::Layer( QgsAbstractLabelProvider* provider, const QString& name, QgsPalLay
4949
, mLabelLayer( toLabel )
5050
, mDisplayAll( displayAll )
5151
, mCentroidInside( false )
52-
, mFitInPolygon( false )
5352
, mArrangement( arrangement )
5453
, mArrangementFlags( nullptr )
5554
, mMode( LabelPerFeature )

src/core/pal/layer.h

-16
Original file line numberDiff line numberDiff line change
@@ -210,21 +210,6 @@ namespace pal
210210
*/
211211
bool centroidInside() const { return mCentroidInside; }
212212

213-
/** Sets whether labels which do not fit completely within a polygon feature
214-
* are discarded.
215-
* @param fitInPolygon set to true to discard labels which do not fit within
216-
* polygon features. Set to false to allow labels which partially fall outside
217-
* the polygon.
218-
* @see fitInPolygonOnly
219-
*/
220-
void setFitInPolygonOnly( bool fitInPolygon ) { mFitInPolygon = fitInPolygon; }
221-
222-
/** Returns whether labels which do not fit completely within a polygon feature
223-
* are discarded.
224-
* @see setFitInPolygonOnly
225-
*/
226-
bool fitInPolygonOnly() const { return mFitInPolygon; }
227-
228213
/** Register a feature in the layer.
229214
*
230215
* Does not take ownership of the label feature (it is owned by its provider).
@@ -266,7 +251,6 @@ namespace pal
266251
bool mLabelLayer;
267252
bool mDisplayAll;
268253
bool mCentroidInside;
269-
bool mFitInPolygon;
270254

271255
/** Optional flags used for some placement methods */
272256
QgsPalLayerSettings::Placement mArrangement;

src/core/pal/pointset.cpp

+1-44
Original file line numberDiff line numberDiff line change
@@ -289,50 +289,7 @@ bool PointSet::containsPoint( double x, double y ) const
289289

290290
bool PointSet::containsLabelCandidate( double x, double y, double width, double height, double alpha ) const
291291
{
292-
GEOSContextHandle_t geosctxt = geosContext();
293-
GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );
294-
295-
GEOSCoordSeq_setX_r( geosctxt, coord, 0, x );
296-
GEOSCoordSeq_setY_r( geosctxt, coord, 0, y );
297-
if ( !qgsDoubleNear( alpha, 0.0 ) )
298-
{
299-
double beta = alpha + ( M_PI / 2 );
300-
double dx1 = cos( alpha ) * width;
301-
double dy1 = sin( alpha ) * width;
302-
double dx2 = cos( beta ) * height;
303-
double dy2 = sin( beta ) * height;
304-
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + dx1 );
305-
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y + dy1 );
306-
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + dx1 + dx2 );
307-
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + dy1 + dy2 );
308-
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x + dx2 );
309-
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + dy2 );
310-
}
311-
else
312-
{
313-
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + width );
314-
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y );
315-
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + width );
316-
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + height );
317-
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x );
318-
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + height );
319-
}
320-
//close ring
321-
GEOSCoordSeq_setX_r( geosctxt, coord, 4, x );
322-
GEOSCoordSeq_setY_r( geosctxt, coord, 4, y );
323-
324-
try
325-
{
326-
GEOSGeometry* bboxGeos = GEOSGeom_createLinearRing_r( geosctxt, coord );
327-
bool result = ( GEOSPreparedContainsProperly_r( geosctxt, preparedGeom(), bboxGeos ) == 1 );
328-
GEOSGeom_destroy_r( geosctxt, bboxGeos );
329-
return result;
330-
}
331-
catch ( GEOSException &e )
332-
{
333-
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
334-
return false;
335-
}
292+
return GeomFunction::containsCandidate( preparedGeom(), x, y, width, height, alpha );
336293
}
337294

338295
void PointSet::splitPolygons( QLinkedList<PointSet*> &shapes_toProcess,

0 commit comments

Comments
 (0)