Skip to content
Permalink
Browse files

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)
  • Loading branch information
nyalldawson committed Aug 9, 2016
1 parent fe6401e commit 785f244b9fb7c0ead9b21fd1636b9e235a718712
@@ -227,7 +227,7 @@ LabelPosition::Quadrant FeaturePart::quadrantFromOffset() const
}
}

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

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

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

lPos << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );

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

++i;
}

return lPos.count();
}

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


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

if ( aboveLine )
{
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha ) )
positions.append( new LabelPosition( i, bx + cos( beta ) *distlabel, by + sin( beta ) *distlabel, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
}
if ( belowLine )
{
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha ) )
positions.append( new LabelPosition( i, bx - cos( beta ) *( distlabel + yrm ), by - sin( beta ) *( distlabel + yrm ), xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
}
if ( flags & FLAG_ON_LINE )
{
if ( !mLF->layer()->fitInPolygonOnly() || mapShape->containsLabelCandidate( bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
if ( !mLF->permissibleZonePrepared() || GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha ) )
positions.append( new LabelPosition( i, bx - yrm*cos( beta ) / 2, by - yrm*sin( beta ) / 2, xrm, yrm, alpha, cost, this, isRightToLeft ) ); // Line
}
}
@@ -879,6 +881,7 @@ LabelPosition* FeaturePart::curvedPlacementAtOffset( PointSet* path_positions, d
delete slp;
return nullptr;
}

// Shift the character downwards since the draw position is specified at the baseline
// and we're calculating the mean line here
double dist = 0.9 * li->label_height / 2;
@@ -1058,14 +1061,36 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,

// average angle is calculated with respect to periodicity of angles
double angle_avg = atan2( sin_avg / li->char_num, cos_avg / li->char_num );
// displacement
if (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) )
positions.append( _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 ) );
if ( flags & FLAG_ON_LINE )
positions.append( _createCurvedCandidate( slp, angle_avg, 0 ) );
if (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) )
positions.append( _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() ) );
// displacement - we loop through 3 times, generating above, online then below line placements successively
for ( int i = 0; i <= 2; ++i )
{
LabelPosition* p = nullptr;
if ( i == 0 && (( !reversed && ( flags & FLAG_ABOVE_LINE ) ) || ( reversed && ( flags & FLAG_BELOW_LINE ) ) ) )
p = _createCurvedCandidate( slp, angle_avg, mLF->distLabel() + li->label_height / 2 );
if ( i == 1 && flags & FLAG_ON_LINE )
p = _createCurvedCandidate( slp, angle_avg, 0 );
if ( i == 2 && (( !reversed && ( flags & FLAG_BELOW_LINE ) ) || ( reversed && ( flags & FLAG_ABOVE_LINE ) ) ) )
p = _createCurvedCandidate( slp, angle_avg, -li->label_height / 2 - mLF->distLabel() );

if ( p && mLF->permissibleZonePrepared() )
{
bool within = true;
LabelPosition* currentPos = p;
while ( within && currentPos )
{
within = GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), currentPos->getX(), currentPos->getY(), currentPos->getWidth(), currentPos->getHeight(), currentPos->getAlpha() );
currentPos = currentPos->getNextPart();
}
if ( !within )
{
delete p;
p = nullptr;
}
}

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

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

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

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

bool candidateAcceptable = ( mLF->layer()->fitInPolygonOnly()
? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
bool candidateAcceptable = ( mLF->permissibleZonePrepared()
? GeomFunction::containsCandidate( mLF->permissibleZonePrepared(), rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
: mapShape->containsPoint( rx, ry ) );
if ( candidateAcceptable )
{
@@ -1350,9 +1376,9 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
double cx, cy;
mapShape->getCentroid( cx, cy, mLF->layer()->centroidInside() );
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::OverPoint )
createCandidatesOverPoint( cx, cy, lPos, angle, mapShape );
createCandidatesOverPoint( cx, cy, lPos, angle );
else
createCandidatesAroundPoint( cx, cy, lPos, angle, mapShape );
createCandidatesAroundPoint( cx, cy, lPos, angle );
break;
case QgsPalLayerSettings::Line:
createCandidatesAlongLine( lPos, mapShape );
@@ -132,20 +132,18 @@ namespace pal
* @param y y coordinate of the point
* @param lPos pointer to an array of candidates, will be filled by generated candidates
* @param angle orientation of the label
* @param mapShape optional geometry of source polygon
* @returns the number of generated candidates
*/
int createCandidatesAroundPoint( double x, double y, QList<LabelPosition *> &lPos, double angle, PointSet *mapShape = nullptr );
int createCandidatesAroundPoint( double x, double y, QList<LabelPosition *> &lPos, double angle );

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

/** Generates candidates following a prioritised list of predefined positions around a point.
* @param x x coordinate of the point
@@ -31,6 +31,8 @@
#include "feature.h"
#include "util.h"
#include "qgis.h"
#include "pal.h"
#include "qgsmessagelog.h"

using namespace pal;

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

bool GeomFunction::containsCandidate( const GEOSPreparedGeometry *geom, double x, double y, double width, double height, double alpha )
{
if ( !geom )
return false;

GEOSContextHandle_t geosctxt = geosContext();
GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );

GEOSCoordSeq_setX_r( geosctxt, coord, 0, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 0, y );
if ( !qgsDoubleNear( alpha, 0.0 ) )
{
double beta = alpha + ( M_PI / 2 );
double dx1 = cos( alpha ) * width;
double dy1 = sin( alpha ) * width;
double dx2 = cos( beta ) * height;
double dy2 = sin( beta ) * height;
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + dx1 );
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y + dy1 );
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + dx1 + dx2 );
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + dy1 + dy2 );
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x + dx2 );
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + dy2 );
}
else
{
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + width );
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y );
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + width );
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + height );
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + height );
}
//close ring
GEOSCoordSeq_setX_r( geosctxt, coord, 4, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 4, y );

try
{
GEOSGeometry* bboxGeos = GEOSGeom_createLinearRing_r( geosctxt, coord );
bool result = ( GEOSPreparedContainsProperly_r( geosctxt, geom, bboxGeos ) == 1 );
GEOSGeom_destroy_r( geosctxt, bboxGeos );
return result;
}
catch ( GEOSException &e )
{
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
return false;
}
}

void GeomFunction::findLineCircleIntersection( double cx, double cy, double radius,
double x1, double y1, double x2, double y2,
double& xRes, double& yRes )
@@ -31,6 +31,7 @@
#define PAL_GEOM_FUNCTION

#include "util.h"
#include "qgsgeos.h"

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

/** Returns true if a GEOS prepared geometry totally contains a label candidate.
* @param geom GEOS prepared geometry
* @param x candidate x
* @param y candidate y
* @param width candidate width
* @param height candidate height
* @param alpha candidate angle
* @returns true if candidate is totally contained
*/
static bool containsCandidate( const GEOSPreparedGeometry* geom, double x, double y, double width, double height, double alpha );

};
} //namespace

