Skip to content

Commit dc4049d

Browse files
committedJul 20, 2015
[FEATURE][labeling] Add option to only draw labels which fit
completely within polygon features (fix #12136)
1 parent 42bef4e commit dc4049d

File tree

11 files changed

+208
-56
lines changed

11 files changed

+208
-56
lines changed
 

‎python/core/qgspallabeling.sip

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,10 @@ class QgsPalLayerSettings
418418

419419
bool centroidWhole; // whether centroid calculated from whole or visible polygon
420420
bool centroidInside; // whether centroid-point calculated must be inside polygon
421+
422+
/** True if only labels which completely fit within a polygon are allowed.
423+
*/
424+
bool fitInPolygonOnly;
421425
double dist; // distance from the feature (in mm)
422426
bool distInMapUnits; //true if distance is in map units (otherwise in mm)
423427
QgsMapUnitScale distMapUnitScale;

‎src/app/qgslabelinggui.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas,
156156
mDirectSymbolsFrame->setVisible( layer->geometryType() == QGis::Line );
157157
mMinSizeFrame->setVisible( layer->geometryType() != QGis::Point );
158158
mPolygonObstacleTypeFrame->setVisible( layer->geometryType() == QGis::Polygon );
159+
mPolygonFeatureOptionsFrame->setVisible( layer->geometryType() == QGis::Polygon );
159160

160161
// field combo and expression button
161162
mFieldExpressionWidget->setLayer( mLayer );
@@ -310,6 +311,7 @@ void QgsLabelingGui::init()
310311
// populate placement options
311312
mCentroidRadioWhole->setChecked( lyr.centroidWhole );
312313
mCentroidInsideCheckBox->setChecked( lyr.centroidInside );
314+
mFitInsidePolygonCheckBox->setChecked( lyr.fitInPolygonOnly );
313315
mLineDistanceSpnBx->setValue( lyr.dist );
314316
mLineDistanceUnitWidget->setUnit( lyr.distInMapUnits ? QgsSymbolV2::MapUnit : QgsSymbolV2::MM );
315317
mLineDistanceUnitWidget->setMapUnitScale( lyr.distMapUnitScale );
@@ -593,6 +595,7 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
593595
QWidget* curPlacementWdgt = stackedPlacement->currentWidget();
594596
lyr.centroidWhole = mCentroidRadioWhole->isChecked();
595597
lyr.centroidInside = mCentroidInsideCheckBox->isChecked();
598+
lyr.fitInPolygonOnly = mFitInsidePolygonCheckBox->isChecked();
596599
lyr.dist = mLineDistanceSpnBx->value();
597600
lyr.distInMapUnits = ( mLineDistanceUnitWidget->unit() == QgsSymbolV2::MapUnit );
598601
lyr.distMapUnitScale = mLineDistanceUnitWidget->getMapUnitScale();

‎src/core/pal/feature.cpp

Lines changed: 94 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ namespace pal
237237
}
238238
}
239239

240-
int FeaturePart::setPositionOverPoint( double x, double y, LabelPosition ***lPos, double angle )
240+
int FeaturePart::setPositionOverPoint( double x, double y, LabelPosition ***lPos, double angle, PointSet *mapShape )
241241
{
242242
int nbp = 1;
243243
*lPos = new LabelPosition *[nbp];
@@ -308,20 +308,30 @@ namespace pal
308308
double lx = x + xdiff;
309309
double ly = y + ydiff;
310310

311+
if ( mapShape && type == GEOS_POLYGON && mFeature->layer->fitInPolygonOnly() )
312+
{
313+
if ( !mapShape->containsLabelCandidate( lx, ly, labelW, labelH, angle ) )
314+
{
315+
delete[] *lPos;
316+
*lPos = 0;
317+
return 0;
318+
}
319+
}
320+
311321
( *lPos )[0] = new LabelPosition( id, lx, ly, labelW, labelH, angle, cost, this, false, quadrantFromOffset() );
312322
return nbp;
313323
}
314324

