Skip to content
Permalink
Browse files

[FEATURE] Label polygons using curved labels along perimeter

This adds a new mode for labeling polygons, where the perimeter
of the polygon is labeled using curved labeling.
  • Loading branch information
nyalldawson committed Jul 27, 2016
1 parent c0b1684 commit 5f33991e7bcd95069ba88e01cafa7078bbad5f24
@@ -219,6 +219,7 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas,
mPlacePolygonBtnGrp->addButton( radPolygonHorizontal, ( int )QgsPalLayerSettings::Horizontal );
mPlacePolygonBtnGrp->addButton( radPolygonFree, ( int )QgsPalLayerSettings::Free );
mPlacePolygonBtnGrp->addButton( radPolygonPerimeter, ( int )QgsPalLayerSettings::Line );
mPlacePolygonBtnGrp->addButton( radPolygonPerimeterCurved, ( int )QgsPalLayerSettings::PerimeterCurved );
mPlacePolygonBtnGrp->setExclusive( true );
connect( mPlacePolygonBtnGrp, SIGNAL( buttonClicked( int ) ), this, SLOT( updatePlacementWidgets() ) );

@@ -464,6 +465,7 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas,
<< radPolygonFree
<< radPolygonHorizontal
<< radPolygonPerimeter
<< radPolygonPerimeterCurved
<< radPredefinedOrder
<< mFieldExpressionWidget;
connectValueChanged( widgets, SLOT( updatePreview() ) );
@@ -671,6 +673,9 @@ void QgsLabelingGui::init()
case QgsPalLayerSettings::Free:
radPolygonFree->setChecked( true );
break;
case QgsPalLayerSettings::PerimeterCurved:
radPolygonPerimeterCurved->setChecked( true );
break;
}

// Label repeat distance
@@ -961,11 +966,17 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings()
lyr.placement = QgsPalLayerSettings::OrderedPositionsAroundPoint;
}
else if (( curPlacementWdgt == pageLine && radLineParallel->isChecked() )
|| ( curPlacementWdgt == pagePolygon && radPolygonPerimeter->isChecked() )
|| ( curPlacementWdgt == pageLine && radLineCurved->isChecked() ) )
|| ( curPlacementWdgt == pagePolygon && radPolygonPerimeter->isChecked() ) )
{
lyr.placement = QgsPalLayerSettings::Line;
}
else if ( curPlacementWdgt == pageLine && radLineCurved->isChecked() )
{
lyr.placement = QgsPalLayerSettings::Curved;
}
else if ( curPlacementWdgt == pagePolygon && radPolygonPerimeterCurved->isChecked() )
{
bool curved = ( curPlacementWdgt == pageLine && radLineCurved->isChecked() );
lyr.placement = ( curved ? QgsPalLayerSettings::Curved : QgsPalLayerSettings::Line );
lyr.placement = QgsPalLayerSettings::PerimeterCurved;
}
else if (( curPlacementWdgt == pageLine && radLineHorizontal->isChecked() )
|| ( curPlacementWdgt == pagePolygon && radPolygonHorizontal->isChecked() ) )
@@ -1706,7 +1717,8 @@ void QgsLabelingGui::updatePlacementWidgets()
}
else if (( curWdgt == pageLine && radLineParallel->isChecked() )
|| ( curWdgt == pagePolygon && radPolygonPerimeter->isChecked() )
|| ( curWdgt == pageLine && radLineCurved->isChecked() ) )
|| ( curWdgt == pageLine && radLineCurved->isChecked() )
|| ( curWdgt == pagePolygon && radPolygonPerimeterCurved->isChecked() ) )
{
showLineFrame = true;
showDistanceFrame = true;
@@ -1716,9 +1728,11 @@ void QgsLabelingGui::updatePlacementWidgets()
chkLineOrientationDependent->setEnabled( offline );
mPlacementDistanceFrame->setEnabled( offline );

showMaxCharAngleFrame = ( curWdgt == pageLine && radLineCurved->isChecked() );
bool isCurved = ( curWdgt == pageLine && radLineCurved->isChecked() )
|| ( curWdgt == pagePolygon && radPolygonPerimeterCurved->isChecked() );
showMaxCharAngleFrame = isCurved;
// TODO: enable mMultiLinesFrame when supported for curved labels
enableMultiLinesFrame = !( curWdgt == pageLine && radLineCurved->isChecked() );
enableMultiLinesFrame = !isCurved;
}