@@ -49,7 +49,6 @@ Layer::Layer( QgsAbstractLabelProvider* provider, const QString& name, QgsPalLay
, mLabelLayer( toLabel )
, mDisplayAll( displayAll )
, mCentroidInside( false )
, mFitInPolygon( false )
, mArrangement( arrangement )
, mArrangementFlags( nullptr )
, mMode( LabelPerFeature )
@@ -213,21 +213,6 @@ namespace pal
*/
bool centroidInside() const { return mCentroidInside; }

/** Sets whether labels which do not fit completely within a polygon feature
* are discarded.
* @param fitInPolygon set to true to discard labels which do not fit within
* polygon features. Set to false to allow labels which partially fall outside
* the polygon.
* @see fitInPolygonOnly
*/
void setFitInPolygonOnly( bool fitInPolygon ) { mFitInPolygon = fitInPolygon; }

/** Returns whether labels which do not fit completely within a polygon feature
* are discarded.
* @see setFitInPolygonOnly
*/
bool fitInPolygonOnly() const { return mFitInPolygon; }

/** Register a feature in the layer.
*
* Does not take ownership of the label feature (it is owned by its provider).
@@ -269,7 +254,6 @@ namespace pal
bool mLabelLayer;
bool mDisplayAll;
bool mCentroidInside;
bool mFitInPolygon;

/** Optional flags used for some placement methods */
QgsPalLayerSettings::Placement mArrangement;
@@ -289,50 +289,7 @@ bool PointSet::containsPoint( double x, double y ) const

bool PointSet::containsLabelCandidate( double x, double y, double width, double height, double alpha ) const
{
GEOSContextHandle_t geosctxt = geosContext();
GEOSCoordSequence *coord = GEOSCoordSeq_create_r( geosctxt, 5, 2 );

GEOSCoordSeq_setX_r( geosctxt, coord, 0, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 0, y );
if ( !qgsDoubleNear( alpha, 0.0 ) )
{
double beta = alpha + ( M_PI / 2 );
double dx1 = cos( alpha ) * width;
double dy1 = sin( alpha ) * width;
double dx2 = cos( beta ) * height;
double dy2 = sin( beta ) * height;
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + dx1 );
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y + dy1 );
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + dx1 + dx2 );
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + dy1 + dy2 );
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x + dx2 );
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + dy2 );
}
else
{
GEOSCoordSeq_setX_r( geosctxt, coord, 1, x + width );
GEOSCoordSeq_setY_r( geosctxt, coord, 1, y );
GEOSCoordSeq_setX_r( geosctxt, coord, 2, x + width );
GEOSCoordSeq_setY_r( geosctxt, coord, 2, y + height );
GEOSCoordSeq_setX_r( geosctxt, coord, 3, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 3, y + height );
}
//close ring
GEOSCoordSeq_setX_r( geosctxt, coord, 4, x );
GEOSCoordSeq_setY_r( geosctxt, coord, 4, y );

try
{
GEOSGeometry* bboxGeos = GEOSGeom_createLinearRing_r( geosctxt, coord );
bool result = ( GEOSPreparedContainsProperly_r( geosctxt, preparedGeom(), bboxGeos ) == 1 );
GEOSGeom_destroy_r( geosctxt, bboxGeos );
return result;
}
catch ( GEOSException &e )
{
QgsMessageLog::logMessage( QObject::tr( "Exception: %1" ).arg( e.what() ), QObject::tr( "GEOS" ) );
return false;
}
return GeomFunction::containsCandidate( preparedGeom(), x, y, width, height, alpha );
}

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

0 comments on commit 785f244

Please sign in to comment.
You can’t perform that action at this time.