315-
int FeaturePart::setPositionForPoint( double x, double y, LabelPosition ***lPos, double angle )
325+
int FeaturePart::setPositionForPoint( double x, double y, LabelPosition ***lPos, double angle, PointSet *mapShape )
316326
{
317327

318328
#ifdef _DEBUG_
319329
std::cout << "SetPosition (point) : " << layer->name << "/" << uid << std::endl;
320330
#endif
321331

322-
double xrm = mFeature->label_x;
323-
double yrm = mFeature->label_y;
324-
double distlabel = mFeature->distlabel;
332+
double labelWidth = mFeature->label_x;
333+
double labelHeight = mFeature->label_y;
334+
double distanceToLabel = mFeature->distlabel;
325335

326336
int numberCandidates = mFeature->layer->pal->point_p;
327337

@@ -339,10 +349,10 @@ namespace pal
339349

340350
double gamma1, gamma2;
341351

342-
if ( distlabel > 0 )
352+
if ( distanceToLabel > 0 )
343353
{
344-
gamma1 = atan2( yrm / 2, distlabel + xrm / 2 );
345-
gamma2 = atan2( xrm / 2, distlabel + yrm / 2 );
354+
gamma1 = atan2( labelHeight / 2, distanceToLabel + labelWidth / 2 );
355+
gamma2 = atan2( labelWidth / 2, distanceToLabel + labelHeight / 2 );
346356
}
347357
else
348358
{
@@ -361,7 +371,7 @@ namespace pal
361371
std::cout << "Oups... label size error..." << std::endl;
362372
}
363373

364-
*lPos = new LabelPosition *[numberCandidates];
374+
QList< LabelPosition* > candidates;
365375

366376
int i;
367377
double angleToCandidate;
@@ -377,59 +387,59 @@ namespace pal
377387

378388
if ( angleToCandidate < gamma1 || angleToCandidate > a360 - gamma1 ) // on the right
379389
{
380-
labelX += distlabel;
390+
labelX += distanceToLabel;
381391
double iota = ( angleToCandidate + gamma1 );
382392
if ( iota > a360 - gamma1 )
383393
iota -= a360;
384394

385395
//ly += -yrm/2.0 + tan(alpha)*(distlabel + xrm/2);
386-
labelY += -yrm + yrm * iota / ( 2 * gamma1 );
396+
labelY += -labelHeight + labelHeight * iota / ( 2 * gamma1 );
387397

388398
quadrant = LabelPosition::QuadrantRight;
389399
}
390400
else if ( angleToCandidate < a90 - gamma2 ) // top-right
391401
{
392-
labelX += distlabel * cos( angleToCandidate );
393-
labelY += distlabel * sin( angleToCandidate );
402+
labelX += distanceToLabel * cos( angleToCandidate );
403+
labelY += distanceToLabel * sin( angleToCandidate );
394404
quadrant = LabelPosition::QuadrantAboveRight;
395405
}
396406
else if ( angleToCandidate < a90 + gamma2 ) // top
397407
{
398408
//lx += -xrm/2.0 - tan(alpha+a90)*(distlabel + yrm/2);
399-
labelX += -xrm * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
400-
labelY += distlabel;
409+
labelX += -labelWidth * ( angleToCandidate - a90 + gamma2 ) / ( 2 * gamma2 );
410+
labelY += distanceToLabel;
401411
quadrant = LabelPosition::QuadrantAbove;
402412
}
403413
else if ( angleToCandidate < a180 - gamma1 ) // top left
404414
{
405-
labelX += distlabel * cos( angleToCandidate ) - xrm;
406-
labelY += distlabel * sin( angleToCandidate );
415+
labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
416+
labelY += distanceToLabel * sin( angleToCandidate );
407417
quadrant = LabelPosition::QuadrantAboveLeft;
408418
}
409419
else if ( angleToCandidate < a180 + gamma1 ) // left
410420
{
411-
labelX += -distlabel - xrm;
421+
labelX += -distanceToLabel - labelWidth;
412422
//ly += -yrm/2.0 - tan(alpha)*(distlabel + xrm/2);
413-
labelY += - ( angleToCandidate - a180 + gamma1 ) * yrm / ( 2 * gamma1 );
423+
labelY += - ( angleToCandidate - a180 + gamma1 ) * labelHeight / ( 2 * gamma1 );
414424
quadrant = LabelPosition::QuadrantLeft;
415425
}
416426
else if ( angleToCandidate < a270 - gamma2 ) // down - left
417427
{
418-
labelX += distlabel * cos( angleToCandidate ) - xrm;
419-
labelY += distlabel * sin( angleToCandidate ) - yrm;
428+
labelX += distanceToLabel * cos( angleToCandidate ) - labelWidth;
429+
labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
420430
quadrant = LabelPosition::QuadrantBelowLeft;
421431
}
422432
else if ( angleToCandidate < a270 + gamma2 ) // down
423433
{
424-
labelY += -distlabel - yrm;
434+
labelY += -distanceToLabel - labelHeight;
425435
//lx += -xrm/2.0 + tan(alpha+a90)*(distlabel + yrm/2);
426-
labelX += -xrm + ( angleToCandidate - a270 + gamma2 ) * xrm / ( 2 * gamma2 );
436+
labelX += -labelWidth + ( angleToCandidate - a270 + gamma2 ) * labelWidth / ( 2 * gamma2 );
427437
quadrant = LabelPosition::QuadrantBelow;
428438
}
429439
else if ( angleToCandidate < a360 ) // down - right
430440
{
431-
labelX += distlabel * cos( angleToCandidate );
432-
labelY += distlabel * sin( angleToCandidate ) - yrm;
441+
labelX += distanceToLabel * cos( angleToCandidate );
442+
labelY += distanceToLabel * sin( angleToCandidate ) - labelHeight;
433443
quadrant = LabelPosition::QuadrantBelowRight;
434444
}
435445

@@ -440,7 +450,16 @@ namespace pal
440450
else
441451
cost = 0.0001 + 0.0020 * double( icost ) / double( numberCandidates - 1 );
442452

443-
( *lPos )[i] = new LabelPosition( i, labelX, labelY, xrm, yrm, angle, cost, this, false, quadrant );
453+
454+
if ( mapShape && type == GEOS_POLYGON && mFeature->layer->fitInPolygonOnly() )
455+
{
456+
if ( !mapShape->containsLabelCandidate( labelX, labelY, labelWidth, labelHeight, angle ) )
457+
{
458+
continue;
459+
}
460+
}
461+
462+
candidates << new LabelPosition( i, labelX, labelY, labelWidth, labelHeight, angle, cost, this, false, quadrant );
444463

445464
icost += inc;
446465

@@ -457,10 +476,19 @@ namespace pal
457476

458477
}
459478

