From 3a44e294de08ee03c0386179693c61a3f76e5efe Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 17 Jul 2015 15:26:57 +1000 Subject: [PATCH] [FEATURE][labeling] Add option to control how polygon layers act as obstacles Options are either avoid placing labels over polygon interior or avoid placing over polygon boundaries. (Previous behaviour was always avoid placing over interior). Avoiding placing over boundaries is useful for regional boundary layers, where the features cover an entire area. In this case it's impossible to avoid placing labels within these features, and it looks much better to avoid placing them over the boundaries between features. End result is better cartographic placement of labels in this situation. --- python/core/qgspallabeling.sip | 15 ++++++++ src/app/qgslabelinggui.cpp | 15 ++++++-- src/app/qgslabelinggui.h | 1 + src/core/pal/costcalculator.cpp | 30 +++++++++++----- src/core/pal/costcalculator.h | 12 +++---- src/core/pal/feature.cpp | 38 +++++--------------- src/core/pal/feature.h | 10 +++--- src/core/pal/labelposition.cpp | 14 ++++---- src/core/pal/labelposition.h | 4 +-- src/core/pal/layer.cpp | 1 + src/core/pal/layer.h | 14 ++++++++ src/core/pal/pal.cpp | 10 +++--- src/core/pal/pal.h | 6 ++++ src/core/pal/problem.cpp | 2 +- src/core/qgspallabeling.cpp | 14 ++++++++ src/core/qgspallabeling.h | 15 ++++++++ src/ui/qgslabelingguibase.ui | 62 ++++++++++++++++++++++++++------- 17 files changed, 183 insertions(+), 80 deletions(-) diff --git a/python/core/qgspallabeling.sip b/python/core/qgspallabeling.sip index fd0fdddbc0e..e8bbb829917 100644 --- a/python/core/qgspallabeling.sip +++ b/python/core/qgspallabeling.sip @@ -145,6 +145,17 @@ class QgsPalLayerSettings will be drawn with right alignment*/ }; + /** Valid obstacle types, which affect how features within the layer will act as obstacles + * for labels. + */ + enum ObstacleType + { + PolygonInterior, /*!< avoid placing labels over interior of polygon (prefer placing labels totally + outside or just slightly inside polygon) */ + PolygonBoundary /*!< avoid placing labels over boundary of polygon (prefer placing outside or + completely inside polygon) */ + }; + enum ShapeType { ShapeRectangle, @@ -453,6 +464,10 @@ class QgsPalLayerSettings double minFeatureSize; // minimum feature size to be labelled (in mm) bool obstacle; // whether features for layer are obstacles to labels of other layers + /** Controls how features act as obstacles for labels + */ + ObstacleType obstacleType; + //-- scale factors double vectorScaleFactor; //scale factor painter units->pixels double rasterCompressFactor; //pixel resolution scale factor diff --git a/src/app/qgslabelinggui.cpp b/src/app/qgslabelinggui.cpp index 61b74c0cd60..850a525b333 100644 --- a/src/app/qgslabelinggui.cpp +++ b/src/app/qgslabelinggui.cpp @@ -80,6 +80,9 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, mFontLetterSpacingSpinBox->setClearValue( 0.0 ); mFontWordSpacingSpinBox->setClearValue( 0.0 ); + mObstacleTypeComboBox->addItem( tr( "Over the feature's interior" ), QgsPalLayerSettings::PolygonInterior ); + mObstacleTypeComboBox->addItem( tr( "Over the feature's boundary" ), QgsPalLayerSettings::PolygonBoundary ); + mCharDlg = new QgsCharacterSelectorDialog( this ); mRefFont = lblFontPreview->font(); @@ -152,6 +155,7 @@ QgsLabelingGui::QgsLabelingGui( QgsVectorLayer* layer, QgsMapCanvas* mapCanvas, chkMergeLines->setVisible( layer->geometryType() == QGis::Line ); mDirectSymbolsFrame->setVisible( layer->geometryType() == QGis::Line ); mMinSizeFrame->setVisible( layer->geometryType() != QGis::Point ); + mPolygonObstacleTypeFrame->setVisible( layer->geometryType() == QGis::Polygon ); // field combo and expression button mFieldExpressionWidget->setLayer( mLayer ); @@ -354,7 +358,8 @@ void QgsLabelingGui::init() mRepeatDistanceUnitWidget->setMapUnitScale( lyr.repeatDistanceMapUnitScale ); mPrioritySlider->setValue( lyr.priority ); - chkNoObstacle->setChecked( lyr.obstacle ); + mChkNoObstacle->setChecked( lyr.obstacle ); + mObstacleTypeComboBox->setCurrentIndex( mObstacleTypeComboBox->findData( lyr.obstacleType ) ); chkLabelPerFeaturePart->setChecked( lyr.labelPerPart ); mPalShowAllLabelsForLayerChkBx->setChecked( lyr.displayAll ); chkMergeLines->setChecked( lyr.mergeLines ); @@ -648,7 +653,8 @@ QgsPalLayerSettings QgsLabelingGui::layerSettings() lyr.previewBkgrdColor = mPreviewBackgroundBtn->color(); lyr.priority = mPrioritySlider->value(); - lyr.obstacle = chkNoObstacle->isChecked(); + lyr.obstacle = mChkNoObstacle->isChecked(); + lyr.obstacleType = ( QgsPalLayerSettings::ObstacleType )mObstacleTypeComboBox->itemData( mObstacleTypeComboBox->currentIndex() ).toInt(); lyr.labelPerPart = chkLabelPerFeaturePart->isChecked(); lyr.displayAll = mPalShowAllLabelsForLayerChkBx->isChecked(); lyr.mergeLines = chkMergeLines->isChecked(); @@ -1698,6 +1704,11 @@ void QgsLabelingGui::on_mDirectSymbRightToolBtn_clicked() mDirectSymbRightLineEdit->setText( QString( dirSymb ) ); } +void QgsLabelingGui::on_mChkNoObstacle_toggled( bool active ) +{ + mPolygonObstacleTypeFrame->setEnabled( active ); +} + void QgsLabelingGui::showBackgroundRadius( bool show ) { mShapeRadiusLabel->setVisible( show ); diff --git a/src/app/qgslabelinggui.h b/src/app/qgslabelinggui.h index 6fbb0b6ba85..1941883c8de 100644 --- a/src/app/qgslabelinggui.h +++ b/src/app/qgslabelinggui.h @@ -80,6 +80,7 @@ class APP_EXPORT QgsLabelingGui : public QWidget, private Ui::QgsLabelingGuiBase void on_mPreviewBackgroundBtn_colorChanged( const QColor &color ); void on_mDirectSymbLeftToolBtn_clicked(); void on_mDirectSymbRightToolBtn_clicked(); + void on_mChkNoObstacle_toggled( bool active ); protected: void blockInitSignals( bool block ); diff --git a/src/core/pal/costcalculator.cpp b/src/core/pal/costcalculator.cpp index 4e7b9a08940..212e8a7c72c 100644 --- a/src/core/pal/costcalculator.cpp +++ b/src/core/pal/costcalculator.cpp @@ -30,17 +30,17 @@ namespace pal { - void CostCalculator::addObstacleCostPenalty( LabelPosition* lp, PointSet* feat ) + void CostCalculator::addObstacleCostPenalty( LabelPosition* lp, FeaturePart* obstacle ) { int n = 0; double dist; double distlabel = lp->feature->getLabelDistance(); - switch ( feat->getGeosType() ) + switch ( obstacle->getGeosType() ) { case GEOS_POINT: - dist = lp->getDistanceToPoint( feat->x[0], feat->y[0] ); + dist = lp->getDistanceToPoint( obstacle->x[0], obstacle->y[0] ); if ( dist < 0 ) n = 2; else if ( dist < distlabel ) @@ -52,11 +52,23 @@ namespace pal case GEOS_LINESTRING: // Is one of label's borders crossing the line ? - n = ( lp->isBorderCrossingLine( feat ) ? 1 : 0 ); + n = ( lp->isBorderCrossingLine( obstacle ) ? 1 : 0 ); break; case GEOS_POLYGON: - n = lp->getNumPointsInPolygon( feat->getNumPoints(), feat->x, feat->y ); + // behaviour depends on obstacle avoid type + switch ( obstacle->layer()->obstacleType() ) + { + case PolygonInterior: + // n ranges from 0 -> 12 + n = lp->getNumPointsInPolygon( obstacle->getNumPoints(), obstacle->x, obstacle->y ); + break; + case PolygonBoundary: + // penalty may need tweaking, given that interior mode ranges up to 12 + n = ( lp->isBorderCrossingLine( obstacle ) ? 6 : 0 ); + break; + } + break; } @@ -67,7 +79,7 @@ namespace pal //////// - void CostCalculator::setPolygonCandidatesCost( int nblp, LabelPosition **lPos, int max_p, RTree *obstacles, double bbx[4], double bby[4] ) + void CostCalculator::setPolygonCandidatesCost( int nblp, LabelPosition **lPos, int max_p, RTree *obstacles, double bbx[4], double bby[4] ) { int i; @@ -125,7 +137,7 @@ namespace pal } - void CostCalculator::setCandidateCostFromPolygon( LabelPosition* lp, RTree *obstacles, double bbx[4], double bby[4] ) + void CostCalculator::setCandidateCostFromPolygon( LabelPosition* lp, RTree *obstacles, double bbx[4], double bby[4] ) { double amin[2]; @@ -153,7 +165,7 @@ namespace pal delete pCost; } - int CostCalculator::finalizeCandidatesCosts( Feats* feat, int max_p, RTree *obstacles, double bbx[4], double bby[4] ) + int CostCalculator::finalizeCandidatesCosts( Feats* feat, int max_p, RTree *obstacles, double bbx[4], double bby[4] ) { // If candidates list is smaller than expected if ( max_p > feat->nblp ) @@ -191,7 +203,7 @@ namespace pal if ( feat->feature->getGeosType() == GEOS_POLYGON ) { - int arrangement = feat->feature->getLayer()->arrangement(); + int arrangement = feat->feature->layer()->arrangement(); if ( arrangement == P_FREE || arrangement == P_HORIZ ) setPolygonCandidatesCost( stop, ( LabelPosition** ) feat->lPos, max_p, obstacles, bbx, bby ); } diff --git a/src/core/pal/costcalculator.h b/src/core/pal/costcalculator.h index e4e5b396995..e68b314d12d 100644 --- a/src/core/pal/costcalculator.h +++ b/src/core/pal/costcalculator.h @@ -25,18 +25,18 @@ namespace pal { public: /** Increase candidate's cost according to its collision with passed feature */ - static void addObstacleCostPenalty( LabelPosition* lp, PointSet* feat ); + static void addObstacleCostPenalty( LabelPosition* lp, pal::FeaturePart *obstacle ); - static void setPolygonCandidatesCost( int nblp, LabelPosition **lPos, int max_p, RTree *obstacles, double bbx[4], double bby[4] ); + static void setPolygonCandidatesCost( int nblp, LabelPosition **lPos, int max_p, RTree *obstacles, double bbx[4], double bby[4] ); /** Set cost to the smallest distance between lPos's centroid and a polygon stored in geoetry field */ - static void setCandidateCostFromPolygon( LabelPosition* lp, RTree *obstacles, double bbx[4], double bby[4] ); + static void setCandidateCostFromPolygon( LabelPosition* lp, RTree *obstacles, double bbx[4], double bby[4] ); /** Sort candidates by costs, skip the worse ones, evaluate polygon candidates */ - static int finalizeCandidatesCosts( Feats* feat, int max_p, RTree *obstacles, double bbx[4], double bby[4] ); + static int finalizeCandidatesCosts( Feats* feat, int max_p, RTree *obstacles, double bbx[4], double bby[4] ); }; - /** + /** * \brief Data structure to compute polygon's candidates costs * * eight segment from center of candidat to (rpx,rpy) points (0°, 45°, 90°, ..., 315°) @@ -49,7 +49,7 @@ namespace pal public: PolygonCostCalculator( LabelPosition *lp ); - void update( PointSet *pset ); + void update( pal::PointSet *pset ); double getCost(); diff --git a/src/core/pal/feature.cpp b/src/core/pal/feature.cpp index e278e33367e..1c357b27647 100644 --- a/src/core/pal/feature.cpp +++ b/src/core/pal/feature.cpp @@ -132,8 +132,6 @@ namespace pal void FeaturePart::extractCoords( const GEOSGeometry* geom ) { - int i, j; - const GEOSCoordSequence *coordSeq; GEOSContextHandle_t geosctxt = geosContext(); @@ -145,35 +143,15 @@ namespace pal { // set nbHoles, holes member variables nbHoles = GEOSGetNumInteriorRings_r( geosctxt, geom ); - holes = new PointSet*[nbHoles]; + holes = new FeaturePart*[nbHoles]; - for ( i = 0; i < nbHoles; i++ ) + for ( int i = 0; i < nbHoles; ++i ) { - holes[i] = new PointSet(); - holes[i]->holeOf = NULL; - const GEOSGeometry* interior = GEOSGetInteriorRingN_r( geosctxt, geom, i ); - holes[i]->nbPoints = GEOSGetNumCoordinates_r( geosctxt, interior ); - holes[i]->x = new double[holes[i]->nbPoints]; - holes[i]->y = new double[holes[i]->nbPoints]; - - holes[i]->xmin = holes[i]->ymin = DBL_MAX; - holes[i]->xmax = holes[i]->ymax = -DBL_MAX; - - coordSeq = GEOSGeom_getCoordSeq_r( geosctxt, interior ); - - for ( j = 0; j < holes[i]->nbPoints; j++ ) - { - GEOSCoordSeq_getX_r( geosctxt, coordSeq, j, &holes[i]->x[j] ); - GEOSCoordSeq_getY_r( geosctxt, coordSeq, j, &holes[i]->y[j] ); - - holes[i]->xmax = holes[i]->x[j] > holes[i]->xmax ? holes[i]->x[j] : holes[i]->xmax; - holes[i]->xmin = holes[i]->x[j] < holes[i]->xmin ? holes[i]->x[j] : holes[i]->xmin; - - holes[i]->ymax = holes[i]->y[j] > holes[i]->ymax ? holes[i]->y[j] : holes[i]->ymax; - holes[i]->ymin = holes[i]->y[j] < holes[i]->ymin ? holes[i]->y[j] : holes[i]->ymin; - } - + holes[i] = new FeaturePart( f, interior ); + holes[i]->holeOf = NULL; + // possibly not needed. it's not done for the exterior ring, so I'm not sure + // why it's just done here... reorderPolygon( holes[i]->nbPoints, holes[i]->x, holes[i]->y ); } } @@ -199,7 +177,7 @@ namespace pal x = new double[nbPoints]; y = new double[nbPoints]; - for ( i = 0; i < nbPoints; i++ ) + for ( int i = 0; i < nbPoints; ++i ) { GEOSCoordSeq_getX_r( geosctxt, coordSeq, i, &x[i] ); GEOSCoordSeq_getY_r( geosctxt, coordSeq, i, &y[i] ); @@ -259,7 +237,7 @@ namespace pal - Layer *FeaturePart::getLayer() + Layer* FeaturePart::layer() { return f->layer; } diff --git a/src/core/pal/feature.h b/src/core/pal/feature.h index 0f108aefed3..e63a718577d 100644 --- a/src/core/pal/feature.h +++ b/src/core/pal/feature.h @@ -247,11 +247,9 @@ namespace pal */ const GEOSGeometry* getGeometry() const { return the_geom; } - /** - * \brief return the layer that feature belongs to - * \return the layer of the feature + /** Returns the layer that feature belongs to. */ - Layer * getLayer(); + Layer* layer(); /** * \brief generic method to generate candidates @@ -295,7 +293,7 @@ namespace pal bool getAlwaysShow() { return f->alwaysShow; } int getNumSelfObstacles() const { return nbHoles; } - PointSet* getSelfObstacle( int i ) { return holes[i]; } + FeaturePart* getSelfObstacle( int i ) { return holes[i]; } /** Check whether this part is connected with some other part */ bool isConnected( FeaturePart* p2 ); @@ -310,7 +308,7 @@ namespace pal Feature* f; int nbHoles; - PointSet **holes; + FeaturePart **holes; GEOSGeometry *the_geom; bool ownsGeom; diff --git a/src/core/pal/labelposition.cpp b/src/core/pal/labelposition.cpp index 1b4ed4d614f..9c233d283b7 100644 --- a/src/core/pal/labelposition.cpp +++ b/src/core/pal/labelposition.cpp @@ -97,12 +97,12 @@ namespace pal y[3] = y1 + dy2; // upside down ? (curved labels are always correct) - if ( feature->getLayer()->arrangement() != P_CURVED && + if ( feature->layer()->arrangement() != P_CURVED && this->alpha > M_PI / 2 && this->alpha <= 3*M_PI / 2 ) { bool uprightLabel = false; - switch ( feature->getLayer()->upsidedownLabels() ) + switch ( feature->layer()->upsidedownLabels() ) { case Layer::Upright: uprightLabel = true; @@ -388,7 +388,7 @@ namespace pal QString LabelPosition::getLayerName() const { - return feature->getLayer()->name(); + return feature->layer()->name(); } bool LabelPosition::costShrink( void *l, void *r ) @@ -402,17 +402,17 @@ namespace pal } - bool LabelPosition::polygonObstacleCallback( PointSet *feat, void *ctx ) + bool LabelPosition::polygonObstacleCallback( FeaturePart *obstacle, void *ctx ) { PolygonCostCalculator *pCost = ( PolygonCostCalculator* ) ctx; LabelPosition *lp = pCost->getLabel(); - if (( feat == lp->feature ) || ( feat->getHoleOf() && feat->getHoleOf() != lp->feature ) ) + if (( obstacle == lp->feature ) || ( obstacle->getHoleOf() && obstacle->getHoleOf() != lp->feature ) ) { return true; } - pCost->update( feat ); + pCost->update( obstacle ); return true; } @@ -440,7 +440,7 @@ namespace pal bool LabelPosition::pruneCallback( LabelPosition *lp, void *ctx ) { - PointSet *feat = (( PruneCtx* ) ctx )->obstacle; + FeaturePart *feat = (( PruneCtx* ) ctx )->obstacle; if (( feat == lp->feature ) || ( feat->getHoleOf() && feat->getHoleOf() != lp->feature ) ) { diff --git a/src/core/pal/labelposition.h b/src/core/pal/labelposition.h index b8a4c5b1df1..a767e904dca 100644 --- a/src/core/pal/labelposition.h +++ b/src/core/pal/labelposition.h @@ -217,7 +217,7 @@ namespace pal typedef struct { Pal* pal; - PointSet *obstacle; + FeaturePart *obstacle; } PruneCtx; /** Check whether the candidate in ctx overlap with obstacle feat */ @@ -247,7 +247,7 @@ namespace pal static bool removeOverlapCallback( LabelPosition *lp, void *ctx ); // for polygon cost calculation - static bool polygonObstacleCallback( PointSet *feat, void *ctx ); + static bool polygonObstacleCallback( pal::FeaturePart *obstacle, void *ctx ); protected: diff --git a/src/core/pal/layer.cpp b/src/core/pal/layer.cpp index 3b7c1270d48..7201b6a8de5 100644 --- a/src/core/pal/layer.cpp +++ b/src/core/pal/layer.cpp @@ -52,6 +52,7 @@ namespace pal : mName( lyrName ) , pal( pal ) , mObstacle( obstacle ) + , mObstacleType( PolygonInterior ) , mActive( active ) , mLabelLayer( toLabel ) , mDisplayAll( displayAll ) diff --git a/src/core/pal/layer.h b/src/core/pal/layer.h index 5c1bd80211b..8b4d3bea02b 100644 --- a/src/core/pal/layer.h +++ b/src/core/pal/layer.h @@ -149,6 +149,19 @@ namespace pal */ bool obstacle() const { return mObstacle; } + /** Returns the obstacle type, which controls how features within the layer + * act as obstacles for labels. + * @see setObstacleType + */ + ObstacleType obstacleType() const { return mObstacleType; } + + /** Sets the obstacle type, which controls how features within the layer + * act as obstacles for labels. + * @param obstacleType new obstacle type + * @see obstacleType + */ + void setObstacleType( ObstacleType obstacleType ) { mObstacleType = obstacleType; } + /** Sets the layer's priority. * @param priority layer priority, between 0 and 1. 0 corresponds to highest priority, * 1 to lowest priority. @@ -260,6 +273,7 @@ namespace pal double mDefaultPriority; bool mObstacle; + ObstacleType mObstacleType; bool mActive; bool mLabelLayer; bool mDisplayAll; diff --git a/src/core/pal/pal.cpp b/src/core/pal/pal.cpp index 53c0666d057..51d568504d0 100644 --- a/src/core/pal/pal.cpp +++ b/src/core/pal/pal.cpp @@ -200,7 +200,7 @@ namespace pal { Layer *layer; QLinkedList* fFeats; - RTree *obstacles; + RTree *obstacles; RTree *candidates; double bbox_min[2]; double bbox_max[2]; @@ -287,7 +287,7 @@ namespace pal Pal* pal; } FilterContext; - bool filteringCallback( PointSet *pset, void *ctx ) + bool filteringCallback( FeaturePart *featurePart, void *ctx ) { RTree *cdtsIndex = (( FilterContext* ) ctx )->cdtsIndex; @@ -297,10 +297,10 @@ namespace pal return false; // do not continue searching double amin[2], amax[2]; - pset->getBoundingBox( amin, amax ); + featurePart->getBoundingBox( amin, amax ); LabelPosition::PruneCtx pruneContext; - pruneContext.obstacle = pset; + pruneContext.obstacle = featurePart; pruneContext.pal = pal; cdtsIndex->Search( amin, amax, LabelPosition::pruneCallback, ( void* ) &pruneContext ); @@ -310,7 +310,7 @@ namespace pal Problem* Pal::extract( int nbLayers, const QStringList& layersName, double lambda_min, double phi_min, double lambda_max, double phi_max ) { // to store obstacles - RTree *obstacles = new RTree(); + RTree *obstacles = new RTree(); Problem *prob = new Problem(); diff --git a/src/core/pal/pal.h b/src/core/pal/pal.h index 4f52fd6a13d..403e87c55e7 100644 --- a/src/core/pal/pal.h +++ b/src/core/pal/pal.h @@ -92,6 +92,12 @@ namespace pal }; Q_DECLARE_FLAGS( LineArrangementFlags, LineArrangementFlag ) + enum ObstacleType + { + PolygonInterior, + PolygonBoundary + }; + /** * \brief Pal main class. * diff --git a/src/core/pal/problem.cpp b/src/core/pal/problem.cpp index b783ff81e75..5d5f84939fe 100644 --- a/src/core/pal/problem.cpp +++ b/src/core/pal/problem.cpp @@ -2626,7 +2626,7 @@ namespace pal solList->push_back( labelpositions[sol->s[i]] ); // active labels } else if ( returnInactive - || labelpositions[featStartId[i]]->getFeaturePart()->getLayer()->displayAll() + || labelpositions[featStartId[i]]->getFeaturePart()->layer()->displayAll() || labelpositions[featStartId[i]]->getFeaturePart()->getAlwaysShow() ) { solList->push_back( labelpositions[featStartId[i]] ); // unplaced label diff --git a/src/core/qgspallabeling.cpp b/src/core/qgspallabeling.cpp index ae665c321b7..e9453d30c26 100644 --- a/src/core/qgspallabeling.cpp +++ b/src/core/qgspallabeling.cpp @@ -201,6 +201,7 @@ QgsPalLayerSettings::QgsPalLayerSettings() limitNumLabels = false; maxNumLabels = 2000; obstacle = true; + obstacleType = PolygonInterior; // scale factors vectorScaleFactor = 1.0; @@ -413,6 +414,7 @@ QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings& s ) limitNumLabels = s.limitNumLabels; maxNumLabels = s.maxNumLabels; obstacle = s.obstacle; + obstacleType = s.obstacleType; // shape background shapeDraw = s.shapeDraw; @@ -920,6 +922,7 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer ) limitNumLabels = layer->customProperty( "labeling/limitNumLabels", QVariant( false ) ).toBool(); maxNumLabels = layer->customProperty( "labeling/maxNumLabels", QVariant( 2000 ) ).toInt(); obstacle = layer->customProperty( "labeling/obstacle", QVariant( true ) ).toBool(); + obstacleType = ( ObstacleType )layer->customProperty( "labeling/obstacleType", QVariant( PolygonInterior ) ).toUInt(); readDataDefinedPropertyMap( layer, dataDefinedProperties ); } @@ -1071,6 +1074,7 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer ) layer->setCustomProperty( "labeling/limitNumLabels", limitNumLabels ); layer->setCustomProperty( "labeling/maxNumLabels", maxNumLabels ); layer->setCustomProperty( "labeling/obstacle", obstacle ); + layer->setCustomProperty( "labeling/obstacleType", ( unsigned int )obstacleType ); writeDataDefinedPropertyMap( layer, dataDefinedProperties ); } @@ -3316,6 +3320,16 @@ int QgsPalLabeling::prepareLayer( QgsVectorLayer* layer, QStringList& attrNames, // set whether adjacent lines should be merged l->setMergeConnectedLines( lyr.mergeLines ); + // set obstacle type + switch ( lyr.obstacleType ) + { + case PolygonInterior: + l->setObstacleType( pal::PolygonInterior ); + break; + case PolygonBoundary: + l->setObstacleType( pal::PolygonBoundary ); + break; + } // set whether location of centroid must be inside of polygons l->setCentroidInside( lyr.centroidInside ); diff --git a/src/core/qgspallabeling.h b/src/core/qgspallabeling.h index bf08ff154bf..cd96fea7ee7 100644 --- a/src/core/qgspallabeling.h +++ b/src/core/qgspallabeling.h @@ -120,6 +120,17 @@ class CORE_EXPORT QgsPalLayerSettings will be drawn with right alignment*/ }; + /** Valid obstacle types, which affect how features within the layer will act as obstacles + * for labels. + */ + enum ObstacleType + { + PolygonInterior, /*!< avoid placing labels over interior of polygon (prefer placing labels totally + outside or just slightly inside polygon) */ + PolygonBoundary /*!< avoid placing labels over boundary of polygon (prefer placing outside or + completely inside polygon) */ + }; + enum ShapeType { ShapeRectangle = 0, @@ -428,6 +439,10 @@ class CORE_EXPORT QgsPalLayerSettings double minFeatureSize; // minimum feature size to be labelled (in mm) bool obstacle; // whether features for layer are obstacles to labels of other layers + /** Controls how features act as obstacles for labels + */ + ObstacleType obstacleType; + //-- scale factors double vectorScaleFactor; //scale factor painter units->pixels double rasterCompressFactor; //pixel resolution scale factor diff --git a/src/ui/qgslabelingguibase.ui b/src/ui/qgslabelingguibase.ui index 19a660fe1af..f2fc80b3606 100644 --- a/src/ui/qgslabelingguibase.ui +++ b/src/ui/qgslabelingguibase.ui @@ -494,7 +494,7 @@ - 0 + 6 @@ -1244,7 +1244,7 @@ font-style: italic; 0 0 - 566 + 383 389 @@ -1844,8 +1844,8 @@ font-style: italic; 0 0 - 582 - 379 + 298 + 257 @@ -2205,7 +2205,7 @@ font-style: italic; 0 0 - 566 + 362 697 @@ -3008,7 +3008,7 @@ font-style: italic; 0 0 - 566 + 325 424 @@ -3489,7 +3489,7 @@ font-style: italic; 0 0 - 566 + 427 829 @@ -4818,9 +4818,9 @@ font-style: italic; 0 - 0 - 566 - 622 + -292 + 578 + 655 @@ -5440,7 +5440,7 @@ font-style: italic; - + true @@ -5449,6 +5449,44 @@ font-style: italic; + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + + + Minimise placing labels + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + @@ -5875,7 +5913,7 @@ font-style: italic; mLimitLabelChkBox mLimitLabelSpinBox mMinSizeSpinBox - chkNoObstacle + mChkNoObstacle mRenderingLabelGrpBx