mPlacementLineFrame->setVisible( showLineFrame );
@@ -1730,7 +1744,8 @@ void QgsLabelingGui::updatePlacementWidgets()
mPlacementDistanceFrame->setVisible( showDistanceFrame );
mPlacementOffsetTypeFrame->setVisible( showOffsetTypeFrame );
mPlacementRotationFrame->setVisible( showRotationFrame );
mPlacementRepeatDistanceFrame->setVisible( curWdgt == pageLine || ( curWdgt == pagePolygon && radPolygonPerimeter->isChecked() ) );
mPlacementRepeatDistanceFrame->setVisible( curWdgt == pageLine || ( curWdgt == pagePolygon &&
( radPolygonPerimeter->isChecked() || radPolygonPerimeterCurved->isChecked() ) ) );
mPlacementMaxCharAngleFrame->setVisible( showMaxCharAngleFrame );

mMultiLinesFrame->setEnabled( enableMultiLinesFrame );
@@ -1008,10 +1008,21 @@ int FeaturePart::createCurvedCandidatesAlongLine( QList< LabelPosition* >& lPos,
// and the line has right-to-left direction
bool reversed = ( !( flags & FLAG_MAP_ORIENTATION ) ? isRightToLeft : false );

// an orientation of 0 means try both orientations and choose the best
int orientation = 0;
if ( !( flags & FLAG_MAP_ORIENTATION )
&& mLF->layer()->arrangement() == QgsPalLayerSettings::PerimeterCurved )
{
//... but if we are labeling the perimeter of a polygon and using line orientation flags,
// then we can only accept a single orientation, as we need to ensure that the labels fall
// inside or outside the polygon (and not mixed)
orientation = reversed ? -1 : 1;
}

// generate curved labels
for ( int i = 0; i*delta < total_distance; i++ )
{
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, 0, 1, i * delta );
LabelPosition* slp = curvedPlacementAtOffset( mapShape, path_distances, orientation, 1, i * delta );

if ( slp )
{
@@ -1325,6 +1336,8 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
case GEOS_LINESTRING:
if ( mLF->layer()->arrangement() == QgsPalLayerSettings::Curved )
createCurvedCandidatesAlongLine( lPos, mapShape );
else if ( mLF->layer()->arrangement() == QgsPalLayerSettings::PerimeterCurved )
createCurvedCandidatesAlongLine( lPos, mapShape );
else
createCandidatesAlongLine( lPos, mapShape );
break;
@@ -1344,6 +1357,9 @@ int FeaturePart::createCandidates( QList< LabelPosition*>& lPos,
case QgsPalLayerSettings::Line:
createCandidatesAlongLine( lPos, mapShape );
break;
case QgsPalLayerSettings::PerimeterCurved:
createCurvedCandidatesAlongLine( lPos, mapShape );
break;
default:
createCandidatesForPolygon( lPos, mapShape );
break;
@@ -99,6 +99,7 @@ LabelPosition::LabelPosition( int id, double x1, double y1, double w, double h,

// upside down ? (curved labels are always correct)
if ( feature->layer()->arrangement() != QgsPalLayerSettings::Curved &&
feature->layer()->arrangement() != QgsPalLayerSettings::PerimeterCurved &&
this->alpha > M_PI / 2 && this->alpha <= 3*M_PI / 2 )
{
bool uprightLabel = false;
@@ -2371,7 +2371,7 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
double maxcharanglein = 20.0; // range 20.0-60.0
double maxcharangleout = -20.0; // range 20.0-95.0

if ( placement == QgsPalLayerSettings::Curved )
if ( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved )
{
maxcharanglein = maxCurvedCharAngleIn;
maxcharangleout = maxCurvedCharAngleOut;
@@ -2514,8 +2514,8 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont
}

GEOSGeometry* geos_geom_clone;
GEOSGeomTypes geomType = (GEOSGeomTypes) GEOSGeomTypeId_r( QgsGeometry::getGEOSHandler(), geos_geom );
if ( (geomType == GEOS_POLYGON || geomType == GEOS_MULTIPOLYGON) && repeatDistance > 0 && placement == Line )
GEOSGeomTypes geomType = ( GEOSGeomTypes ) GEOSGeomTypeId_r( QgsGeometry::getGEOSHandler(), geos_geom );
if (( geomType == GEOS_POLYGON || geomType == GEOS_MULTIPOLYGON ) && repeatDistance > 0 && ( placement == Line || placement == PerimeterCurved ) )
{
geos_geom_clone = GEOSBoundary_r( QgsGeometry::getGEOSHandler(), geos_geom );
}
@@ -2876,7 +2876,8 @@ void QgsPalLayerSettings::registerFeature( QgsFeature& f, QgsRenderContext &cont

// TODO: only for placement which needs character info
// account for any data defined font metrics adjustments
lf->calculateInfo( placement == QgsPalLayerSettings::Curved, labelFontMetrics.data(), xform, rasterCompressFactor, maxcharanglein, maxcharangleout );
lf->calculateInfo( placement == QgsPalLayerSettings::Curved || placement == QgsPalLayerSettings::PerimeterCurved,
labelFontMetrics.data(), xform, rasterCompressFactor, maxcharanglein, maxcharangleout );
// for labelFeature the LabelInfo is passed to feat when it is registered

// TODO: allow layer-wide feature dist in PAL...?
@@ -194,6 +194,7 @@ class CORE_EXPORT QgsPalLayerSettings
Horizontal, /**< Arranges horizontal candidates scattered throughout a polygon feature. Applies to polygon layers only.*/
Free, /**< Arranges candidates scattered throughout a polygon feature. Candidates are rotated to respect the polygon's orientation. Applies to polygon layers only.*/
OrderedPositionsAroundPoint, /**< Candidates are placed in predefined positions around a point. Peference is given to positions with greatest cartographic appeal, eg top right, bottom right, etc. Applies to point layers only.*/
PerimeterCurved, /** Arranges candidates following the curvature of a polygon's boundary. Applies to polygon layers only.*/
};

//! Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode

4 comments on commit 5f33991

@fritsvanveen

This comment has been minimized.

Copy link
Contributor

@fritsvanveen fritsvanveen replied Jul 28, 2016

@nyalldawson
Nice, this would have been my next project!
However, I found an issue. When 'Using perimeter (curved)' is selected, labels are never plotted upside down (with 'Show upside-labels' set as 'Always'). Spacing is a bit off, but can be corrected with a higher maximum angle.
perimeter polygon labelling
perimeter polygon labelling curved
perimeter polygon labelling curved higher angle

@nyalldawson

This comment has been minimized.

Copy link
Contributor Author

@nyalldawson nyalldawson replied Jul 28, 2016

@fritsvanveen I believe thats a limitation/feature(?) of the curved labeling code. It has its own routines to avoid upside down labels in all cases.

@fritsvanveen

This comment has been minimized.

Copy link
Contributor

@fritsvanveen fritsvanveen replied Jul 28, 2016

@nyalldawson Do you want me to look in to it? Perhaps some code (upsidedown or not) needs to be refactored out.

@nyalldawson

This comment has been minimized.

Copy link
Contributor Author

@nyalldawson nyalldawson replied Jul 28, 2016

@fritsvanveen sure! There's probably lots more ways the curved labeling/perimeter labeling can be improved, and any contributions are very much welcomed!

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