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

Generated by: LCOV version 1.14