Skip to content

Commit c234d80

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
1 parent b4fe900 commit c234d80

13 files changed

+202
-107
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;
@@ -1058,14 +1061,36 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
10581061

10591062
// average angle is calculated with respect to periodicity of angles
10601063
double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
1061-
// displacement
1062-
if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
1063-
positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 ) );
1064-
if ( flags & FLAG_ON_LINE )
1065-
positions.append( _createCurvedCandidate( slp, angle_avg, 0 ) );
1066-
if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
1067-
positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() ) );
1064+
// displacement - we loop through 3 times, generating above, online then below line placements successively
1065+
for ( int i = 0; i <= 2; ++i )
1066+
{
1067+
LabelPosition* p = nullptr;
1068+
if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
1069+
p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
1070+
if ( i == 1 && flags & FLAG_ON_LINE )
1071+
p = _createCurvedCandidate( slp, angle_avg, 0 );
1072+
if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
1073+
p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );
1074+
1075+
if ( p && mLF->permissibleZonePrepared() )
1076+
{
1077+
bool within = true;
1078+
LabelPosition* currentPos = p;
1079+
while ( within && currentPos )
1080+
{
1081+
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
1082+
currentPos = currentPos->getNextPart();
1083+
}
1084+
if ( !within )
1085+
{
1086+
delete p;
1087+
p = nullptr;
1088+
}
1089+
}
10681090

1091+
if ( p )
1092+
positions.append( p );
1093+
}
10691094
// delete original candidate
10701095
delete slp;
10711096
}
@@ -1155,7 +1180,7 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
11551180

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

11601185
do
11611186
{
@@ -1169,10 +1194,11 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
11691194
continue;
11701195
}
11711196

1172-
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal && mLF->layer()->fitInPolygonOnly() )
1197+
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Horizontal && mLF->permissibleZonePrepared() )
11731198
{
11741199
//check width/height of bbox is sufficient for label
1175-
if ( box->length < labelWidth || box->width < labelHeight )
1200+
if ( mLF->permissibleZone().boundingBox().width() < labelWidth ||
1201+
mLF->permissibleZone().boundingBox().height() < labelHeight )
11761202
{
11771203
//no way label can fit in this box, skip it
11781204
continue;
@@ -1260,8 +1286,8 @@ int FeaturePart::createCandidatesForPolygon( QList< LabelPosition*>& lPos, Point
12601286
rx += box->x[0];
12611287
ry += box->y[0];
12621288

1263-
bool candidateAcceptable = ( mLF->layer()->fitInPolygonOnly()
1264-
? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1289+
bool candidateAcceptable = ( mLF->permissibleZonePrepared()
1290+
? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
12651291
: mapShape->containsPoint( rx, ry ) );
12661292
if ( candidateAcceptable )
12671293
{
@@ -1350,9 +1376,9 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
13501376
double cx, cy;
13511377
mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
13521378
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::OverPoint )
1353-
createCandidatesOverPoint( cx, cy, lPos, angle, mapShape );
1379+
createCandidatesOverPoint( cx, cy, lPos, angle );
13541380
else
1355-
createCandidatesAroundPoint( cx, cy, lPos, angle, mapShape );
1381+
createCandidatesAroundPoint( cx, cy, lPos, angle );
13561382
break;
13571383
case QgsPalLayerSettings::Line:
13581384
createCandidatesAlongLine( lPos, mapShape );

src/core/pal/feature.h

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

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

148146
/** Generates candidates following a prioritised list of predefined positions around a point.
149147
* @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 "math.h"
34+
#include "qgsgeos.h"
3435

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

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

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
@@ -214,21 +214,6 @@ namespace pal
214214
*/
215215
bool centroidInside() const { return mCentroidInside; }
216216

217-
/** Sets whether labels which do not fit completely within a polygon feature
218-
* are discarded.
219-
* @param fitInPolygon set to true to discard labels which do not fit within
220-
* polygon features. Set to false to allow labels which partially fall outside
221-
* the polygon.
222-
* @see fitInPolygonOnly
223-
*/
224-
void setFitInPolygonOnly( bool fitInPolygon ) { mFitInPolygon = fitInPolygon; }
225-
226-
/** Returns whether labels which do not fit completely within a polygon feature
227-
* are discarded.
228-
* @see setFitInPolygonOnly
229-
*/
230-
bool fitInPolygonOnly() const { return mFitInPolygon; }
231-
232217
/** Register a feature in the layer.
233218
*
234219
* Does not take ownership of the label feature (it is owned by its provider).
@@ -270,7 +255,6 @@ namespace pal
270255
bool mLabelLayer;
271256
bool mDisplayAll;
272257
bool mCentroidInside;
273-
bool mFitInPolygon;
274258

275259
/** Optional flags used for some placement methods */
276260
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)