460-
return numberCandidates;
479+
if ( !candidates.isEmpty() )
480+
{
481+
*lPos = new LabelPosition *[candidates.count()];
482+
for ( int i = 0; i < candidates.count(); ++i )
483+
{
484+
( *lPos )[i] = candidates.at( i );
485+
}
486+
}
487+
488+
return candidates.count();
461489
}
462490

463-
// TODO work with squared distance by remonving call to sqrt or dist_euc2d
491+
// TODO work with squared distance by removing call to sqrt or dist_euc2d
464492
int FeaturePart::setPositionForLine( LabelPosition ***lPos, PointSet *mapShape )
465493
{
466494
#ifdef _DEBUG_
@@ -969,8 +997,8 @@ namespace pal
969997
int i;
970998
int j;
971999

972-
double xrm = mFeature->label_x;
973-
double yrm = mFeature->label_y;
1000+
double labelWidth = mFeature->label_x;
1001+
double labelHeight = mFeature->label_y;
9741002

9751003
//print();
9761004

@@ -981,7 +1009,7 @@ namespace pal
9811009

9821010
shapes_toProcess.append( mapShape );
9831011

984-
splitPolygons( shapes_toProcess, shapes_final, xrm, yrm, mFeature->uid );
1012+
splitPolygons( shapes_toProcess, shapes_final, labelWidth, labelHeight, mFeature->uid );
9851013

9861014
int nbp;
9871015

@@ -997,7 +1025,7 @@ namespace pal
9971025
double dy;
9981026
int bbid;
9991027
double beta;
1000-
double diago = sqrt( xrm * xrm / 4.0 + yrm * yrm / 4 );
1028+
double diago = sqrt( labelWidth * labelWidth / 4.0 + labelHeight * labelHeight / 4 );
10011029
double rx, ry;
10021030
CHullBox **boxes = new CHullBox*[shapes_final.size()];
10031031
j = 0;
@@ -1015,12 +1043,16 @@ namespace pal
10151043
}
10161044

10171045
//dx = dy = min( yrm, xrm ) / 2;
1018-
dx = xrm / 2.0;
1019-
dy = yrm / 2.0;
1046+
dx = labelWidth / 2.0;
1047+
dy = labelHeight / 2.0;
1048+
10201049

1050+
int numTry = 0;
1051+
1052+
//fit in polygon only mode slows down calculation a lot, so if it's enabled
1053+
//then use a smaller limit for number of iterations
1054+
int maxTry = mFeature->layer->fitInPolygonOnly() ? 7 : 10;
10211055

1022-
int num_try = 0;
1023-
int max_try = 10;
10241056
do
10251057
{
10261058
for ( bbid = 0; bbid < j; bbid++ )
@@ -1033,11 +1065,21 @@ namespace pal
10331065
std::cout << " Box size: " << box->length << "/" << box->width << std::endl;
10341066
std::cout << " Alpha: " << alpha << " " << alpha * 180 / M_PI << std::endl;
10351067
std::cout << " Dx;Dy: " << dx << " " << dy << std::endl;
1036-
std::cout << " LabelSizerm: " << xrm << " " << yrm << std::endl;
1068+
std::cout << " LabelSizerm: " << labelWidth << " " << labelHeight << std::endl;
10371069
std::cout << " LabelSizeUn: " << mFeature->label_x << " " << mFeature->label_y << std::endl;
10381070
continue;
10391071
}
10401072

