Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 10, 2016
1 parent 183c53b commit c442a06
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 97 deletions.
80 changes: 53 additions & 27 deletions src/core/pal/feature.cpp
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1047,14 +1050,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;
}
Expand Down Expand Up @@ -1144,7 +1169,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
{
Expand All @@ -1158,10 +1183,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;
Expand Down Expand Up @@ -1249,8 +1275,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 )
{
Expand Down Expand Up @@ -1337,9 +1363,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 );
Expand Down
6 changes: 2 additions & 4 deletions src/core/pal/feature.h
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions src/core/pal/geomfunction.cpp
Expand Up @@ -31,6 +31,8 @@
#include "feature.h"
#include "util.h"
#include "qgis.h"
#include "pal.h"
#include "qgsmessagelog.h"

using namespace pal;

Expand Down Expand Up @@ -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 )
Expand Down
12 changes: 12 additions & 0 deletions src/core/pal/geomfunction.h
Expand Up @@ -31,6 +31,7 @@
#define PAL_GEOM_FUNCTION

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

namespace pal
{
Expand Down Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion src/core/pal/layer.cpp
Expand Up @@ -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 )
Expand Down
16 changes: 0 additions & 16 deletions src/core/pal/layer.h
Expand Up @@ -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).
Expand Down Expand Up @@ -269,7 +254,6 @@ namespace pal
bool mLabelLayer;
bool mDisplayAll;
bool mCentroidInside;
bool mFitInPolygon;

/** Optional flags used for some placement methods */
QgsPalLayerSettings::Placement mArrangement;
Expand Down
45 changes: 1 addition & 44 deletions src/core/pal/pointset.cpp
Expand Up @@ -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,
Expand Down

0 comments on commit c442a06

Please sign in to comment.