LCOV - code coverage report
Current view: top level - core/callouts - qgscallout.cpp (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 1 772 0.1 %
Date: 2021-03-26 12:19:53 Functions: 0 0 -
Branches: 0 0 -

           Branch data     Line data    Source code
       1                 :            : /***************************************************************************
       2                 :            :                              qgscallout.cpp
       3                 :            :                              ----------------
       4                 :            :     begin                : July 2019
       5                 :            :     copyright            : (C) 2019 Nyall Dawson
       6                 :            :     email                : nyall dot dawson at gmail dot com
       7                 :            :  ***************************************************************************/
       8                 :            : 
       9                 :            : /***************************************************************************
      10                 :            :  *                                                                         *
      11                 :            :  *   This program is free software; you can redistribute it and/or modify  *
      12                 :            :  *   it under the terms of the GNU General Public License as published by  *
      13                 :            :  *   the Free Software Foundation; either version 2 of the License, or     *
      14                 :            :  *   (at your option) any later version.                                   *
      15                 :            :  *                                                                         *
      16                 :            :  ***************************************************************************/
      17                 :            : 
      18                 :            : #include "qgscallout.h"
      19                 :            : #include "qgsrendercontext.h"
      20                 :            : #include "qgssymbol.h"
      21                 :            : #include "qgslinesymbollayer.h"
      22                 :            : #include "qgsfillsymbollayer.h"
      23                 :            : #include "qgssymbollayerutils.h"
      24                 :            : #include "qgsxmlutils.h"
      25                 :            : #include "qgslinestring.h"
      26                 :            : #include "qgslogger.h"
      27                 :            : #include "qgsgeos.h"
      28                 :            : #include "qgsgeometryutils.h"
      29                 :            : #include "qgscircularstring.h"
      30                 :            : #include "qgsshapegenerator.h"
      31                 :            : #include <QPainter>
      32                 :            : #include <mutex>
      33                 :            : 
      34                 :          5 : QgsPropertiesDefinition QgsCallout::sPropertyDefinitions;
      35                 :            : 
      36                 :          0 : void QgsCallout::initPropertyDefinitions()
      37                 :            : {
      38                 :          0 :   const QString origin = QStringLiteral( "callouts" );
      39                 :            : 
      40                 :          0 :   sPropertyDefinitions = QgsPropertiesDefinition
      41                 :          0 :   {
      42                 :          0 :     { QgsCallout::MinimumCalloutLength, QgsPropertyDefinition( "MinimumCalloutLength", QObject::tr( "Minimum callout length" ), QgsPropertyDefinition::DoublePositive, origin ) },
      43                 :          0 :     { QgsCallout::OffsetFromAnchor, QgsPropertyDefinition( "OffsetFromAnchor", QObject::tr( "Offset from feature" ), QgsPropertyDefinition::DoublePositive, origin ) },
      44                 :          0 :     { QgsCallout::OffsetFromLabel, QgsPropertyDefinition( "OffsetFromLabel", QObject::tr( "Offset from label" ), QgsPropertyDefinition::DoublePositive, origin ) },
      45                 :          0 :     { QgsCallout::DrawCalloutToAllParts, QgsPropertyDefinition( "DrawCalloutToAllParts", QObject::tr( "Draw lines to all feature parts" ), QgsPropertyDefinition::Boolean, origin ) },
      46                 :          0 :     { QgsCallout::AnchorPointPosition, QgsPropertyDefinition( "AnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Feature's anchor point position" ), QObject::tr( "string " ) + "[<b>pole_of_inaccessibility</b>|<b>point_on_exterior</b>|<b>point_on_surface</b>|<b>centroid</b>]", origin ) },
      47                 :          0 :     {
      48                 :          0 :       QgsCallout::LabelAnchorPointPosition, QgsPropertyDefinition( "LabelAnchorPointPosition", QgsPropertyDefinition::DataTypeString, QObject::tr( "Label's anchor point position" ), QObject::tr( "string " ) + "[<b>point_on_exterior</b>|<b>centroid</b>|<b>TL</b>=Top left|<b>T</b>=Top middle|"
      49                 :            :           "<b>TR</b>=Top right|<br>"
      50                 :            :           "<b>L</b>=Left|<b>R</b>=Right|<br>"
      51                 :            :           "<b>BL</b>=Bottom left|<b>B</b>=Bottom middle|"
      52                 :            :           "<b>BR</b>=Bottom right]", origin )
      53                 :            :     },
      54                 :          0 :     { QgsCallout::OriginX, QgsPropertyDefinition( "OriginX", QObject::tr( "Callout origin (X)" ), QgsPropertyDefinition::Double, origin ) },
      55                 :          0 :     { QgsCallout::OriginY, QgsPropertyDefinition( "OriginY", QObject::tr( "Callout origin (Y)" ), QgsPropertyDefinition::Double, origin ) },
      56                 :          0 :     { QgsCallout::DestinationX, QgsPropertyDefinition( "DestinationX", QObject::tr( "Callout destination (X)" ), QgsPropertyDefinition::Double, origin ) },
      57                 :          0 :     { QgsCallout::DestinationY, QgsPropertyDefinition( "DestinationY", QObject::tr( "Callout destination (Y)" ), QgsPropertyDefinition::Double, origin ) },
      58                 :          0 :     { QgsCallout::Curvature, QgsPropertyDefinition( "Curvature", QObject::tr( "Callout line curvature" ), QgsPropertyDefinition::Double, origin ) },
      59                 :          0 :     {
      60                 :          0 :       QgsCallout::Orientation, QgsPropertyDefinition( "Orientation", QgsPropertyDefinition::DataTypeString, QObject::tr( "Callout curve orientation" ),  QObject::tr( "string " ) + "[<b>auto</b>|<b>clockwise</b>|<b>counterclockwise</b>]", origin )
      61                 :            :     },
      62                 :          0 :     {
      63                 :          0 :       QgsCallout::Margins, QgsPropertyDefinition( "Margins", QgsPropertyDefinition::DataTypeString, QObject::tr( "Margins" ), QObject::tr( "string of four doubles '<b>top,right,bottom,left</b>' or array of doubles <b>[top, right, bottom, left]</b>" ) )
      64                 :            :     },
      65                 :          0 :     { QgsCallout::WedgeWidth, QgsPropertyDefinition( "WedgeWidth", QObject::tr( "Wedge width" ), QgsPropertyDefinition::DoublePositive, origin ) },
      66                 :            :   };
      67                 :          0 : }
      68                 :            : 
      69                 :            : 
      70                 :          0 : QgsCallout::QgsCallout()
      71                 :          0 : {
      72                 :          0 : }
      73                 :            : 
      74                 :          0 : QVariantMap QgsCallout::properties( const QgsReadWriteContext & ) const
      75                 :            : {
      76                 :          0 :   QVariantMap props;
      77                 :          0 :   props.insert( QStringLiteral( "enabled" ), mEnabled ? "1" : "0" );
      78                 :          0 :   props.insert( QStringLiteral( "anchorPoint" ), encodeAnchorPoint( mAnchorPoint ) );
      79                 :          0 :   props.insert( QStringLiteral( "labelAnchorPoint" ), encodeLabelAnchorPoint( mLabelAnchorPoint ) );
      80                 :          0 :   props.insert( QStringLiteral( "ddProperties" ), mDataDefinedProperties.toVariant( propertyDefinitions() ) );
      81                 :          0 :   return props;
      82                 :          0 : }
      83                 :            : 
      84                 :          0 : void QgsCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext & )
      85                 :            : {
      86                 :          0 :   mEnabled = props.value( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
      87                 :          0 :   mAnchorPoint = decodeAnchorPoint( props.value( QStringLiteral( "anchorPoint" ), QString() ).toString() );
      88                 :          0 :   mLabelAnchorPoint = decodeLabelAnchorPoint( props.value( QStringLiteral( "labelAnchorPoint" ), QString() ).toString() );
      89                 :          0 :   mDataDefinedProperties.loadVariant( props.value( QStringLiteral( "ddProperties" ) ), propertyDefinitions() );
      90                 :          0 : }
      91                 :            : 
      92                 :          0 : bool QgsCallout::saveProperties( QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context ) const
      93                 :            : {
      94                 :          0 :   if ( element.isNull() )
      95                 :            :   {
      96                 :          0 :     return false;
      97                 :            :   }
      98                 :            : 
      99                 :          0 :   QDomElement calloutPropsElement = QgsXmlUtils::writeVariant( properties( context ), doc );
     100                 :            : 
     101                 :          0 :   QDomElement calloutElement = doc.createElement( QStringLiteral( "callout" ) );
     102                 :          0 :   calloutElement.setAttribute( QStringLiteral( "type" ), type() );
     103                 :          0 :   calloutElement.appendChild( calloutPropsElement );
     104                 :            : 
     105                 :          0 :   element.appendChild( calloutElement );
     106                 :          0 :   return true;
     107                 :          0 : }
     108                 :            : 
     109                 :          0 : void QgsCallout::restoreProperties( const QDomElement &element, const QgsReadWriteContext &context )
     110                 :            : {
     111                 :          0 :   const QVariantMap props = QgsXmlUtils::readVariant( element.firstChildElement() ).toMap();
     112                 :          0 :   readProperties( props, context );
     113                 :          0 : }
     114                 :            : 
     115                 :          0 : void QgsCallout::startRender( QgsRenderContext & )
     116                 :            : {
     117                 :            : 
     118                 :          0 : }
     119                 :          0 : void QgsCallout::stopRender( QgsRenderContext & )
     120                 :            : {
     121                 :            : 
     122                 :          0 : }
     123                 :            : 
     124                 :          0 : QSet<QString> QgsCallout::referencedFields( const QgsRenderContext &context ) const
     125                 :            : {
     126                 :          0 :   mDataDefinedProperties.prepare( context.expressionContext() );
     127                 :          0 :   return mDataDefinedProperties.referencedFields( context.expressionContext() );
     128                 :            : }
     129                 :            : 
     130                 :          0 : QgsCallout::DrawOrder QgsCallout::drawOrder() const
     131                 :            : {
     132                 :          0 :   return OrderBelowAllLabels;
     133                 :            : }
     134                 :            : 
     135                 :          0 : void QgsCallout::render( QgsRenderContext &context, const QRectF &rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
     136                 :            : {
     137                 :          0 :   if ( !mEnabled )
     138                 :          0 :     return;
     139                 :            : 
     140                 :            : #if 0 // for debugging
     141                 :            :   QPainter *painter = context.painter();
     142                 :            :   painter->save();
     143                 :            :   painter->setRenderHint( QPainter::Antialiasing, false );
     144                 :            :   painter->translate( rect.center() );
     145                 :            :   painter->rotate( -angle );
     146                 :            : 
     147                 :            :   painter->setBrush( QColor( 255, 0, 0, 100 ) );
     148                 :            :   painter->setPen( QColor( 255, 0, 0, 150 ) );
     149                 :            : 
     150                 :            :   painter->drawRect( rect.width() * -0.5, rect.height() * -0.5, rect.width(), rect.height() );
     151                 :            :   painter->restore();
     152                 :            : 
     153                 :            :   painter->setBrush( QColor( 0, 255, 0, 100 ) );
     154                 :            :   painter->setPen( QColor( 0, 255, 0, 150 ) );
     155                 :            : 
     156                 :            :   painter->drawRect( anchor.boundingBox( ).buffered( 30 ).toRectF() );
     157                 :            : #endif
     158                 :            : 
     159                 :          0 :   draw( context, rect, angle, anchor, calloutContext );
     160                 :          0 : }
     161                 :            : 
     162                 :          0 : void QgsCallout::setEnabled( bool enabled )
     163                 :            : {
     164                 :          0 :   mEnabled = enabled;
     165                 :          0 : }
     166                 :            : 
     167                 :          0 : QgsPropertiesDefinition QgsCallout::propertyDefinitions()
     168                 :            : {
     169                 :            :   static std::once_flag initialized;
     170                 :          0 :   std::call_once( initialized, [ = ]( )
     171                 :            :   {
     172                 :          0 :     initPropertyDefinitions();
     173                 :          0 :   } );
     174                 :          0 :   return sPropertyDefinitions;
     175                 :            : }
     176                 :            : 
     177                 :          0 : QgsCallout::AnchorPoint QgsCallout::decodeAnchorPoint( const QString &name, bool *ok )
     178                 :            : {
     179                 :          0 :   if ( ok )
     180                 :          0 :     *ok = true;
     181                 :          0 :   QString cleaned = name.toLower().trimmed();
     182                 :            : 
     183                 :          0 :   if ( cleaned == QLatin1String( "pole_of_inaccessibility" ) )
     184                 :          0 :     return PoleOfInaccessibility;
     185                 :          0 :   else if ( cleaned == QLatin1String( "point_on_exterior" ) )
     186                 :          0 :     return PointOnExterior;
     187                 :          0 :   else if ( cleaned == QLatin1String( "point_on_surface" ) )
     188                 :          0 :     return PointOnSurface;
     189                 :          0 :   else if ( cleaned == QLatin1String( "centroid" ) )
     190                 :          0 :     return Centroid;
     191                 :            : 
     192                 :          0 :   if ( ok )
     193                 :          0 :     *ok = false;
     194                 :          0 :   return PoleOfInaccessibility;
     195                 :          0 : }
     196                 :            : 
     197                 :          0 : QString QgsCallout::encodeAnchorPoint( AnchorPoint anchor )
     198                 :            : {
     199                 :          0 :   switch ( anchor )
     200                 :            :   {
     201                 :            :     case PoleOfInaccessibility:
     202                 :          0 :       return QStringLiteral( "pole_of_inaccessibility" );
     203                 :            :     case PointOnExterior:
     204                 :          0 :       return QStringLiteral( "point_on_exterior" );
     205                 :            :     case PointOnSurface:
     206                 :          0 :       return QStringLiteral( "point_on_surface" );
     207                 :            :     case Centroid:
     208                 :          0 :       return QStringLiteral( "centroid" );
     209                 :            :   }
     210                 :          0 :   return QString();
     211                 :          0 : }
     212                 :            : 
     213                 :          0 : QString QgsCallout::encodeLabelAnchorPoint( QgsCallout::LabelAnchorPoint anchor )
     214                 :            : {
     215                 :          0 :   switch ( anchor )
     216                 :            :   {
     217                 :            :     case LabelPointOnExterior:
     218                 :          0 :       return QStringLiteral( "point_on_exterior" );
     219                 :            :     case LabelCentroid:
     220                 :          0 :       return QStringLiteral( "centroid" );
     221                 :            :     case LabelTopLeft:
     222                 :          0 :       return QStringLiteral( "tl" );
     223                 :            :     case LabelTopMiddle:
     224                 :          0 :       return QStringLiteral( "t" );
     225                 :            :     case LabelTopRight:
     226                 :          0 :       return QStringLiteral( "tr" );
     227                 :            :     case LabelMiddleLeft:
     228                 :          0 :       return QStringLiteral( "l" );
     229                 :            :     case LabelMiddleRight:
     230                 :          0 :       return QStringLiteral( "r" );
     231                 :            :     case LabelBottomLeft:
     232                 :          0 :       return QStringLiteral( "bl" );
     233                 :            :     case LabelBottomMiddle:
     234                 :          0 :       return QStringLiteral( "b" );
     235                 :            :     case LabelBottomRight:
     236                 :          0 :       return QStringLiteral( "br" );
     237                 :            :   }
     238                 :            : 
     239                 :          0 :   return QString();
     240                 :          0 : }
     241                 :            : 
     242                 :          0 : QgsCallout::LabelAnchorPoint QgsCallout::decodeLabelAnchorPoint( const QString &name, bool *ok )
     243                 :            : {
     244                 :          0 :   if ( ok )
     245                 :          0 :     *ok = true;
     246                 :          0 :   QString cleaned = name.toLower().trimmed();
     247                 :            : 
     248                 :          0 :   if ( cleaned == QLatin1String( "point_on_exterior" ) )
     249                 :          0 :     return LabelPointOnExterior;
     250                 :          0 :   else if ( cleaned == QLatin1String( "centroid" ) )
     251                 :          0 :     return LabelCentroid;
     252                 :          0 :   else if ( cleaned == QLatin1String( "tl" ) )
     253                 :          0 :     return LabelTopLeft;
     254                 :          0 :   else if ( cleaned == QLatin1String( "t" ) )
     255                 :          0 :     return LabelTopMiddle;
     256                 :          0 :   else if ( cleaned == QLatin1String( "tr" ) )
     257                 :          0 :     return LabelTopRight;
     258                 :          0 :   else if ( cleaned == QLatin1String( "l" ) )
     259                 :          0 :     return LabelMiddleLeft;
     260                 :          0 :   else if ( cleaned == QLatin1String( "r" ) )
     261                 :          0 :     return LabelMiddleRight;
     262                 :          0 :   else if ( cleaned == QLatin1String( "bl" ) )
     263                 :          0 :     return LabelBottomLeft;
     264                 :          0 :   else if ( cleaned == QLatin1String( "b" ) )
     265                 :          0 :     return LabelBottomMiddle;
     266                 :          0 :   else if ( cleaned == QLatin1String( "br" ) )
     267                 :          0 :     return LabelBottomRight;
     268                 :            : 
     269                 :          0 :   if ( ok )
     270                 :          0 :     *ok = false;
     271                 :          0 :   return LabelPointOnExterior;
     272                 :          0 : }
     273                 :            : 
     274                 :          0 : QgsGeometry QgsCallout::labelAnchorGeometry( const QRectF &rect, const double angle, LabelAnchorPoint anchor ) const
     275                 :            : {
     276                 :          0 :   QgsGeometry label;
     277                 :          0 :   switch ( anchor )
     278                 :            :   {
     279                 :            :     case LabelPointOnExterior:
     280                 :          0 :       label = QgsGeometry::fromRect( rect );
     281                 :          0 :       break;
     282                 :            : 
     283                 :            :     case LabelCentroid:
     284                 :          0 :       label = QgsGeometry::fromRect( rect ).centroid();
     285                 :          0 :       break;
     286                 :            : 
     287                 :            :     case LabelTopLeft:
     288                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.bottomLeft() ) );
     289                 :          0 :       break;
     290                 :            : 
     291                 :            :     case LabelTopMiddle:
     292                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.bottom() ) );
     293                 :          0 :       break;
     294                 :            : 
     295                 :            :     case LabelTopRight:
     296                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.bottomRight() ) );
     297                 :          0 :       break;
     298                 :            : 
     299                 :            :     case LabelMiddleLeft:
     300                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.left(), ( rect.top() + rect.bottom() ) / 2.0 ) );
     301                 :          0 :       break;
     302                 :            : 
     303                 :            :     case LabelMiddleRight:
     304                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.right(), ( rect.top() + rect.bottom() ) / 2.0 ) );
     305                 :          0 :       break;
     306                 :            : 
     307                 :            :     case LabelBottomLeft:
     308                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.topLeft() ) );
     309                 :          0 :       break;
     310                 :            : 
     311                 :            :     case LabelBottomMiddle:
     312                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.top() ) );
     313                 :          0 :       break;
     314                 :            : 
     315                 :            :     case LabelBottomRight:
     316                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.topRight() ) );
     317                 :          0 :       break;
     318                 :            :   }
     319                 :            : 
     320                 :          0 :   label.rotate( angle, rect.topLeft() );
     321                 :          0 :   return label;
     322                 :          0 : }
     323                 :            : 
     324                 :          0 : QgsGeometry QgsCallout::calloutLabelPoint( const QRectF &rect, const double angle, QgsCallout::LabelAnchorPoint anchor, QgsRenderContext &context, const QgsCallout::QgsCalloutContext &calloutContext, bool &pinned ) const
     325                 :            : {
     326                 :          0 :   pinned = false;
     327                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::OriginX ) && dataDefinedProperties().isActive( QgsCallout::OriginY ) )
     328                 :            :   {
     329                 :          0 :     bool ok = false;
     330                 :          0 :     const double x = dataDefinedProperties().valueAsDouble( QgsCallout::OriginX, context.expressionContext(), 0, &ok );
     331                 :          0 :     if ( ok )
     332                 :            :     {
     333                 :          0 :       const double y = dataDefinedProperties().valueAsDouble( QgsCallout::OriginY, context.expressionContext(), 0, &ok );
     334                 :          0 :       if ( ok )
     335                 :            :       {
     336                 :          0 :         pinned = true;
     337                 :            :         // data defined label point, use it directly
     338                 :          0 :         QgsGeometry labelPoint = QgsGeometry::fromPointXY( QgsPointXY( x, y ) );
     339                 :            :         try
     340                 :            :         {
     341                 :          0 :           labelPoint.transform( calloutContext.originalFeatureToMapTransform( context ) );
     342                 :          0 :           labelPoint.transform( context.mapToPixel().transform() );
     343                 :          0 :         }
     344                 :            :         catch ( QgsCsException & )
     345                 :            :         {
     346                 :          0 :           return QgsGeometry();
     347                 :          0 :         }
     348                 :          0 :         return labelPoint;
     349                 :          0 :       }
     350                 :          0 :     }
     351                 :          0 :   }
     352                 :            : 
     353                 :          0 :   QgsGeometry label;
     354                 :          0 :   switch ( anchor )
     355                 :            :   {
     356                 :            :     case LabelPointOnExterior:
     357                 :          0 :       label = QgsGeometry::fromRect( rect );
     358                 :          0 :       break;
     359                 :            : 
     360                 :            :     case LabelCentroid:
     361                 :          0 :       label = QgsGeometry::fromRect( rect ).centroid();
     362                 :          0 :       break;
     363                 :            : 
     364                 :            :     case LabelTopLeft:
     365                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.bottomLeft() ) );
     366                 :          0 :       break;
     367                 :            : 
     368                 :            :     case LabelTopMiddle:
     369                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.bottom() ) );
     370                 :          0 :       break;
     371                 :            : 
     372                 :            :     case LabelTopRight:
     373                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.bottomRight() ) );
     374                 :          0 :       break;
     375                 :            : 
     376                 :            :     case LabelMiddleLeft:
     377                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.left(), ( rect.top() + rect.bottom() ) / 2.0 ) );
     378                 :          0 :       break;
     379                 :            : 
     380                 :            :     case LabelMiddleRight:
     381                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.right(), ( rect.top() + rect.bottom() ) / 2.0 ) );
     382                 :          0 :       break;
     383                 :            : 
     384                 :            :     case LabelBottomLeft:
     385                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.topLeft() ) );
     386                 :          0 :       break;
     387                 :            : 
     388                 :            :     case LabelBottomMiddle:
     389                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( ( rect.left() + rect.right() ) / 2.0, rect.top() ) );
     390                 :          0 :       break;
     391                 :            : 
     392                 :            :     case LabelBottomRight:
     393                 :          0 :       label = QgsGeometry::fromPointXY( QgsPointXY( rect.topRight() ) );
     394                 :          0 :       break;
     395                 :            :   }
     396                 :            : 
     397                 :          0 :   label.rotate( angle, rect.topLeft() );
     398                 :          0 :   return label;
     399                 :          0 : }
     400                 :            : 
     401                 :          0 : QgsGeometry QgsCallout::calloutLineToPart( const QgsGeometry &labelGeometry, const QgsAbstractGeometry *partGeometry, QgsRenderContext &context, const QgsCalloutContext &calloutContext, bool &pinned ) const
     402                 :            : {
     403                 :          0 :   pinned = false;
     404                 :          0 :   AnchorPoint anchor = anchorPoint();
     405                 :          0 :   const QgsAbstractGeometry *evaluatedPartAnchor = partGeometry;
     406                 :          0 :   std::unique_ptr< QgsAbstractGeometry > tempPartAnchor;
     407                 :            : 
     408                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::DestinationX ) && dataDefinedProperties().isActive( QgsCallout::DestinationY ) )
     409                 :            :   {
     410                 :          0 :     bool ok = false;
     411                 :          0 :     const double x = dataDefinedProperties().valueAsDouble( QgsCallout::DestinationX, context.expressionContext(), 0, &ok );
     412                 :          0 :     if ( ok )
     413                 :            :     {
     414                 :          0 :       const double y = dataDefinedProperties().valueAsDouble( QgsCallout::DestinationY, context.expressionContext(), 0, &ok );
     415                 :          0 :       if ( ok )
     416                 :            :       {
     417                 :          0 :         pinned = true;
     418                 :          0 :         tempPartAnchor = std::make_unique< QgsPoint >( QgsWkbTypes::Point, x, y );
     419                 :          0 :         evaluatedPartAnchor = tempPartAnchor.get();
     420                 :            :         try
     421                 :            :         {
     422                 :          0 :           tempPartAnchor->transform( calloutContext.originalFeatureToMapTransform( context ) );
     423                 :          0 :           tempPartAnchor->transform( context.mapToPixel().transform() );
     424                 :          0 :         }
     425                 :            :         catch ( QgsCsException & )
     426                 :            :         {
     427                 :          0 :           evaluatedPartAnchor = partGeometry;
     428                 :          0 :         }
     429                 :          0 :       }
     430                 :          0 :     }
     431                 :          0 :   }
     432                 :            : 
     433                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::AnchorPointPosition ) )
     434                 :            :   {
     435                 :          0 :     QString encodedAnchor = encodeAnchorPoint( anchor );
     436                 :          0 :     context.expressionContext().setOriginalValueVariable( encodedAnchor );
     437                 :          0 :     anchor = decodeAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::AnchorPointPosition, context.expressionContext(), encodedAnchor ) );
     438                 :          0 :   }
     439                 :            : 
     440                 :          0 :   QgsGeometry line;
     441                 :          0 :   QgsGeos labelGeos( labelGeometry.constGet() );
     442                 :            : 
     443                 :          0 :   switch ( QgsWkbTypes::geometryType( evaluatedPartAnchor->wkbType() ) )
     444                 :            :   {
     445                 :            :     case QgsWkbTypes::PointGeometry:
     446                 :            :     case QgsWkbTypes::LineGeometry:
     447                 :            :     {
     448                 :          0 :       line = labelGeos.shortestLine( evaluatedPartAnchor );
     449                 :          0 :       break;
     450                 :            :     }
     451                 :            : 
     452                 :            :     case QgsWkbTypes::PolygonGeometry:
     453                 :            :     {
     454                 :          0 :       if ( labelGeos.intersects( evaluatedPartAnchor ) )
     455                 :          0 :         return QgsGeometry();
     456                 :            : 
     457                 :            :       // ideally avoid this unwanted clone in future. For now we need it because poleOfInaccessibility/pointOnSurface are
     458                 :            :       // only available to QgsGeometry objects
     459                 :          0 :       QgsGeometry evaluatedPartAnchorGeom( evaluatedPartAnchor->clone() );
     460                 :          0 :       switch ( anchor )
     461                 :            :       {
     462                 :          0 :         case QgsCallout::PoleOfInaccessibility:
     463                 :          0 :           line = labelGeos.shortestLine( evaluatedPartAnchorGeom.poleOfInaccessibility( std::max( evaluatedPartAnchor->boundingBox().width(), evaluatedPartAnchor->boundingBox().height() ) / 20.0 ) ); // really rough (but quick) pole of inaccessibility
     464                 :          0 :           break;
     465                 :          0 :         case QgsCallout::PointOnSurface:
     466                 :          0 :           line = labelGeos.shortestLine( evaluatedPartAnchorGeom.pointOnSurface() );
     467                 :          0 :           break;
     468                 :            :         case QgsCallout::PointOnExterior:
     469                 :          0 :           line = labelGeos.shortestLine( evaluatedPartAnchor );
     470                 :          0 :           break;
     471                 :            :         case QgsCallout::Centroid:
     472                 :          0 :           line = labelGeos.shortestLine( evaluatedPartAnchorGeom.centroid() );
     473                 :          0 :           break;
     474                 :            :       }
     475                 :            :       break;
     476                 :          0 :     }
     477                 :            : 
     478                 :            :     case QgsWkbTypes::NullGeometry:
     479                 :            :     case QgsWkbTypes::UnknownGeometry:
     480                 :          0 :       return QgsGeometry(); // shouldn't even get here..
     481                 :            :   }
     482                 :          0 :   return line;
     483                 :          0 : }
     484                 :            : 
     485                 :            : //
     486                 :            : // QgsCallout::QgsCalloutContext
     487                 :            : //
     488                 :            : 
     489                 :          0 : QgsCoordinateTransform QgsCallout::QgsCalloutContext::originalFeatureToMapTransform( const QgsRenderContext &renderContext ) const
     490                 :            : {
     491                 :          0 :   if ( !mOriginalFeatureToMapTransform.isValid() )
     492                 :            :   {
     493                 :            :     // lazy initialization, only create if needed...
     494                 :          0 :     mOriginalFeatureToMapTransform = QgsCoordinateTransform( originalFeatureCrs, renderContext.coordinateTransform().destinationCrs(), renderContext.transformContext() );
     495                 :          0 :   }
     496                 :          0 :   return mOriginalFeatureToMapTransform;
     497                 :          0 : }
     498                 :            : 
     499                 :            : 
     500                 :            : //
     501                 :            : // QgsSimpleLineCallout
     502                 :            : //
     503                 :            : 
     504                 :          0 : QgsSimpleLineCallout::QgsSimpleLineCallout()
     505                 :          0 : {
     506                 :          0 :   mLineSymbol = std::make_unique< QgsLineSymbol >( QgsSymbolLayerList() << new QgsSimpleLineSymbolLayer( QColor( 60, 60, 60 ), .3 ) );
     507                 :            : 
     508                 :          0 : }
     509                 :            : 
     510                 :          0 : QgsSimpleLineCallout::~QgsSimpleLineCallout() = default;
     511                 :            : 
     512                 :          0 : QgsSimpleLineCallout::QgsSimpleLineCallout( const QgsSimpleLineCallout &other )
     513                 :          0 :   : QgsCallout( other )
     514                 :          0 :   , mLineSymbol( other.mLineSymbol ? other.mLineSymbol->clone() : nullptr )
     515                 :          0 :   , mMinCalloutLength( other.mMinCalloutLength )
     516                 :          0 :   , mMinCalloutLengthUnit( other.mMinCalloutLengthUnit )
     517                 :          0 :   , mMinCalloutLengthScale( other.mMinCalloutLengthScale )
     518                 :          0 :   , mOffsetFromAnchorDistance( other.mOffsetFromAnchorDistance )
     519                 :          0 :   , mOffsetFromAnchorUnit( other.mOffsetFromAnchorUnit )
     520                 :          0 :   , mOffsetFromAnchorScale( other.mOffsetFromAnchorScale )
     521                 :          0 :   , mOffsetFromLabelDistance( other.mOffsetFromLabelDistance )
     522                 :          0 :   , mOffsetFromLabelUnit( other.mOffsetFromLabelUnit )
     523                 :          0 :   , mOffsetFromLabelScale( other.mOffsetFromLabelScale )
     524                 :          0 :   , mDrawCalloutToAllParts( other.mDrawCalloutToAllParts )
     525                 :          0 : {
     526                 :            : 
     527                 :          0 : }
     528                 :            : 
     529                 :          0 : QgsCallout *QgsSimpleLineCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
     530                 :            : {
     531                 :          0 :   std::unique_ptr< QgsSimpleLineCallout > callout = std::make_unique< QgsSimpleLineCallout >();
     532                 :          0 :   callout->readProperties( properties, context );
     533                 :          0 :   return callout.release();
     534                 :          0 : }
     535                 :            : 
     536                 :          0 : QString QgsSimpleLineCallout::type() const
     537                 :            : {
     538                 :          0 :   return QStringLiteral( "simple" );
     539                 :            : }
     540                 :            : 
     541                 :          0 : QgsSimpleLineCallout *QgsSimpleLineCallout::clone() const
     542                 :            : {
     543                 :          0 :   return new QgsSimpleLineCallout( *this );
     544                 :          0 : }
     545                 :            : 
     546                 :          0 : QVariantMap QgsSimpleLineCallout::properties( const QgsReadWriteContext &context ) const
     547                 :            : {
     548                 :          0 :   QVariantMap props = QgsCallout::properties( context );
     549                 :            : 
     550                 :          0 :   if ( mLineSymbol )
     551                 :            :   {
     552                 :          0 :     props[ QStringLiteral( "lineSymbol" ) ] = QgsSymbolLayerUtils::symbolProperties( mLineSymbol.get() );
     553                 :          0 :   }
     554                 :          0 :   props[ QStringLiteral( "minLength" ) ] = mMinCalloutLength;
     555                 :          0 :   props[ QStringLiteral( "minLengthUnit" ) ] = QgsUnitTypes::encodeUnit( mMinCalloutLengthUnit );
     556                 :          0 :   props[ QStringLiteral( "minLengthMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mMinCalloutLengthScale );
     557                 :            : 
     558                 :          0 :   props[ QStringLiteral( "offsetFromAnchor" ) ] = mOffsetFromAnchorDistance;
     559                 :          0 :   props[ QStringLiteral( "offsetFromAnchorUnit" ) ] = QgsUnitTypes::encodeUnit( mOffsetFromAnchorUnit );
     560                 :          0 :   props[ QStringLiteral( "offsetFromAnchorMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetFromAnchorScale );
     561                 :          0 :   props[ QStringLiteral( "offsetFromLabel" ) ] = mOffsetFromLabelDistance;
     562                 :          0 :   props[ QStringLiteral( "offsetFromLabelUnit" ) ] = QgsUnitTypes::encodeUnit( mOffsetFromLabelUnit );
     563                 :          0 :   props[ QStringLiteral( "offsetFromLabelMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetFromLabelScale );
     564                 :            : 
     565                 :          0 :   props[ QStringLiteral( "drawToAllParts" ) ] = mDrawCalloutToAllParts;
     566                 :            : 
     567                 :          0 :   return props;
     568                 :          0 : }
     569                 :            : 
     570                 :          0 : void QgsSimpleLineCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext &context )
     571                 :            : {
     572                 :          0 :   QgsCallout::readProperties( props, context );
     573                 :            : 
     574                 :          0 :   const QString lineSymbolDef = props.value( QStringLiteral( "lineSymbol" ) ).toString();
     575                 :          0 :   QDomDocument doc( QStringLiteral( "symbol" ) );
     576                 :          0 :   doc.setContent( lineSymbolDef );
     577                 :          0 :   QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
     578                 :          0 :   std::unique_ptr< QgsLineSymbol > lineSymbol( QgsSymbolLayerUtils::loadSymbol< QgsLineSymbol >( symbolElem, context ) );
     579                 :          0 :   if ( lineSymbol )
     580                 :          0 :     mLineSymbol = std::move( lineSymbol );
     581                 :            : 
     582                 :          0 :   mMinCalloutLength = props.value( QStringLiteral( "minLength" ), 0 ).toDouble();
     583                 :          0 :   mMinCalloutLengthUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "minLengthUnit" ) ).toString() );
     584                 :          0 :   mMinCalloutLengthScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "minLengthMapUnitScale" ) ).toString() );
     585                 :            : 
     586                 :          0 :   mOffsetFromAnchorDistance = props.value( QStringLiteral( "offsetFromAnchor" ), 0 ).toDouble();
     587                 :          0 :   mOffsetFromAnchorUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "offsetFromAnchorUnit" ) ).toString() );
     588                 :          0 :   mOffsetFromAnchorScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "offsetFromAnchorMapUnitScale" ) ).toString() );
     589                 :          0 :   mOffsetFromLabelDistance = props.value( QStringLiteral( "offsetFromLabel" ), 0 ).toDouble();
     590                 :          0 :   mOffsetFromLabelUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "offsetFromLabelUnit" ) ).toString() );
     591                 :          0 :   mOffsetFromLabelScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "offsetFromLabelMapUnitScale" ) ).toString() );
     592                 :            : 
     593                 :          0 :   mDrawCalloutToAllParts = props.value( QStringLiteral( "drawToAllParts" ), false ).toBool();
     594                 :          0 : }
     595                 :            : 
     596                 :          0 : void QgsSimpleLineCallout::startRender( QgsRenderContext &context )
     597                 :            : {
     598                 :          0 :   QgsCallout::startRender( context );
     599                 :          0 :   if ( mLineSymbol )
     600                 :          0 :     mLineSymbol->startRender( context );
     601                 :          0 : }
     602                 :            : 
     603                 :          0 : void QgsSimpleLineCallout::stopRender( QgsRenderContext &context )
     604                 :            : {
     605                 :          0 :   QgsCallout::stopRender( context );
     606                 :          0 :   if ( mLineSymbol )
     607                 :          0 :     mLineSymbol->stopRender( context );
     608                 :          0 : }
     609                 :            : 
     610                 :          0 : QSet<QString> QgsSimpleLineCallout::referencedFields( const QgsRenderContext &context ) const
     611                 :            : {
     612                 :          0 :   QSet<QString> fields = QgsCallout::referencedFields( context );
     613                 :          0 :   if ( mLineSymbol )
     614                 :          0 :     fields.unite( mLineSymbol->usedAttributes( context ) );
     615                 :          0 :   return fields;
     616                 :          0 : }
     617                 :            : 
     618                 :          0 : QgsLineSymbol *QgsSimpleLineCallout::lineSymbol()
     619                 :            : {
     620                 :          0 :   return mLineSymbol.get();
     621                 :            : }
     622                 :            : 
     623                 :          0 : void QgsSimpleLineCallout::setLineSymbol( QgsLineSymbol *symbol )
     624                 :            : {
     625                 :          0 :   mLineSymbol.reset( symbol );
     626                 :          0 : }
     627                 :            : 
     628                 :          0 : void QgsSimpleLineCallout::draw( QgsRenderContext &context, const QRectF &rect, const double angle, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
     629                 :            : {
     630                 :          0 :   LabelAnchorPoint labelAnchor = labelAnchorPoint();
     631                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::LabelAnchorPointPosition ) )
     632                 :            :   {
     633                 :          0 :     QString encodedAnchor = encodeLabelAnchorPoint( labelAnchor );
     634                 :          0 :     context.expressionContext().setOriginalValueVariable( encodedAnchor );
     635                 :          0 :     labelAnchor = decodeLabelAnchorPoint( dataDefinedProperties().valueAsString( QgsCallout::LabelAnchorPointPosition, context.expressionContext(), encodedAnchor ) );
     636                 :          0 :   }
     637                 :            : 
     638                 :          0 :   bool originPinned = false;
     639                 :          0 :   const QgsGeometry label = calloutLabelPoint( rect, angle, labelAnchor, context, calloutContext, originPinned );
     640                 :          0 :   if ( label.isNull() )
     641                 :          0 :     return;
     642                 :            : 
     643                 :          0 :   auto drawCalloutLine = [this, &context, &calloutContext, &label, &rect, angle, &anchor, originPinned]( const QgsAbstractGeometry * partAnchor )
     644                 :            :   {
     645                 :          0 :     bool destinationPinned = false;
     646                 :          0 :     QgsGeometry line = calloutLineToPart( label, partAnchor, context, calloutContext, destinationPinned );
     647                 :          0 :     if ( line.isEmpty() )
     648                 :          0 :       return;
     649                 :            : 
     650                 :          0 :     const double lineLength = line.length();
     651                 :          0 :     if ( qgsDoubleNear( lineLength, 0 ) )
     652                 :          0 :       return;
     653                 :            : 
     654                 :          0 :     double minLength = mMinCalloutLength;
     655                 :          0 :     if ( dataDefinedProperties().isActive( QgsCallout::MinimumCalloutLength ) )
     656                 :            :     {
     657                 :          0 :       context.expressionContext().setOriginalValueVariable( minLength );
     658                 :          0 :       minLength = dataDefinedProperties().valueAsDouble( QgsCallout::MinimumCalloutLength, context.expressionContext(), minLength );
     659                 :          0 :     }
     660                 :          0 :     double minLengthPixels = context.convertToPainterUnits( minLength, mMinCalloutLengthUnit, mMinCalloutLengthScale );
     661                 :          0 :     if ( minLengthPixels > 0 && lineLength < minLengthPixels )
     662                 :          0 :       return; // too small!
     663                 :            : 
     664                 :          0 :     std::unique_ptr< QgsCurve > calloutCurve( createCalloutLine( qgsgeometry_cast< const QgsLineString * >( line.constGet() )->startPoint(),
     665                 :          0 :         qgsgeometry_cast< const QgsLineString * >( line.constGet() )->endPoint(), context, rect, angle, anchor, calloutContext ) );
     666                 :            : 
     667                 :          0 :     double offsetFromAnchor = mOffsetFromAnchorDistance;
     668                 :          0 :     if ( dataDefinedProperties().isActive( QgsCallout::OffsetFromAnchor ) )
     669                 :            :     {
     670                 :          0 :       context.expressionContext().setOriginalValueVariable( offsetFromAnchor );
     671                 :          0 :       offsetFromAnchor = dataDefinedProperties().valueAsDouble( QgsCallout::OffsetFromAnchor, context.expressionContext(), offsetFromAnchor );
     672                 :          0 :     }
     673                 :          0 :     const double offsetFromAnchorPixels = context.convertToPainterUnits( offsetFromAnchor, mOffsetFromAnchorUnit, mOffsetFromAnchorScale );
     674                 :            : 
     675                 :          0 :     double offsetFromLabel = mOffsetFromLabelDistance;
     676                 :          0 :     if ( dataDefinedProperties().isActive( QgsCallout::OffsetFromLabel ) )
     677                 :            :     {
     678                 :          0 :       context.expressionContext().setOriginalValueVariable( offsetFromLabel );
     679                 :          0 :       offsetFromLabel = dataDefinedProperties().valueAsDouble( QgsCallout::OffsetFromLabel, context.expressionContext(), offsetFromLabel );
     680                 :          0 :     }
     681                 :          0 :     const double offsetFromLabelPixels = context.convertToPainterUnits( offsetFromLabel, mOffsetFromLabelUnit, mOffsetFromLabelScale );
     682                 :          0 :     if ( offsetFromAnchorPixels > 0 || offsetFromLabelPixels > 0 )
     683                 :            :     {
     684                 :          0 :       calloutCurve.reset( calloutCurve->curveSubstring( offsetFromLabelPixels, calloutCurve->length() - offsetFromAnchorPixels ) );
     685                 :          0 :     }
     686                 :            : 
     687                 :          0 :     const QPolygonF points = calloutCurve->asQPolygonF();
     688                 :            : 
     689                 :          0 :     if ( points.empty() )
     690                 :          0 :       return;
     691                 :            : 
     692                 :          0 :     QgsCalloutPosition position;
     693                 :          0 :     position.setOrigin( context.mapToPixel().toMapCoordinates( points.at( 0 ).x(), points.at( 0 ).y() ).toQPointF() );
     694                 :          0 :     position.setOriginIsPinned( originPinned );
     695                 :          0 :     position.setDestination( context.mapToPixel().toMapCoordinates( points.constLast().x(), points.constLast().y() ).toQPointF() );
     696                 :          0 :     position.setDestinationIsPinned( destinationPinned );
     697                 :          0 :     calloutContext.addCalloutPosition( position );
     698                 :          0 : 
     699                 :          0 :     mLineSymbol->renderPolyline( points, nullptr, context );
     700                 :          0 :   };
     701                 :          0 : 
     702                 :          0 :   bool toAllParts = mDrawCalloutToAllParts;
     703                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::DrawCalloutToAllParts ) )
     704                 :            :   {
     705                 :          0 :     context.expressionContext().setOriginalValueVariable( toAllParts );
     706                 :          0 :     toAllParts = dataDefinedProperties().valueAsBool( QgsCallout::DrawCalloutToAllParts, context.expressionContext(), toAllParts );
     707                 :          0 :   }
     708                 :            : 
     709                 :          0 :   if ( calloutContext.allFeaturePartsLabeled || !toAllParts )
     710                 :          0 :     drawCalloutLine( anchor.constGet() );
     711                 :            :   else
     712                 :            :   {
     713                 :          0 :     for ( auto it = anchor.const_parts_begin(); it != anchor.const_parts_end(); ++it )
     714                 :          0 :       drawCalloutLine( *it );
     715                 :            :   }
     716                 :          0 : }
     717                 :            : 
     718                 :          0 : QgsCurve *QgsSimpleLineCallout::createCalloutLine( const QgsPoint &start, const QgsPoint &end, QgsRenderContext &, const QRectF &, const double, const QgsGeometry &, QgsCallout::QgsCalloutContext & ) const
     719                 :            : {
     720                 :          0 :   return new QgsLineString( start, end );
     721                 :          0 : }
     722                 :            : 
     723                 :            : //
     724                 :            : // QgsManhattanLineCallout
     725                 :            : //
     726                 :            : 
     727                 :          0 : QgsManhattanLineCallout::QgsManhattanLineCallout()
     728                 :          0 : {
     729                 :          0 : }
     730                 :            : 
     731                 :          0 : QgsManhattanLineCallout::QgsManhattanLineCallout( const QgsManhattanLineCallout &other )
     732                 :          0 :   : QgsSimpleLineCallout( other )
     733                 :          0 : {
     734                 :            : 
     735                 :          0 : }
     736                 :            : 
     737                 :            : 
     738                 :          0 : QgsCallout *QgsManhattanLineCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
     739                 :            : {
     740                 :          0 :   std::unique_ptr< QgsManhattanLineCallout > callout = std::make_unique< QgsManhattanLineCallout >();
     741                 :          0 :   callout->readProperties( properties, context );
     742                 :          0 :   return callout.release();
     743                 :          0 : }
     744                 :            : 
     745                 :          0 : QString QgsManhattanLineCallout::type() const
     746                 :            : {
     747                 :          0 :   return QStringLiteral( "manhattan" );
     748                 :            : }
     749                 :            : 
     750                 :          0 : QgsManhattanLineCallout *QgsManhattanLineCallout::clone() const
     751                 :            : {
     752                 :          0 :   return new QgsManhattanLineCallout( *this );
     753                 :          0 : }
     754                 :            : 
     755                 :          0 : QgsCurve *QgsManhattanLineCallout::createCalloutLine( const QgsPoint &start, const QgsPoint &end, QgsRenderContext &, const QRectF &, const double, const QgsGeometry &, QgsCallout::QgsCalloutContext & ) const
     756                 :            : {
     757                 :          0 :   QgsPoint mid1 = QgsPoint( start.x(), end.y() );
     758                 :          0 :   return new QgsLineString( QVector< QgsPoint >() << start << mid1 << end );
     759                 :          0 : }
     760                 :            : 
     761                 :            : 
     762                 :            : //
     763                 :            : // QgsCurvedLineCallout
     764                 :            : //
     765                 :            : 
     766                 :          0 : QgsCurvedLineCallout::QgsCurvedLineCallout()
     767                 :          0 : {
     768                 :          0 : }
     769                 :            : 
     770                 :          0 : QgsCurvedLineCallout::QgsCurvedLineCallout( const QgsCurvedLineCallout &other )
     771                 :          0 :   : QgsSimpleLineCallout( other )
     772                 :          0 :   , mOrientation( other.mOrientation )
     773                 :          0 :   , mCurvature( other.mCurvature )
     774                 :          0 : {
     775                 :            : 
     776                 :          0 : }
     777                 :            : 
     778                 :          0 : QgsCallout *QgsCurvedLineCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
     779                 :            : {
     780                 :          0 :   std::unique_ptr< QgsCurvedLineCallout > callout = std::make_unique< QgsCurvedLineCallout >();
     781                 :          0 :   callout->readProperties( properties, context );
     782                 :            : 
     783                 :          0 :   callout->setCurvature( properties.value( QStringLiteral( "curvature" ), 0.1 ).toDouble() );
     784                 :          0 :   callout->setOrientation( decodeOrientation( properties.value( QStringLiteral( "orientation" ), QStringLiteral( "auto" ) ).toString() ) );
     785                 :            : 
     786                 :          0 :   return callout.release();
     787                 :          0 : }
     788                 :            : 
     789                 :          0 : QString QgsCurvedLineCallout::type() const
     790                 :            : {
     791                 :          0 :   return QStringLiteral( "curved" );
     792                 :            : }
     793                 :            : 
     794                 :          0 : QgsCurvedLineCallout *QgsCurvedLineCallout::clone() const
     795                 :            : {
     796                 :          0 :   return new QgsCurvedLineCallout( *this );
     797                 :          0 : }
     798                 :            : 
     799                 :          0 : QVariantMap QgsCurvedLineCallout::properties( const QgsReadWriteContext &context ) const
     800                 :            : {
     801                 :          0 :   QVariantMap props = QgsSimpleLineCallout::properties( context );
     802                 :          0 :   props.insert( QStringLiteral( "curvature" ), mCurvature );
     803                 :          0 :   props.insert( QStringLiteral( "orientation" ), encodeOrientation( mOrientation ) );
     804                 :          0 :   return props;
     805                 :          0 : }
     806                 :            : 
     807                 :          0 : QgsCurve *QgsCurvedLineCallout::createCalloutLine( const QgsPoint &start, const QgsPoint &end, QgsRenderContext &context, const QRectF &rect, const double, const QgsGeometry &, QgsCallout::QgsCalloutContext & ) const
     808                 :            : {
     809                 :          0 :   double curvature = mCurvature * 100;
     810                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::Curvature ) )
     811                 :            :   {
     812                 :          0 :     context.expressionContext().setOriginalValueVariable( curvature );
     813                 :          0 :     curvature = dataDefinedProperties().valueAsDouble( QgsCallout::Curvature, context.expressionContext(), curvature );
     814                 :          0 :   }
     815                 :            : 
     816                 :          0 :   Orientation orientation = mOrientation;
     817                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::Orientation ) )
     818                 :            :   {
     819                 :          0 :     bool ok = false;
     820                 :          0 :     const QString orientationString = dataDefinedProperties().property( QgsCallout::Orientation ).valueAsString( context.expressionContext(), QString(), &ok );
     821                 :          0 :     if ( ok )
     822                 :            :     {
     823                 :          0 :       orientation = decodeOrientation( orientationString );
     824                 :          0 :     }
     825                 :          0 :   }
     826                 :            : 
     827                 :          0 :   if ( orientation == Automatic )
     828                 :            :   {
     829                 :            :     // to calculate automatically the best curve orientation, we first check which side of the label bounding box
     830                 :            :     // the callout origin is nearest to
     831                 :          0 :     switch ( QgsGeometryUtils::closestSideOfRectangle( rect.right(), rect.bottom(), rect.left(), rect.top(), start.x(), start.y() ) )
     832                 :            :     {
     833                 :            :       case 1:
     834                 :            :         // closest to bottom
     835                 :          0 :         if ( qgsDoubleNear( end.x(), start.x() ) )
     836                 :            :         {
     837                 :            :           // if vertical line, we bend depending on whether the line sits towards the left or right side of the label
     838                 :          0 :           if ( start.x() < ( rect.left() + 0.5 * rect.width() ) )
     839                 :          0 :             orientation = CounterClockwise;
     840                 :            :           else
     841                 :          0 :             orientation = Clockwise;
     842                 :          0 :         }
     843                 :          0 :         else if ( end.x() > start.x() )
     844                 :          0 :           orientation = CounterClockwise;
     845                 :            :         else
     846                 :          0 :           orientation = Clockwise;
     847                 :          0 :         break;
     848                 :            : 
     849                 :            :       case 2:
     850                 :            :         // closest to bottom-right
     851                 :          0 :         if ( end.x() < start.x() )
     852                 :          0 :           orientation = Clockwise;
     853                 :          0 :         else if ( end.y() < start.y() )
     854                 :          0 :           orientation = CounterClockwise;
     855                 :          0 :         else if ( end.x() - start.x() < end.y() - start.y() )
     856                 :          0 :           orientation = Clockwise;
     857                 :            :         else
     858                 :          0 :           orientation = CounterClockwise;
     859                 :          0 :         break;
     860                 :            : 
     861                 :            :       case 3:
     862                 :            :         // closest to right
     863                 :          0 :         if ( qgsDoubleNear( end.y(), start.y() ) )
     864                 :            :         {
     865                 :            :           // if horizontal line, we bend depending on whether the line sits towards the top or bottom side of the label
     866                 :          0 :           if ( start.y() < ( rect.top() + 0.5 * rect.height() ) )
     867                 :          0 :             orientation = Clockwise;
     868                 :            :           else
     869                 :          0 :             orientation = CounterClockwise;
     870                 :          0 :         }
     871                 :          0 :         else if ( end.y() < start.y() )
     872                 :          0 :           orientation = CounterClockwise;
     873                 :            :         else
     874                 :          0 :           orientation = Clockwise;
     875                 :          0 :         break;
     876                 :            : 
     877                 :            :       case 4:
     878                 :            :         // closest to top-right
     879                 :          0 :         if ( end.x() < start.x() )
     880                 :          0 :           orientation = CounterClockwise;
     881                 :          0 :         else if ( end.y() > start.y() )
     882                 :          0 :           orientation = Clockwise;
     883                 :          0 :         else if ( end.x() - start.x() < start.y() - end.y() )
     884                 :          0 :           orientation = CounterClockwise;
     885                 :            :         else
     886                 :          0 :           orientation = Clockwise;
     887                 :          0 :         break;
     888                 :            : 
     889                 :            :       case 5:
     890                 :            :         // closest to top
     891                 :          0 :         if ( qgsDoubleNear( end.x(), start.x() ) )
     892                 :            :         {
     893                 :            :           // if vertical line, we bend depending on whether the line sits towards the left or right side of the label
     894                 :          0 :           if ( start.x() < ( rect.left() + 0.5 * rect.width() ) )
     895                 :          0 :             orientation = Clockwise;
     896                 :            :           else
     897                 :          0 :             orientation = CounterClockwise;
     898                 :          0 :         }
     899                 :          0 :         else if ( end.x() < start.x() )
     900                 :          0 :           orientation = CounterClockwise;
     901                 :            :         else
     902                 :          0 :           orientation = Clockwise;
     903                 :          0 :         break;
     904                 :            : 
     905                 :            :       case 6:
     906                 :            :         // closest to top-left
     907                 :          0 :         if ( end.x() > start.x() )
     908                 :          0 :           orientation = Clockwise;
     909                 :          0 :         else if ( end.y() > start.y() )
     910                 :          0 :           orientation = CounterClockwise;
     911                 :          0 :         else if ( start.x() - end.x() < start.y() - end.y() )
     912                 :          0 :           orientation = Clockwise;
     913                 :            :         else
     914                 :          0 :           orientation = CounterClockwise;
     915                 :          0 :         break;
     916                 :            : 
     917                 :            :       case 7:
     918                 :            :         //closest to left
     919                 :          0 :         if ( qgsDoubleNear( end.y(), start.y() ) )
     920                 :            :         {
     921                 :            :           // if horizontal line, we bend depending on whether the line sits towards the top or bottom side of the label
     922                 :          0 :           if ( start.y() < ( rect.top() + 0.5 * rect.height() ) )
     923                 :          0 :             orientation = CounterClockwise;
     924                 :            :           else
     925                 :          0 :             orientation = Clockwise;
     926                 :          0 :         }
     927                 :          0 :         else if ( end.y() > start.y() )
     928                 :          0 :           orientation = CounterClockwise;
     929                 :            :         else
     930                 :          0 :           orientation = Clockwise;
     931                 :          0 :         break;
     932                 :            : 
     933                 :            :       case 8:
     934                 :            :         //closest to bottom-left
     935                 :          0 :         if ( end.x() > start.x() )
     936                 :          0 :           orientation = CounterClockwise;
     937                 :          0 :         else if ( end.y() < start.y() )
     938                 :          0 :           orientation = Clockwise;
     939                 :          0 :         else if ( start.x() - end.x() < end.y() - start.y() )
     940                 :          0 :           orientation = CounterClockwise;
     941                 :            :         else
     942                 :          0 :           orientation = Clockwise;
     943                 :          0 :         break;
     944                 :            :     }
     945                 :          0 :   }
     946                 :            : 
     947                 :            :   // turn the line into a curved line. We do this by creating a circular string from the callout line's
     948                 :            :   // start to end point, where the curve point is in the middle of the callout line and perpendicularly offset
     949                 :            :   // by a proportion of the overall callout line length
     950                 :          0 :   const double distance = ( orientation == Clockwise ? 1 : -1 ) * start.distance( end ) * curvature / 100.0;
     951                 :            :   double midX, midY;
     952                 :          0 :   QgsGeometryUtils::perpendicularOffsetPointAlongSegment( start.x(), start.y(), end.x(), end.y(), 0.5, distance, &midX, &midY );
     953                 :            : 
     954                 :          0 :   return new QgsCircularString( start, QgsPoint( midX, midY ), end );
     955                 :          0 : }
     956                 :            : 
     957                 :          0 : QgsCurvedLineCallout::Orientation QgsCurvedLineCallout::decodeOrientation( const QString &string )
     958                 :            : {
     959                 :          0 :   const QString cleaned = string.toLower().trimmed();
     960                 :          0 :   if ( cleaned == QLatin1String( "auto" ) )
     961                 :          0 :     return Automatic;
     962                 :          0 :   if ( cleaned == QLatin1String( "clockwise" ) )
     963                 :          0 :     return Clockwise;
     964                 :          0 :   if ( cleaned == QLatin1String( "counterclockwise" ) )
     965                 :          0 :     return CounterClockwise;
     966                 :          0 :   return Automatic;
     967                 :          0 : }
     968                 :            : 
     969                 :          0 : QString QgsCurvedLineCallout::encodeOrientation( QgsCurvedLineCallout::Orientation orientation )
     970                 :            : {
     971                 :          0 :   switch ( orientation )
     972                 :            :   {
     973                 :            :     case QgsCurvedLineCallout::Automatic:
     974                 :          0 :       return QStringLiteral( "auto" );
     975                 :            :     case QgsCurvedLineCallout::Clockwise:
     976                 :          0 :       return QStringLiteral( "clockwise" );
     977                 :            :     case QgsCurvedLineCallout::CounterClockwise:
     978                 :          0 :       return QStringLiteral( "counterclockwise" );
     979                 :            :   }
     980                 :          0 :   return QString();
     981                 :          0 : }
     982                 :            : 
     983                 :          0 : QgsCurvedLineCallout::Orientation QgsCurvedLineCallout::orientation() const
     984                 :            : {
     985                 :          0 :   return mOrientation;
     986                 :            : }
     987                 :            : 
     988                 :          0 : void QgsCurvedLineCallout::setOrientation( Orientation orientation )
     989                 :            : {
     990                 :          0 :   mOrientation = orientation;
     991                 :          0 : }
     992                 :            : 
     993                 :          0 : double QgsCurvedLineCallout::curvature() const
     994                 :            : {
     995                 :          0 :   return mCurvature;
     996                 :            : }
     997                 :            : 
     998                 :          0 : void QgsCurvedLineCallout::setCurvature( double curvature )
     999                 :            : {
    1000                 :          0 :   mCurvature = curvature;
    1001                 :          0 : }
    1002                 :            : 
    1003                 :            : 
    1004                 :            : 
    1005                 :            : //
    1006                 :            : // QgsBalloonCallout
    1007                 :            : //
    1008                 :            : 
    1009                 :          0 : QgsBalloonCallout::QgsBalloonCallout()
    1010                 :          0 : {
    1011                 :          0 :   mFillSymbol = std::make_unique< QgsFillSymbol >( QgsSymbolLayerList() << new QgsSimpleFillSymbolLayer( QColor( 255, 200, 60 ) ) );
    1012                 :          0 : }
    1013                 :            : 
    1014                 :          0 : QgsBalloonCallout::~QgsBalloonCallout() = default;
    1015                 :            : 
    1016                 :          0 : QgsBalloonCallout::QgsBalloonCallout( const QgsBalloonCallout &other )
    1017                 :          0 :   : QgsCallout( other )
    1018                 :          0 :   , mFillSymbol( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr )
    1019                 :          0 :   , mOffsetFromAnchorDistance( other.mOffsetFromAnchorDistance )
    1020                 :          0 :   , mOffsetFromAnchorUnit( other.mOffsetFromAnchorUnit )
    1021                 :          0 :   , mOffsetFromAnchorScale( other.mOffsetFromAnchorScale )
    1022                 :          0 :   , mMargins( other.mMargins )
    1023                 :          0 :   , mMarginUnit( other.mMarginUnit )
    1024                 :          0 :   , mWedgeWidth( other.mWedgeWidth )
    1025                 :          0 :   , mWedgeWidthUnit( other.mWedgeWidthUnit )
    1026                 :          0 :   , mWedgeWidthScale( other.mWedgeWidthScale )
    1027                 :          0 : {
    1028                 :            : 
    1029                 :          0 : }
    1030                 :            : 
    1031                 :          0 : QgsCallout *QgsBalloonCallout::create( const QVariantMap &properties, const QgsReadWriteContext &context )
    1032                 :            : {
    1033                 :          0 :   std::unique_ptr< QgsBalloonCallout > callout = std::make_unique< QgsBalloonCallout >();
    1034                 :          0 :   callout->readProperties( properties, context );
    1035                 :          0 :   return callout.release();
    1036                 :          0 : }
    1037                 :            : 
    1038                 :          0 : QString QgsBalloonCallout::type() const
    1039                 :            : {
    1040                 :          0 :   return QStringLiteral( "balloon" );
    1041                 :            : }
    1042                 :            : 
    1043                 :          0 : QgsBalloonCallout *QgsBalloonCallout::clone() const
    1044                 :            : {
    1045                 :          0 :   return new QgsBalloonCallout( *this );
    1046                 :          0 : }
    1047                 :            : 
    1048                 :          0 : QVariantMap QgsBalloonCallout::properties( const QgsReadWriteContext &context ) const
    1049                 :            : {
    1050                 :          0 :   QVariantMap props = QgsCallout::properties( context );
    1051                 :            : 
    1052                 :          0 :   if ( mFillSymbol )
    1053                 :            :   {
    1054                 :          0 :     props[ QStringLiteral( "fillSymbol" ) ] = QgsSymbolLayerUtils::symbolProperties( mFillSymbol.get() );
    1055                 :          0 :   }
    1056                 :            : 
    1057                 :          0 :   props[ QStringLiteral( "offsetFromAnchor" ) ] = mOffsetFromAnchorDistance;
    1058                 :          0 :   props[ QStringLiteral( "offsetFromAnchorUnit" ) ] = QgsUnitTypes::encodeUnit( mOffsetFromAnchorUnit );
    1059                 :          0 :   props[ QStringLiteral( "offsetFromAnchorMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetFromAnchorScale );
    1060                 :            : 
    1061                 :          0 :   props[ QStringLiteral( "margins" ) ] = mMargins.toString();
    1062                 :          0 :   props[ QStringLiteral( "marginsUnit" ) ] = QgsUnitTypes::encodeUnit( mMarginUnit );
    1063                 :          0 : 
    1064                 :          0 :   props[ QStringLiteral( "wedgeWidth" ) ] = mWedgeWidth;
    1065                 :          0 :   props[ QStringLiteral( "wedgeWidthUnit" ) ] = QgsUnitTypes::encodeUnit( mWedgeWidthUnit );
    1066                 :          0 :   props[ QStringLiteral( "wedgeWidthMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mWedgeWidthScale );
    1067                 :            : 
    1068                 :          0 :   return props;
    1069                 :          0 : }
    1070                 :          0 : 
    1071                 :          0 : void QgsBalloonCallout::readProperties( const QVariantMap &props, const QgsReadWriteContext &context )
    1072                 :            : {
    1073                 :          0 :   QgsCallout::readProperties( props, context );
    1074                 :            : 
    1075                 :          0 :   const QString fillSymbolDef = props.value( QStringLiteral( "fillSymbol" ) ).toString();
    1076                 :          0 :   QDomDocument doc( QStringLiteral( "symbol" ) );
    1077                 :          0 :   doc.setContent( fillSymbolDef );
    1078                 :          0 :   QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
    1079                 :          0 :   std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( symbolElem, context ) );
    1080                 :          0 :   if ( fillSymbol )
    1081                 :          0 :     mFillSymbol = std::move( fillSymbol );
    1082                 :            : 
    1083                 :          0 :   mOffsetFromAnchorDistance = props.value( QStringLiteral( "offsetFromAnchor" ), 0 ).toDouble();
    1084                 :          0 :   mOffsetFromAnchorUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "offsetFromAnchorUnit" ) ).toString() );
    1085                 :          0 :   mOffsetFromAnchorScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "offsetFromAnchorMapUnitScale" ) ).toString() );
    1086                 :            : 
    1087                 :          0 :   mMargins = QgsMargins::fromString( props.value( QStringLiteral( "margins" ) ).toString() );
    1088                 :          0 :   mMarginUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "marginsUnit" ) ).toString() );
    1089                 :            : 
    1090                 :          0 :   mWedgeWidth = props.value( QStringLiteral( "wedgeWidth" ), 2.64 ).toDouble();
    1091                 :          0 :   mWedgeWidthUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "wedgeWidthUnit" ) ).toString() );
    1092                 :          0 :   mWedgeWidthScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "wedgeWidthMapUnitScale" ) ).toString() );
    1093                 :          0 : }
    1094                 :            : 
    1095                 :          0 : void QgsBalloonCallout::startRender( QgsRenderContext &context )
    1096                 :            : {
    1097                 :          0 :   QgsCallout::startRender( context );
    1098                 :          0 :   if ( mFillSymbol )
    1099                 :          0 :     mFillSymbol->startRender( context );
    1100                 :          0 : }
    1101                 :            : 
    1102                 :          0 : void QgsBalloonCallout::stopRender( QgsRenderContext &context )
    1103                 :            : {
    1104                 :          0 :   QgsCallout::stopRender( context );
    1105                 :          0 :   if ( mFillSymbol )
    1106                 :          0 :     mFillSymbol->stopRender( context );
    1107                 :          0 : }
    1108                 :            : 
    1109                 :          0 : QSet<QString> QgsBalloonCallout::referencedFields( const QgsRenderContext &context ) const
    1110                 :            : {
    1111                 :          0 :   QSet<QString> fields = QgsCallout::referencedFields( context );
    1112                 :          0 :   if ( mFillSymbol )
    1113                 :          0 :     fields.unite( mFillSymbol->usedAttributes( context ) );
    1114                 :          0 :   return fields;
    1115                 :          0 : }
    1116                 :            : 
    1117                 :          0 : QgsFillSymbol *QgsBalloonCallout::fillSymbol()
    1118                 :            : {
    1119                 :          0 :   return mFillSymbol.get();
    1120                 :            : }
    1121                 :            : 
    1122                 :          0 : void QgsBalloonCallout::setFillSymbol( QgsFillSymbol *symbol )
    1123                 :            : {
    1124                 :          0 :   mFillSymbol.reset( symbol );
    1125                 :          0 : }
    1126                 :            : 
    1127                 :          0 : void QgsBalloonCallout::draw( QgsRenderContext &context, const QRectF &rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
    1128                 :            : {
    1129                 :          0 :   bool destinationIsPinned = false;
    1130                 :          0 :   QgsGeometry line = calloutLineToPart( QgsGeometry::fromRect( rect ), anchor.constGet(), context, calloutContext, destinationIsPinned );
    1131                 :            : 
    1132                 :          0 :   double offsetFromAnchor = mOffsetFromAnchorDistance;
    1133                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::OffsetFromAnchor ) )
    1134                 :            :   {
    1135                 :          0 :     context.expressionContext().setOriginalValueVariable( offsetFromAnchor );
    1136                 :          0 :     offsetFromAnchor = dataDefinedProperties().valueAsDouble( QgsCallout::OffsetFromAnchor, context.expressionContext(), offsetFromAnchor );
    1137                 :          0 :   }
    1138                 :          0 :   const double offsetFromAnchorPixels = context.convertToPainterUnits( offsetFromAnchor, mOffsetFromAnchorUnit, mOffsetFromAnchorScale );
    1139                 :            : 
    1140                 :          0 :   if ( offsetFromAnchorPixels > 0 )
    1141                 :            :   {
    1142                 :          0 :     if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( line.constGet() ) )
    1143                 :            :     {
    1144                 :          0 :       line = QgsGeometry( ls->curveSubstring( 0, ls->length() - offsetFromAnchorPixels ) );
    1145                 :          0 :     }
    1146                 :          0 :   }
    1147                 :            : 
    1148                 :          0 :   QgsPointXY destination;
    1149                 :          0 :   QgsPointXY origin;
    1150                 :          0 :   if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( line.constGet() ) )
    1151                 :            :   {
    1152                 :          0 :     origin = ls->startPoint();
    1153                 :          0 :     destination = ls->endPoint();
    1154                 :          0 :   }
    1155                 :            :   else
    1156                 :            :   {
    1157                 :          0 :     destination = QgsPointXY( rect.center() );
    1158                 :            :   }
    1159                 :            : 
    1160                 :          0 :   const QPolygonF points = getPoints( context, destination, rect );
    1161                 :          0 :   if ( points.empty() )
    1162                 :          0 :     return;
    1163                 :            : 
    1164                 :          0 :   if ( !origin.isEmpty() )
    1165                 :            :   {
    1166                 :          0 :     QgsCalloutPosition position;
    1167                 :          0 :     position.setOrigin( context.mapToPixel().toMapCoordinates( origin.x(), origin.y() ).toQPointF() );
    1168                 :          0 :     position.setOriginIsPinned( false );
    1169                 :          0 :     position.setDestination( context.mapToPixel().toMapCoordinates( destination.x(), destination.y() ).toQPointF() );
    1170                 :          0 :     position.setDestinationIsPinned( destinationIsPinned );
    1171                 :          0 :     calloutContext.addCalloutPosition( position );
    1172                 :          0 :   }
    1173                 :            : 
    1174                 :          0 :   mFillSymbol->renderPolygon( points, nullptr, nullptr, context );
    1175                 :          0 : }
    1176                 :            : 
    1177                 :          0 : QPolygonF QgsBalloonCallout::getPoints( QgsRenderContext &context, QgsPointXY origin, QRectF rect ) const
    1178                 :            : {
    1179                 :          0 :   double segmentPointWidth = mWedgeWidth;
    1180                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::WedgeWidth ) )
    1181                 :            :   {
    1182                 :          0 :     context.expressionContext().setOriginalValueVariable( segmentPointWidth );
    1183                 :          0 :     segmentPointWidth = dataDefinedProperties().valueAsDouble( QgsCallout::WedgeWidth, context.expressionContext(), segmentPointWidth );
    1184                 :          0 :   }
    1185                 :          0 :   segmentPointWidth = context.convertToPainterUnits( segmentPointWidth, mWedgeWidthUnit, mWedgeWidthScale );
    1186                 :            : 
    1187                 :          0 :   double left = mMargins.left();
    1188                 :          0 :   double right = mMargins.right();
    1189                 :          0 :   double top = mMargins.top();
    1190                 :          0 :   double bottom = mMargins.bottom();
    1191                 :            : 
    1192                 :          0 :   if ( dataDefinedProperties().isActive( QgsCallout::Margins ) )
    1193                 :            :   {
    1194                 :          0 :     const QVariant value = dataDefinedProperties().value( QgsCallout::Margins, context.expressionContext() );
    1195                 :          0 :     if ( !value.isNull() )
    1196                 :            :     {
    1197                 :          0 :       if ( value.type() == QVariant::List )
    1198                 :            :       {
    1199                 :          0 :         const QVariantList list = value.toList();
    1200                 :          0 :         if ( list.size() == 4 )
    1201                 :            :         {
    1202                 :          0 :           bool topOk = false;
    1203                 :          0 :           bool rightOk = false;
    1204                 :          0 :           bool bottomOk = false;
    1205                 :          0 :           bool leftOk = false;
    1206                 :          0 :           double evaluatedTop = list.at( 0 ).toDouble( &topOk );
    1207                 :          0 :           double evaluatedRight = list.at( 1 ).toDouble( &rightOk );
    1208                 :          0 :           double evaluatedBottom = list.at( 2 ).toDouble( &bottomOk );
    1209                 :          0 :           double evaluatedLeft = list.at( 3 ).toDouble( &leftOk );
    1210                 :          0 :           if ( topOk && rightOk && bottomOk && leftOk )
    1211                 :            :           {
    1212                 :          0 :             left = evaluatedLeft;
    1213                 :          0 :             top = evaluatedTop;
    1214                 :          0 :             right = evaluatedRight;
    1215                 :          0 :             bottom = evaluatedBottom;
    1216                 :          0 :           }
    1217                 :          0 :         }
    1218                 :          0 :       }
    1219                 :            :       else
    1220                 :            :       {
    1221                 :          0 :         const QStringList list = value.toString().trimmed().split( ',' );
    1222                 :          0 :         if ( list.count() == 4 )
    1223                 :            :         {
    1224                 :          0 :           bool topOk = false;
    1225                 :          0 :           bool rightOk = false;
    1226                 :          0 :           bool bottomOk = false;
    1227                 :          0 :           bool leftOk = false;
    1228                 :          0 :           double evaluatedTop = list.at( 0 ).toDouble( &topOk );
    1229                 :          0 :           double evaluatedRight = list.at( 1 ).toDouble( &rightOk );
    1230                 :          0 :           double evaluatedBottom = list.at( 2 ).toDouble( &bottomOk );
    1231                 :          0 :           double evaluatedLeft = list.at( 3 ).toDouble( &leftOk );
    1232                 :          0 :           if ( topOk && rightOk && bottomOk && leftOk )
    1233                 :            :           {
    1234                 :          0 :             left = evaluatedLeft;
    1235                 :          0 :             top = evaluatedTop;
    1236                 :          0 :             right = evaluatedRight;
    1237                 :          0 :             bottom = evaluatedBottom;
    1238                 :          0 :           }
    1239                 :          0 :         }
    1240                 :          0 :       }
    1241                 :          0 :     }
    1242                 :          0 :   }
    1243                 :            : 
    1244                 :          0 :   const double marginLeft = context.convertToPainterUnits( left, mMarginUnit );
    1245                 :          0 :   const double marginRight = context.convertToPainterUnits( right, mMarginUnit );
    1246                 :          0 :   const double marginTop = context.convertToPainterUnits( top, mMarginUnit );
    1247                 :          0 :   const double marginBottom = context.convertToPainterUnits( bottom, mMarginUnit );
    1248                 :            : 
    1249                 :          0 :   const QRectF expandedRect( rect.left() - marginLeft, rect.top() + marginBottom,
    1250                 :          0 :                              rect.width() + marginLeft + marginRight,
    1251                 :          0 :                              rect.height() - marginTop - marginBottom );
    1252                 :            : 
    1253                 :            :   // IMPORTANT -- check for degenerate height is >=0, because QRectF are not normalized and we are using painter
    1254                 :            :   // coordinates with descending vertical axis!
    1255                 :          0 :   if ( expandedRect.width() <= 0 || expandedRect.height() >= 0 )
    1256                 :          0 :     return QPolygonF();
    1257                 :            : 
    1258                 :          0 :   return QgsShapeGenerator::createBalloon( origin, expandedRect, segmentPointWidth );
    1259                 :          0 : }

Generated by: LCOV version 1.14