1073+
if ( mFeature->layer->arrangement() == P_HORIZ && mFeature->layer->fitInPolygonOnly() )
1074+
{
1075+
//check width/height of bbox is sufficient for label
1076+
if ( box->length < labelWidth || box->width < labelHeight )
1077+
{
1078+
//no way label can fit in this box, skip it
1079+
continue;
1080+
}
1081+
}
1082+
10411083
#ifdef _DEBUG_FULL_
10421084
std::cout << "New BBox : " << bbid << std::endl;
10431085
for ( i = 0; i < 4; i++ )
@@ -1050,16 +1092,16 @@ namespace pal
10501092
if ( mFeature->layer->arrangement() == P_FREE )
10511093
{
10521094
enoughPlace = true;
1053-
px = ( box->x[0] + box->x[2] ) / 2 - xrm;
1054-
py = ( box->y[0] + box->y[2] ) / 2 - yrm;
1095+
px = ( box->x[0] + box->x[2] ) / 2 - labelWidth;
1096+
py = ( box->y[0] + box->y[2] ) / 2 - labelHeight;
10551097
int i, j;
10561098

10571099
// Virtual label: center on bbox center, label size = 2x original size
10581100
// alpha = 0.
10591101
// If all corner are in bbox then place candidates horizontaly
1060-
for ( rx = px, i = 0; i < 2; rx = rx + 2 * xrm, i++ )
1102+
for ( rx = px, i = 0; i < 2; rx = rx + 2 * labelWidth, i++ )
10611103
{
1062-
for ( ry = py, j = 0; j < 2; ry = ry + 2 * yrm, j++ )
1104+
for ( ry = py, j = 0; j < 2; ry = ry + 2 * labelHeight, j++ )
10631105
{
10641106
if ( !mapShape->containsPoint( rx, ry ) )
10651107
{
@@ -1079,7 +1121,7 @@ namespace pal
10791121
{
10801122
alpha = 0.0; // HORIZ
10811123
}
1082-
else if ( box->length > 1.5*xrm && box->width > 1.5*xrm )
1124+
else if ( box->length > 1.5*labelWidth && box->width > 1.5*labelWidth )
10831125
{
10841126
if ( box->alpha <= M_PI / 4 )
10851127
{
@@ -1099,7 +1141,7 @@ namespace pal
10991141
alpha = box->alpha;
11001142
}
11011143

1102-
beta = atan2( yrm, xrm ) + alpha;
1144+
beta = atan2( labelHeight, labelWidth ) + alpha;
11031145

11041146

11051147
//alpha = box->alpha;
@@ -1108,7 +1150,6 @@ namespace pal
11081150
dlx = cos( beta ) * diago;
11091151
dly = sin( beta ) * diago;
11101152

1111-
11121153
double px0, py0;
11131154

11141155
px0 = box->width / 2.0;
@@ -1128,11 +1169,13 @@ namespace pal
11281169
rx += box->x[0];
11291170
ry += box->y[0];
11301171

1131-
// Only accept candidate that center is in the polygon
1132-
if ( mapShape->containsPoint( rx, ry ) )
1172+
bool candidateAcceptable = ( mFeature->layer->fitInPolygonOnly()
1173+
? mapShape->containsLabelCandidate( rx - dlx, ry - dly, labelWidth, labelHeight, alpha )
1174+
: mapShape->containsPoint( rx, ry ) );
1175+
if ( candidateAcceptable )
11331176
{
11341177
// cost is set to minimal value, evaluated later
1135-
positions.append( new LabelPosition( id++, rx - dlx, ry - dly, xrm, yrm, alpha, 0.0001, this ) ); // Polygon
1178+
positions.append( new LabelPosition( id++, rx - dlx, ry - dly, labelWidth, labelHeight, alpha, 0.0001, this ) ); // Polygon
11361179
}
11371180
}
11381181
}
@@ -1143,10 +1186,10 @@ namespace pal
11431186
{
11441187
dx /= 2;
11451188
dy /= 2;
1146-
num_try++;
1189+
numTry++;
11471190
}
11481191
}
1149-
while ( nbp == 0 && num_try < max_try );
1192+
while ( nbp == 0 && numTry < maxTry );
11501193

11511194
nbp = positions.size();
11521195

@@ -1245,9 +1288,9 @@ namespace pal
12451288
double cx, cy;
12461289
mapShape->getCentroid( cx, cy, mFeature->layer->centroidInside() );
12471290
if ( mFeature->layer->arrangement() == P_POINT_OVER )
1248-
nbp = setPositionOverPoint( cx, cy, lPos, angle );
1291+
nbp = setPositionOverPoint( cx, cy, lPos, angle, mapShape );
12491292
else
1250-
nbp = setPositionForPoint( cx, cy, lPos, angle );
1293+
nbp = setPositionForPoint( cx, cy, lPos, angle, mapShape );
12511294
break;
12521295
case P_LINE:
12531296
nbp = setPositionForLine( lPos, mapShape );

0 commit comments

Comments
 (0)
Please sign in to comment.