QGIS API Documentation  3.23.0-Master (eb871beae0)
qgslayoutitemmapgrid.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgslayoutitemmapgrid.cpp
3  ----------------------
4  begin : October 2017
5  copyright : (C) 2017 by Marco Hugentobler, Nyall Dawson
6  email : marco dot hugentobler at sourcepole dot ch
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 "qgsmessagelog.h"
19 #include "qgslayoutitemmapgrid.h"
20 #include "qgslayoututils.h"
21 #include "qgsclipper.h"
22 #include "qgsgeometry.h"
23 #include "qgslayoutitemmap.h"
24 #include "qgslayout.h"
25 #include "qgsmapsettings.h"
26 #include "qgspathresolver.h"
27 #include "qgsreadwritecontext.h"
28 #include "qgsrendercontext.h"
29 #include "qgssymbollayerutils.h"
30 #include "qgssymbol.h"
32 #include "qgslogger.h"
33 #include "qgsfontutils.h"
34 #include "qgsexpressioncontext.h"
35 #include "qgsexception.h"
36 #include "qgssettings.h"
37 #include "qgscoordinateformatter.h"
38 #include "qgsstyleentityvisitor.h"
39 #include "qgstextrenderer.h"
40 #include "qgslinesymbol.h"
41 #include "qgsmarkersymbol.h"
42 
43 #include <QVector2D>
44 #include <math.h>
45 
46 #include <QPainter>
47 #include <QPen>
48 
49 #define MAX_GRID_LINES 1000 //maximum number of horizontal or vertical grid lines to draw
50 
53 {
54 
55 }
56 
58 {
60 }
61 
62 void QgsLayoutItemMapGridStack::removeGrid( const QString &gridId )
63 {
65 }
66 
67 void QgsLayoutItemMapGridStack::moveGridUp( const QString &gridId )
68 {
70 }
71 
72 void QgsLayoutItemMapGridStack::moveGridDown( const QString &gridId )
73 {
75 }
76 
78 {
80  return qobject_cast<QgsLayoutItemMapGrid *>( item );
81 }
82 
84 {
86  return qobject_cast<QgsLayoutItemMapGrid *>( item );
87 }
88 
89 QList<QgsLayoutItemMapGrid *> QgsLayoutItemMapGridStack::asList() const
90 {
91  QList< QgsLayoutItemMapGrid * > list;
93  {
94  if ( QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item ) )
95  {
96  list.append( grid );
97  }
98  }
99  return list;
100 }
101 
103 {
104  QgsLayoutItemMapItem *item = mItems.at( idx );
105  QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item );
106  return *grid;
107 }
108 
109 bool QgsLayoutItemMapGridStack::readXml( const QDomElement &elem, const QDomDocument &doc, const QgsReadWriteContext &context )
110 {
111  removeItems();
112 
113  //read grid stack
114  const QDomNodeList mapGridNodeList = elem.elementsByTagName( QStringLiteral( "ComposerMapGrid" ) );
115  for ( int i = 0; i < mapGridNodeList.size(); ++i )
116  {
117  const QDomElement mapGridElem = mapGridNodeList.at( i ).toElement();
118  QgsLayoutItemMapGrid *mapGrid = new QgsLayoutItemMapGrid( mapGridElem.attribute( QStringLiteral( "name" ) ), mMap );
119  mapGrid->readXml( mapGridElem, doc, context );
120  mItems.append( mapGrid );
121  }
122 
123  return true;
124 }
125 
127 {
128  double top = 0.0;
129  double right = 0.0;
130  double bottom = 0.0;
131  double left = 0.0;
132  calculateMaxGridExtension( top, right, bottom, left );
133  return std::max( std::max( std::max( top, right ), bottom ), left );
134 }
135 
136 void QgsLayoutItemMapGridStack::calculateMaxGridExtension( double &top, double &right, double &bottom, double &left ) const
137 {
138  top = 0.0;
139  right = 0.0;
140  bottom = 0.0;
141  left = 0.0;
142 
143  for ( QgsLayoutItemMapItem *item : mItems )
144  {
145  if ( QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item ) )
146  {
147  double gridTop = 0.0;
148  double gridRight = 0.0;
149  double gridBottom = 0.0;
150  double gridLeft = 0.0;
151  grid->calculateMaxExtension( gridTop, gridRight, gridBottom, gridLeft );
152  top = std::max( top, gridTop );
153  right = std::max( right, gridRight );
154  bottom = std::max( bottom, gridBottom );
155  left = std::max( left, gridLeft );
156  }
157  }
158 }
159 
160 
161 //
162 // QgsLayoutItemMapGrid
163 //
164 
166 {
167  // returns a border as a vector2D for vector arithmetic
168  switch ( border )
169  {
171  return QVector2D( 0, 1 );
173  return QVector2D( -1, 0 );
175  return QVector2D( 0, -1 );
177  return QVector2D( 1, 0 );
178  }
179  return QVector2D();
180 }
182 {
183  // returns a border normal (towards center) as a vector2D for vector arithmetic
184  const QVector2D borderVector = borderToVector2D( border );
185  return QVector2D( borderVector.y(), -borderVector.x() );
186 }
187 
189  : QgsLayoutItemMapItem( name, map )
190  , mGridFrameSides( QgsLayoutItemMapGrid::FrameLeft | QgsLayoutItemMapGrid::FrameRight |
191  QgsLayoutItemMapGrid::FrameTop | QgsLayoutItemMapGrid::FrameBottom )
192 {
193  //get default layout font from settings
194  const QgsSettings settings;
195  const QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
196  if ( !defaultFontString.isEmpty() )
197  {
198  QFont font;
199  font.setFamily( defaultFontString );
200  mAnnotationFormat.setFont( font );
201  }
202 
203  createDefaultGridLineSymbol();
204  createDefaultGridMarkerSymbol();
205 
206  connect( mMap, &QgsLayoutItemMap::extentChanged, this, &QgsLayoutItemMapGrid::refreshDataDefinedProperties );
207  connect( mMap, &QgsLayoutItemMap::mapRotationChanged, this, &QgsLayoutItemMapGrid::refreshDataDefinedProperties );
208  connect( mMap, &QgsLayoutItemMap::crsChanged, this, [ = ]
209  {
210  if ( !mCRS.isValid() )
211  emit crsChanged();
212  } );
213 }
214 
216 
217 void QgsLayoutItemMapGrid::createDefaultGridLineSymbol()
218 {
219  QVariantMap properties;
220  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
221  properties.insert( QStringLiteral( "width" ), QStringLiteral( "0.3" ) );
222  properties.insert( QStringLiteral( "capstyle" ), QStringLiteral( "flat" ) );
223  mGridLineSymbol.reset( QgsLineSymbol::createSimple( properties ) );
224 }
225 
226 void QgsLayoutItemMapGrid::createDefaultGridMarkerSymbol()
227 {
228  QVariantMap properties;
229  properties.insert( QStringLiteral( "name" ), QStringLiteral( "circle" ) );
230  properties.insert( QStringLiteral( "size" ), QStringLiteral( "2.0" ) );
231  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
232  mGridMarkerSymbol.reset( QgsMarkerSymbol::createSimple( properties ) );
233 }
234 
235 void QgsLayoutItemMapGrid::setGridLineWidth( const double width )
236 {
237  if ( mGridLineSymbol )
238  {
239  mGridLineSymbol->setWidth( width );
240  }
241 }
242 
244 {
245  if ( mGridLineSymbol )
246  {
247  mGridLineSymbol->setColor( c );
248  }
249 }
250 
251 bool QgsLayoutItemMapGrid::writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
252 {
253  if ( elem.isNull() )
254  {
255  return false;
256  }
257 
258  QDomElement mapGridElem = doc.createElement( QStringLiteral( "ComposerMapGrid" ) );
259  mapGridElem.setAttribute( QStringLiteral( "gridStyle" ), mGridStyle );
260  mapGridElem.setAttribute( QStringLiteral( "intervalX" ), qgsDoubleToString( mGridIntervalX ) );
261  mapGridElem.setAttribute( QStringLiteral( "intervalY" ), qgsDoubleToString( mGridIntervalY ) );
262  mapGridElem.setAttribute( QStringLiteral( "offsetX" ), qgsDoubleToString( mGridOffsetX ) );
263  mapGridElem.setAttribute( QStringLiteral( "offsetY" ), qgsDoubleToString( mGridOffsetY ) );
264  mapGridElem.setAttribute( QStringLiteral( "crossLength" ), qgsDoubleToString( mCrossLength ) );
265 
266  QDomElement lineStyleElem = doc.createElement( QStringLiteral( "lineStyle" ) );
267  const QDomElement gridLineStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mGridLineSymbol.get(), doc, context );
268  lineStyleElem.appendChild( gridLineStyleElem );
269  mapGridElem.appendChild( lineStyleElem );
270 
271  QDomElement markerStyleElem = doc.createElement( QStringLiteral( "markerStyle" ) );
272  const QDomElement gridMarkerStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mGridMarkerSymbol.get(), doc, context );
273  markerStyleElem.appendChild( gridMarkerStyleElem );
274  mapGridElem.appendChild( markerStyleElem );
275 
276  mapGridElem.setAttribute( QStringLiteral( "gridFrameStyle" ), mGridFrameStyle );
277  mapGridElem.setAttribute( QStringLiteral( "gridFrameSideFlags" ), mGridFrameSides );
278  mapGridElem.setAttribute( QStringLiteral( "gridFrameWidth" ), qgsDoubleToString( mGridFrameWidth ) );
279  mapGridElem.setAttribute( QStringLiteral( "gridFrameMargin" ), qgsDoubleToString( mGridFrameMargin ) );
280  mapGridElem.setAttribute( QStringLiteral( "gridFramePenThickness" ), qgsDoubleToString( mGridFramePenThickness ) );
281  mapGridElem.setAttribute( QStringLiteral( "gridFramePenColor" ), QgsSymbolLayerUtils::encodeColor( mGridFramePenColor ) );
282  mapGridElem.setAttribute( QStringLiteral( "frameFillColor1" ), QgsSymbolLayerUtils::encodeColor( mGridFrameFillColor1 ) );
283  mapGridElem.setAttribute( QStringLiteral( "frameFillColor2" ), QgsSymbolLayerUtils::encodeColor( mGridFrameFillColor2 ) );
284  mapGridElem.setAttribute( QStringLiteral( "leftFrameDivisions" ), mLeftFrameDivisions );
285  mapGridElem.setAttribute( QStringLiteral( "rightFrameDivisions" ), mRightFrameDivisions );
286  mapGridElem.setAttribute( QStringLiteral( "topFrameDivisions" ), mTopFrameDivisions );
287  mapGridElem.setAttribute( QStringLiteral( "bottomFrameDivisions" ), mBottomFrameDivisions );
288  mapGridElem.setAttribute( QStringLiteral( "rotatedTicksLengthMode" ), mRotatedTicksLengthMode );
289  mapGridElem.setAttribute( QStringLiteral( "rotatedTicksEnabled" ), mRotatedTicksEnabled );
290  mapGridElem.setAttribute( QStringLiteral( "rotatedTicksMinimumAngle" ), QString::number( mRotatedTicksMinimumAngle ) );
291  mapGridElem.setAttribute( QStringLiteral( "rotatedTicksMarginToCorner" ), QString::number( mRotatedTicksMarginToCorner ) );
292  mapGridElem.setAttribute( QStringLiteral( "rotatedAnnotationsLengthMode" ), mRotatedAnnotationsLengthMode );
293  mapGridElem.setAttribute( QStringLiteral( "rotatedAnnotationsEnabled" ), mRotatedAnnotationsEnabled );
294  mapGridElem.setAttribute( QStringLiteral( "rotatedAnnotationsMinimumAngle" ), QString::number( mRotatedAnnotationsMinimumAngle ) );
295  mapGridElem.setAttribute( QStringLiteral( "rotatedAnnotationsMarginToCorner" ), QString::number( mRotatedAnnotationsMarginToCorner ) );
296  if ( mCRS.isValid() )
297  {
298  mCRS.writeXml( mapGridElem, doc );
299  }
300 
301  mapGridElem.setAttribute( QStringLiteral( "annotationFormat" ), mGridAnnotationFormat );
302  mapGridElem.setAttribute( QStringLiteral( "showAnnotation" ), mShowGridAnnotation );
303  mapGridElem.setAttribute( QStringLiteral( "annotationExpression" ), mGridAnnotationExpressionString );
304  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationDisplay" ), mLeftGridAnnotationDisplay );
305  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationDisplay" ), mRightGridAnnotationDisplay );
306  mapGridElem.setAttribute( QStringLiteral( "topAnnotationDisplay" ), mTopGridAnnotationDisplay );
307  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationDisplay" ), mBottomGridAnnotationDisplay );
308  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationPosition" ), mLeftGridAnnotationPosition );
309  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationPosition" ), mRightGridAnnotationPosition );
310  mapGridElem.setAttribute( QStringLiteral( "topAnnotationPosition" ), mTopGridAnnotationPosition );
311  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationPosition" ), mBottomGridAnnotationPosition );
312  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationDirection" ), mLeftGridAnnotationDirection );
313  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationDirection" ), mRightGridAnnotationDirection );
314  mapGridElem.setAttribute( QStringLiteral( "topAnnotationDirection" ), mTopGridAnnotationDirection );
315  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationDirection" ), mBottomGridAnnotationDirection );
316  mapGridElem.setAttribute( QStringLiteral( "frameAnnotationDistance" ), QString::number( mAnnotationFrameDistance ) );
317  mapGridElem.appendChild( mAnnotationFormat.writeXml( doc, context ) );
318  mapGridElem.setAttribute( QStringLiteral( "annotationPrecision" ), mGridAnnotationPrecision );
319  mapGridElem.setAttribute( QStringLiteral( "unit" ), mGridUnit );
320  mapGridElem.setAttribute( QStringLiteral( "blendMode" ), mBlendMode );
321  mapGridElem.setAttribute( QStringLiteral( "minimumIntervalWidth" ), QString::number( mMinimumIntervalWidth ) );
322  mapGridElem.setAttribute( QStringLiteral( "maximumIntervalWidth" ), QString::number( mMaximumIntervalWidth ) );
323 
324  const bool ok = QgsLayoutItemMapItem::writeXml( mapGridElem, doc, context );
325  elem.appendChild( mapGridElem );
326  return ok;
327 }
328 
329 bool QgsLayoutItemMapGrid::readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
330 {
331  Q_UNUSED( doc )
332  if ( itemElem.isNull() )
333  {
334  return false;
335  }
336 
337  const bool ok = QgsLayoutItemMapItem::readXml( itemElem, doc, context );
338 
339  //grid
340  mGridStyle = QgsLayoutItemMapGrid::GridStyle( itemElem.attribute( QStringLiteral( "gridStyle" ), QStringLiteral( "0" ) ).toInt() );
341  mGridIntervalX = itemElem.attribute( QStringLiteral( "intervalX" ), QStringLiteral( "0" ) ).toDouble();
342  mGridIntervalY = itemElem.attribute( QStringLiteral( "intervalY" ), QStringLiteral( "0" ) ).toDouble();
343  mGridOffsetX = itemElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble();
344  mGridOffsetY = itemElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble();
345  mCrossLength = itemElem.attribute( QStringLiteral( "crossLength" ), QStringLiteral( "3" ) ).toDouble();
346  mGridFrameStyle = static_cast< QgsLayoutItemMapGrid::FrameStyle >( itemElem.attribute( QStringLiteral( "gridFrameStyle" ), QStringLiteral( "0" ) ).toInt() );
347  mGridFrameSides = static_cast< QgsLayoutItemMapGrid::FrameSideFlags >( itemElem.attribute( QStringLiteral( "gridFrameSideFlags" ), QStringLiteral( "15" ) ).toInt() );
348  mGridFrameWidth = itemElem.attribute( QStringLiteral( "gridFrameWidth" ), QStringLiteral( "2.0" ) ).toDouble();
349  mGridFrameMargin = itemElem.attribute( QStringLiteral( "gridFrameMargin" ), QStringLiteral( "0.0" ) ).toDouble();
350  mGridFramePenThickness = itemElem.attribute( QStringLiteral( "gridFramePenThickness" ), QStringLiteral( "0.3" ) ).toDouble();
351  mGridFramePenColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridFramePenColor" ), QStringLiteral( "0,0,0" ) ) );
352  mGridFrameFillColor1 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "frameFillColor1" ), QStringLiteral( "255,255,255,255" ) ) );
353  mGridFrameFillColor2 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "frameFillColor2" ), QStringLiteral( "0,0,0,255" ) ) );
354  mLeftFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "leftFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
355  mRightFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "rightFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
356  mTopFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "topFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
357  mBottomFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "bottomFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
358  mRotatedTicksLengthMode = TickLengthMode( itemElem.attribute( QStringLiteral( "rotatedTicksLengthMode" ), QStringLiteral( "0" ) ).toInt() );
359  mRotatedTicksEnabled = itemElem.attribute( QStringLiteral( "rotatedTicksEnabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
360  mRotatedTicksMinimumAngle = itemElem.attribute( QStringLiteral( "rotatedTicksMinimumAngle" ), QStringLiteral( "0" ) ).toDouble();
361  mRotatedTicksMarginToCorner = itemElem.attribute( QStringLiteral( "rotatedTicksMarginToCorner" ), QStringLiteral( "0" ) ).toDouble();
362  mRotatedAnnotationsLengthMode = TickLengthMode( itemElem.attribute( QStringLiteral( "rotatedAnnotationsLengthMode" ), QStringLiteral( "0" ) ).toInt() );
363  mRotatedAnnotationsEnabled = itemElem.attribute( QStringLiteral( "rotatedAnnotationsEnabled" ), QStringLiteral( "0" ) ) != QLatin1String( "0" );
364  mRotatedAnnotationsMinimumAngle = itemElem.attribute( QStringLiteral( "rotatedAnnotationsMinimumAngle" ), QStringLiteral( "0" ) ).toDouble();
365  mRotatedAnnotationsMarginToCorner = itemElem.attribute( QStringLiteral( "rotatedAnnotationsMarginToCorner" ), QStringLiteral( "0" ) ).toDouble();
366 
367  const QDomElement lineStyleElem = itemElem.firstChildElement( QStringLiteral( "lineStyle" ) );
368  if ( !lineStyleElem.isNull() )
369  {
370  const QDomElement symbolElem = lineStyleElem.firstChildElement( QStringLiteral( "symbol" ) );
371  if ( !symbolElem.isNull() )
372  {
373  mGridLineSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( symbolElem, context ) );
374  }
375  }
376  else
377  {
378  //old project file, read penWidth /penColorRed, penColorGreen, penColorBlue
379  mGridLineSymbol.reset( QgsLineSymbol::createSimple( QVariantMap() ) );
380  mGridLineSymbol->setWidth( itemElem.attribute( QStringLiteral( "penWidth" ), QStringLiteral( "0" ) ).toDouble() );
381  mGridLineSymbol->setColor( QColor( itemElem.attribute( QStringLiteral( "penColorRed" ), QStringLiteral( "0" ) ).toInt(),
382  itemElem.attribute( QStringLiteral( "penColorGreen" ), QStringLiteral( "0" ) ).toInt(),
383  itemElem.attribute( QStringLiteral( "penColorBlue" ), QStringLiteral( "0" ) ).toInt() ) );
384  }
385 
386  const QDomElement markerStyleElem = itemElem.firstChildElement( QStringLiteral( "markerStyle" ) );
387  if ( !markerStyleElem.isNull() )
388  {
389  const QDomElement symbolElem = markerStyleElem.firstChildElement( QStringLiteral( "symbol" ) );
390  if ( !symbolElem.isNull() )
391  {
392  mGridMarkerSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context ) );
393  }
394  }
395 
396  if ( !mCRS.readXml( itemElem ) )
398 
399  mBlendMode = static_cast< QPainter::CompositionMode >( itemElem.attribute( QStringLiteral( "blendMode" ), QStringLiteral( "0" ) ).toUInt() );
400 
401  //annotation
402  mShowGridAnnotation = ( itemElem.attribute( QStringLiteral( "showAnnotation" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
403  mGridAnnotationFormat = QgsLayoutItemMapGrid::AnnotationFormat( itemElem.attribute( QStringLiteral( "annotationFormat" ), QStringLiteral( "0" ) ).toInt() );
404  mGridAnnotationExpressionString = itemElem.attribute( QStringLiteral( "annotationExpression" ) );
405  mGridAnnotationExpression.reset();
406  mLeftGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "leftAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
407  mRightGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "rightAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
408  mTopGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "topAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
409  mBottomGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "bottomAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
410  mLeftGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "leftAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
411  mRightGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "rightAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
412  mTopGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "topAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
413  mBottomGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "bottomAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
414 
415  mLeftGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "leftAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
416  mRightGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "rightAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
417  mTopGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "topAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
418  mBottomGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "bottomAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
419  mAnnotationFrameDistance = itemElem.attribute( QStringLiteral( "frameAnnotationDistance" ), QStringLiteral( "0" ) ).toDouble();
420 
421  if ( !itemElem.firstChildElement( "text-style" ).isNull() )
422  {
423  mAnnotationFormat.readXml( itemElem, context );
424  }
425  else
426  {
427  QFont font;
428  if ( !QgsFontUtils::setFromXmlChildNode( font, itemElem, "annotationFontProperties" ) )
429  {
430  font.fromString( itemElem.attribute( "annotationFont", QString() ) );
431  }
432  mAnnotationFormat.setFont( font );
433  mAnnotationFormat.setSize( font.pointSizeF() );
434  mAnnotationFormat.setSizeUnit( QgsUnitTypes::RenderPoints );
435  mAnnotationFormat.setColor( QgsSymbolLayerUtils::decodeColor( itemElem.attribute( "annotationFontColor", "0,0,0,255" ) ) );
436  }
437 
438  mGridAnnotationPrecision = itemElem.attribute( QStringLiteral( "annotationPrecision" ), QStringLiteral( "3" ) ).toInt();
439  const int gridUnitInt = itemElem.attribute( QStringLiteral( "unit" ), QString::number( MapUnit ) ).toInt();
440  mGridUnit = ( gridUnitInt <= static_cast< int >( DynamicPageSizeBased ) ) ? static_cast< GridUnit >( gridUnitInt ) : MapUnit;
441  mMinimumIntervalWidth = itemElem.attribute( QStringLiteral( "minimumIntervalWidth" ), QStringLiteral( "50" ) ).toDouble();
442  mMaximumIntervalWidth = itemElem.attribute( QStringLiteral( "maximumIntervalWidth" ), QStringLiteral( "100" ) ).toDouble();
443 
444  refreshDataDefinedProperties();
445  return ok;
446 }
447 
449 {
450  if ( mCRS == crs )
451  return;
452 
453  mCRS = crs;
454  mTransformDirty = true;
455  emit crsChanged();
456 }
457 
459 {
460  return mBlendMode != QPainter::CompositionMode_SourceOver;
461 }
462 
463 QPolygonF QgsLayoutItemMapGrid::scalePolygon( const QPolygonF &polygon, const double scale ) const
464 {
465  const QTransform t = QTransform::fromScale( scale, scale );
466  return t.map( polygon );
467 }
468 
469 void QgsLayoutItemMapGrid::drawGridCrsTransform( QgsRenderContext &context, double dotsPerMM, bool calculateLinesOnly ) const
470 {
471  if ( !mMap || !mEvaluatedEnabled )
472  {
473  return;
474  }
475 
476  //has map extent/scale changed?
477  const QPolygonF mapPolygon = mMap->transformedMapPolygon();
478  if ( mapPolygon != mPrevMapPolygon )
479  {
480  mTransformDirty = true;
481  mPrevMapPolygon = mapPolygon;
482  }
483 
484  if ( mTransformDirty )
485  {
486  calculateCrsTransformLines();
487  }
488 
489  //draw lines
490  if ( !calculateLinesOnly )
491  {
492  if ( mGridStyle == QgsLayoutItemMapGrid::Solid )
493  {
494  QList< GridLine >::const_iterator gridIt = mGridLines.constBegin();
495  for ( ; gridIt != mGridLines.constEnd(); ++gridIt )
496  {
497  drawGridLine( scalePolygon( gridIt->line, dotsPerMM ), context );
498  }
499  }
500  else if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
501  {
502  const double maxX = mMap->rect().width();
503  const double maxY = mMap->rect().height();
504 
505  QList< QgsPointXY >::const_iterator intersectionIt = mTransformedIntersections.constBegin();
506  for ( ; intersectionIt != mTransformedIntersections.constEnd(); ++intersectionIt )
507  {
508  const double x = intersectionIt->x();
509  const double y = intersectionIt->y();
510  if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
511  {
512  //ensure that crosses don't overshoot the map item bounds
513  const QLineF line1 = QLineF( x - mEvaluatedCrossLength, y, x + mEvaluatedCrossLength, y );
514  line1.p1().rx() = line1.p1().x() < 0 ? 0 : line1.p1().x();
515  line1.p2().rx() = line1.p2().x() > maxX ? maxX : line1.p2().x();
516  const QLineF line2 = QLineF( x, y - mEvaluatedCrossLength, x, y + mEvaluatedCrossLength );
517  line2.p1().ry() = line2.p1().y() < 0 ? 0 : line2.p1().y();
518  line2.p2().ry() = line2.p2().y() > maxY ? maxY : line2.p2().y();
519 
520  //draw line using coordinates scaled to dots
521  drawGridLine( QLineF( line1.p1() * dotsPerMM, line1.p2() * dotsPerMM ), context );
522  drawGridLine( QLineF( line2.p1() * dotsPerMM, line2.p2() * dotsPerMM ), context );
523  }
524  else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
525  {
526  drawGridMarker( QPointF( x, y ) * dotsPerMM, context );
527  }
528  }
529  }
530  }
531 }
532 
533 void QgsLayoutItemMapGrid::calculateCrsTransformLines() const
534 {
535  QgsRectangle crsBoundingRect;
536  QgsCoordinateTransform inverseTr;
537  if ( crsGridParams( crsBoundingRect, inverseTr ) != 0 )
538  {
539  return;
540  }
541 
542  // calculate grid lines
543  mGridLines.clear();
544  xGridLinesCrsTransform( crsBoundingRect, inverseTr );
545  yGridLinesCrsTransform( crsBoundingRect, inverseTr );
546 
547  if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
548  {
549  //cross or markers style - we also need to calculate intersections of lines
550 
551  //first convert lines to QgsGeometry
552  QList< QgsGeometry > xLines;
553  QList< QgsGeometry > yLines;
554  QList< GridLine >::const_iterator gridIt = mGridLines.constBegin();
555  for ( ; gridIt != mGridLines.constEnd(); ++gridIt )
556  {
557 
558  QgsPolylineXY line;
559  for ( int i = 0; i < gridIt->line.size(); ++i )
560  {
561  line.append( QgsPointXY( gridIt->line.at( i ).x(), gridIt->line.at( i ).y() ) );
562  }
563  if ( gridIt->coordinateType == AnnotationCoordinate::Longitude )
564  yLines << QgsGeometry::fromPolylineXY( line );
565  else if ( gridIt->coordinateType == AnnotationCoordinate::Latitude )
566  xLines << QgsGeometry::fromPolylineXY( line );
567  }
568 
569  //now, loop through geometries and calculate intersection points
570  mTransformedIntersections.clear();
571  QList< QgsGeometry >::const_iterator yLineIt = yLines.constBegin();
572  for ( ; yLineIt != yLines.constEnd(); ++yLineIt )
573  {
574  QList< QgsGeometry >::const_iterator xLineIt = xLines.constBegin();
575  for ( ; xLineIt != xLines.constEnd(); ++xLineIt )
576  {
577  //look for intersections between lines
578  const QgsGeometry intersects = ( *yLineIt ).intersection( ( *xLineIt ) );
579  if ( intersects.isNull() )
580  continue;
581 
582  //go through all intersections and draw grid markers/crosses
583  int i = 0;
584  QgsPointXY vertex = intersects.vertexAt( i );
585  while ( !vertex.isEmpty() )
586  {
587  mTransformedIntersections << vertex;
588  i = i + 1;
589  vertex = intersects.vertexAt( i );
590  }
591  }
592  }
593  }
594 
595  mTransformDirty = false;
596 }
597 
598 void QgsLayoutItemMapGrid::draw( QPainter *p )
599 {
600  if ( !mMap || !mEvaluatedEnabled )
601  {
602  return;
603  }
604  QPaintDevice *paintDevice = p->device();
605  if ( !paintDevice )
606  {
607  return;
608  }
609 
610  p->save();
611  p->setCompositionMode( mBlendMode );
612  p->setRenderHint( QPainter::Antialiasing, mMap->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
613 
614  const QRectF thisPaintRect = QRectF( 0, 0, mMap->rect().width(), mMap->rect().height() );
615  p->setClipRect( thisPaintRect );
616  if ( thisPaintRect != mPrevPaintRect )
617  {
618  //rect has changed, so need to recalculate transform
619  mTransformDirty = true;
620  mPrevPaintRect = thisPaintRect;
621  }
622 
623  //setup painter scaling to dots so that raster symbology is drawn to scale
624  const double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
625  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); //scale painter from mm to dots
626 
627  //setup render context
629  context.setForceVectorOutput( true );
631  const QgsExpressionContext expressionContext = createExpressionContext();
632  context.setExpressionContext( expressionContext );
633 
634  //is grid in a different crs than map?
635  switch ( mGridUnit )
636  {
637  case MapUnit:
639  if ( mCRS.isValid() && mCRS != mMap->crs() )
640  {
641  drawGridCrsTransform( context, dotsPerMM );
642  break;
643  }
644 
646  case CM:
647  case MM:
648  drawGridNoTransform( context, dotsPerMM );
649  break;
650  }
651  p->restore();
652 
653  p->setClipping( false );
654 #ifdef Q_OS_MAC
655  //QPainter::setClipping(false) seems to be broken on OSX (#12747). So we hack around it by
656  //setting a larger clip rect
657  p->setClipRect( mMap->mapRectFromScene( mMap->sceneBoundingRect() ).adjusted( -10, -10, 10, 10 ) );
658 #endif
659 
660 
661  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame || mShowGridAnnotation )
662  updateGridLinesAnnotationsPositions();
663 
664  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
665  {
666  drawGridFrame( p );
667  }
668 
669  if ( mShowGridAnnotation )
670  {
671  drawCoordinateAnnotations( context, context.expressionContext() );
672  }
673 }
674 
675 void QgsLayoutItemMapGrid::updateGridLinesAnnotationsPositions() const
676 {
677  QList< GridLine >::iterator it = mGridLines.begin();
678  for ( ; it != mGridLines.end(); ++it )
679  {
680  it->startAnnotation.border = borderForLineCoord( it->line.first(), it->coordinateType );
681  it->endAnnotation.border = borderForLineCoord( it->line.last(), it->coordinateType );
682  it->startAnnotation.position = QVector2D( it->line.first() );
683  it->endAnnotation.position = QVector2D( it->line.last() );
684  it->startAnnotation.vector = QVector2D( it->line.at( 1 ) - it->line.first() ).normalized();
685  it->endAnnotation.vector = QVector2D( it->line.at( it->line.count() - 2 ) - it->line.last() ).normalized();
686  const QVector2D normS = borderToNormal2D( it->startAnnotation.border );
687  it->startAnnotation.angle = atan2( it->startAnnotation.vector.x() * normS.y() - it->startAnnotation.vector.y() * normS.x(), it->startAnnotation.vector.x() * normS.x() + it->startAnnotation.vector.y() * normS.y() );
688  const QVector2D normE = borderToNormal2D( it->endAnnotation.border );
689  it->endAnnotation.angle = atan2( it->endAnnotation.vector.x() * normE.y() - it->endAnnotation.vector.y() * normE.x(), it->endAnnotation.vector.x() * normE.x() + it->endAnnotation.vector.y() * normE.y() );
690  }
691 }
692 
693 void QgsLayoutItemMapGrid::drawGridNoTransform( QgsRenderContext &context, double dotsPerMM, bool calculateLinesOnly ) const
694 {
695  //get line positions
696  mGridLines.clear();
697  yGridLines();
698  xGridLines();
699 
700  if ( calculateLinesOnly )
701  return;
702 
703  QList< GridLine >::const_iterator vIt = mGridLines.constBegin();
704  QList< GridLine >::const_iterator hIt = mGridLines.constBegin();
705 
706  //simple approach: draw vertical lines first, then horizontal ones
707  if ( mGridStyle == QgsLayoutItemMapGrid::Solid )
708  {
709  //we need to scale line coordinates to dots, rather than mm, since the painter has already been scaled to dots
710  //this is done by multiplying each line coordinate by dotsPerMM
711  QLineF line;
712  for ( ; vIt != mGridLines.constEnd(); ++vIt )
713  {
714  if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
715  continue;
716  line = QLineF( vIt->line.first() * dotsPerMM, vIt->line.last() * dotsPerMM );
717  drawGridLine( line, context );
718  }
719 
720  for ( ; hIt != mGridLines.constEnd(); ++hIt )
721  {
722  if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
723  continue;
724  line = QLineF( hIt->line.first() * dotsPerMM, hIt->line.last() * dotsPerMM );
725  drawGridLine( line, context );
726  }
727  }
728  else if ( mGridStyle != QgsLayoutItemMapGrid::FrameAnnotationsOnly ) //cross or markers
729  {
730  QLineF l1, l2;
731  QPointF intersectionPoint, crossEnd1, crossEnd2;
732  for ( ; vIt != mGridLines.constEnd(); ++vIt )
733  {
734  if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
735  continue;
736 
737  l1 = QLineF( vIt->line.first(), vIt->line.last() );
738 
739  //test for intersection with every horizontal line
740  hIt = mGridLines.constBegin();
741  for ( ; hIt != mGridLines.constEnd(); ++hIt )
742  {
743  if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
744  continue;
745 
746  l2 = QLineF( hIt->line.first(), hIt->line.last() );
747 
748 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
749  if ( l2.intersect( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
750 #else
751  if ( l2.intersects( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
752 #endif
753  {
754  if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
755  {
756  //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
757  crossEnd1 = ( ( intersectionPoint - l1.p1() ).manhattanLength() > 0.01 ) ?
758  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p1(), mEvaluatedCrossLength ) : intersectionPoint;
759  crossEnd2 = ( ( intersectionPoint - l1.p2() ).manhattanLength() > 0.01 ) ?
760  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p2(), mEvaluatedCrossLength ) : intersectionPoint;
761  //draw line using coordinates scaled to dots
762  drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
763  }
764  else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
765  {
766  drawGridMarker( intersectionPoint * dotsPerMM, context );
767  }
768  }
769  }
770  }
771  if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
772  {
773  //markers mode, so we have no need to process horizontal lines (we've already
774  //drawn markers on the intersections between horizontal and vertical lines)
775  return;
776  }
777 
778  hIt = mGridLines.constBegin();
779  for ( ; hIt != mGridLines.constEnd(); ++hIt )
780  {
781  if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
782  continue;
783 
784  l1 = QLineF( hIt->line.first(), hIt->line.last() );
785 
786  vIt = mGridLines.constBegin();
787  for ( ; vIt != mGridLines.constEnd(); ++vIt )
788  {
789  if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
790  continue;
791 
792  l2 = QLineF( vIt->line.first(), vIt->line.last() );
793 
794 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
795  if ( l2.intersect( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
796 #else
797  if ( l2.intersects( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
798 #endif
799  {
800  //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
801  crossEnd1 = ( ( intersectionPoint - l1.p1() ).manhattanLength() > 0.01 ) ?
802  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p1(), mEvaluatedCrossLength ) : intersectionPoint;
803  crossEnd2 = ( ( intersectionPoint - l1.p2() ).manhattanLength() > 0.01 ) ?
804  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p2(), mEvaluatedCrossLength ) : intersectionPoint;
805  //draw line using coordinates scaled to dots
806  drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
807  }
808  }
809  }
810  }
811 }
812 
813 void QgsLayoutItemMapGrid::drawGridFrame( QPainter *p, GridExtension *extension ) const
814 {
815  if ( p )
816  {
817  p->save();
818  p->setRenderHint( QPainter::Antialiasing, mMap->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
819  }
820 
821 
822  switch ( mGridFrameStyle )
823  {
826  drawGridFrameZebra( p, extension );
827  break;
831  drawGridFrameTicks( p, extension );
832  break;
833 
836  drawGridFrameLine( p, extension );
837  break;
838 
840  break;
841  }
842 
843  if ( p )
844  p->restore();
845 }
846 
847 void QgsLayoutItemMapGrid::drawGridLine( const QLineF &line, QgsRenderContext &context ) const
848 {
849  QPolygonF poly;
850  poly << line.p1() << line.p2();
851  drawGridLine( poly, context );
852 }
853 
854 void QgsLayoutItemMapGrid::drawGridLine( const QPolygonF &line, QgsRenderContext &context ) const
855 {
856  if ( !mMap || !mMap->layout() || !mGridLineSymbol )
857  {
858  return;
859  }
860 
861  mGridLineSymbol->startRender( context );
862  mGridLineSymbol->renderPolyline( line, nullptr, context );
863  mGridLineSymbol->stopRender( context );
864 }
865 
866 void QgsLayoutItemMapGrid::drawGridMarker( QPointF point, QgsRenderContext &context ) const
867 {
868  if ( !mMap || !mMap->layout() || !mGridMarkerSymbol )
869  {
870  return;
871  }
872 
873  mGridMarkerSymbol->startRender( context );
874  mGridMarkerSymbol->renderPoint( point, nullptr, context );
875  mGridMarkerSymbol->stopRender( context );
876 }
877 
878 void QgsLayoutItemMapGrid::drawGridFrameZebra( QPainter *p, GridExtension *extension ) const
879 {
881  {
882  drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Left, extension ? &extension->left : nullptr );
883  }
885  {
886  drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Right, extension ? &extension->right : nullptr );
887  }
889  {
890  drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Top, extension ? &extension->top : nullptr );
891  }
893  {
894  drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Bottom, extension ? &extension->bottom : nullptr );
895  }
896 }
897 
898 void QgsLayoutItemMapGrid::drawGridFrameZebraBorder( QPainter *p, BorderSide border, double *extension ) const
899 {
900  if ( !mMap )
901  {
902  return;
903  }
904 
905  if ( extension )
906  {
907  *extension = mEvaluatedGridFrameMargin + mEvaluatedGridFrameWidth + mEvaluatedGridFrameLineThickness / 2.0;
908  return;
909  }
910 
911  double currentCoord = 0.0;
912  bool color1 = false;
913  double x = 0;
914  double y = 0;
915  double width = 0;
916  double height = 0;
917 
918  bool drawTLBox = false;
919  bool drawTRBox = false;
920  bool drawBLBox = false;
921  bool drawBRBox = false;
922 
923  QMap< double, double > pos = QMap< double, double >();
924  QList< GridLine >::const_iterator it = mGridLines.constBegin();
925  for ( ; it != mGridLines.constEnd(); ++it )
926  {
927  // for first and last point of the line
928  for ( int i = 0 ; i < 2 ; ++i )
929  {
930  const GridLineAnnotation annot = ( i == 0 ) ? it->startAnnotation : it->endAnnotation;
931 
932  // we skip if the point is on another border
933  if ( annot.border != border )
934  continue;
935 
936  if ( ! shouldShowDivisionForSide( it->coordinateType, annot.border ) )
937  continue;
938 
939  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
940  pos.insert( annot.position.y(), it->coordinate );
941  else
942  pos.insert( annot.position.x(), it->coordinate );
943  }
944  }
945 
946 
947  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
948  {
949  pos.insert( mMap->rect().height(), mMap->rect().height() );
951  {
952  drawBLBox = border == QgsLayoutItemMapGrid::Left;
953  drawBRBox = border == QgsLayoutItemMapGrid::Right;
954  }
956  {
957  drawTLBox = border == QgsLayoutItemMapGrid::Left;
958  drawTRBox = border == QgsLayoutItemMapGrid::Right;
959  }
960  if ( !drawTLBox && border == QgsLayoutItemMapGrid::Left )
961  color1 = true;
962  }
963  else if ( border == QgsLayoutItemMapGrid::Top || border == QgsLayoutItemMapGrid::Bottom )
964  {
965  pos.insert( mMap->rect().width(), mMap->rect().width() );
966  }
967 
968  //set pen to current frame pen
969  QPen framePen = QPen( mGridFramePenColor );
970  framePen.setWidthF( mEvaluatedGridFrameLineThickness );
971  framePen.setJoinStyle( Qt::MiterJoin );
972  p->setPen( framePen );
973 
974  QMap< double, double >::const_iterator posIt = pos.constBegin();
975  for ( ; posIt != pos.constEnd(); ++posIt )
976  {
977  p->setBrush( QBrush( color1 ? mGridFrameFillColor1 : mGridFrameFillColor2 ) );
978  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
979  {
980  height = posIt.key() - currentCoord;
981  width = mEvaluatedGridFrameWidth;
982  x = ( border == QgsLayoutItemMapGrid::Left ) ? -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) : mMap->rect().width() + mEvaluatedGridFrameMargin;
983  y = currentCoord;
984  }
985  else //top or bottom
986  {
987  height = mEvaluatedGridFrameWidth;
988  width = posIt.key() - currentCoord;
989  x = currentCoord;
990  y = ( border == QgsLayoutItemMapGrid::Top ) ? -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) : mMap->rect().height() + mEvaluatedGridFrameMargin;
991  }
992  p->drawRect( QRectF( x, y, width, height ) );
993  currentCoord = posIt.key();
994  color1 = !color1;
995  }
996 
997  if ( mGridFrameStyle == ZebraNautical || qgsDoubleNear( mEvaluatedGridFrameMargin, 0.0 ) )
998  {
999  //draw corners
1000  width = height = ( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) ;
1001  p->setBrush( QBrush( mGridFrameFillColor1 ) );
1002  if ( drawTLBox )
1003  p->drawRect( QRectF( -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), width, height ) );
1004  if ( drawTRBox )
1005  p->drawRect( QRectF( mMap->rect().width(), -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), width, height ) );
1006  if ( drawBLBox )
1007  p->drawRect( QRectF( -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), mMap->rect().height(), width, height ) );
1008  if ( drawBRBox )
1009  p->drawRect( QRectF( mMap->rect().width(), mMap->rect().height(), width, height ) );
1010  }
1011 }
1012 
1013 void QgsLayoutItemMapGrid::drawGridFrameTicks( QPainter *p, GridExtension *extension ) const
1014 {
1015  if ( !mMap )
1016  {
1017  return;
1018  }
1019 
1020  //set pen to current frame pen
1021  if ( p )
1022  {
1023  QPen framePen = QPen( mGridFramePenColor );
1024  framePen.setWidthF( mEvaluatedGridFrameLineThickness );
1025  framePen.setCapStyle( Qt::FlatCap );
1026  p->setBrush( Qt::NoBrush );
1027  p->setPen( framePen );
1028  }
1029 
1030  QList< GridLine >::iterator it = mGridLines.begin();
1031  for ( ; it != mGridLines.end(); ++it )
1032  {
1033  // for first and last point of the line
1034  for ( int i = 0 ; i < 2 ; ++i )
1035  {
1036  const GridLineAnnotation annot = ( i == 0 ) ? it->startAnnotation : it->endAnnotation;
1037 
1038  if ( ! shouldShowDivisionForSide( it->coordinateType, annot.border ) )
1039  continue;
1040 
1041  // If the angle is below the threshold, we don't draw the annotation
1042  if ( abs( annot.angle ) / M_PI * 180.0 > 90.0 - mRotatedTicksMinimumAngle + 0.0001 )
1043  continue;
1044 
1045  // Skip outwards facing annotations that are below mRotatedTicksMarginToCorner
1046  bool facingLeft;
1047  bool facingRight;
1048  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks )
1049  {
1050  facingLeft = ( annot.angle != 0 );
1051  facingRight = ( annot.angle != 0 );
1052  }
1053  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1054  {
1055  facingLeft = ( annot.angle > 0 );
1056  facingRight = ( annot.angle < 0 );
1057  }
1058  else
1059  {
1060  facingLeft = ( annot.angle < 0 );
1061  facingRight = ( annot.angle > 0 );
1062  }
1063 
1064  if ( annot.border == BorderSide::Top && ( ( facingLeft && annot.position.x() < mRotatedTicksMarginToCorner ) ||
1065  ( facingRight && annot.position.x() > mMap->rect().width() - mRotatedTicksMarginToCorner ) ) )
1066  continue;
1067  if ( annot.border == BorderSide::Bottom && ( ( facingLeft && annot.position.x() > mMap->rect().width() - mRotatedTicksMarginToCorner ) ||
1068  ( facingRight && annot.position.x() < mRotatedTicksMarginToCorner ) ) )
1069  continue;
1070  if ( annot.border == BorderSide::Left && ( ( facingLeft && annot.position.y() > mMap->rect().height() - mRotatedTicksMarginToCorner ) ||
1071  ( facingRight && annot.position.y() < mRotatedTicksMarginToCorner ) ) )
1072  continue;
1073  if ( annot.border == BorderSide::Right && ( ( facingLeft && annot.position.y() < mRotatedTicksMarginToCorner ) ||
1074  ( facingRight && annot.position.y() > mMap->rect().height() - mRotatedTicksMarginToCorner ) ) )
1075  continue;
1076 
1077  const QVector2D normalVector = borderToNormal2D( annot.border );
1078  const QVector2D vector = ( mRotatedTicksEnabled ) ? annot.vector : normalVector;
1079 
1080  double fA = mEvaluatedGridFrameMargin; // point near to frame
1081  double fB = mEvaluatedGridFrameMargin + mEvaluatedGridFrameWidth; // point far from frame
1082 
1083  if ( mRotatedTicksEnabled && mRotatedTicksLengthMode == OrthogonalTicks )
1084  {
1085  fA /= QVector2D::dotProduct( vector, normalVector );
1086  fB /= QVector2D::dotProduct( vector, normalVector );
1087  }
1088 
1089  // extents isn't computed accurately
1090  if ( extension )
1091  {
1092  if ( mGridFrameStyle != QgsLayoutItemMapGrid::InteriorTicks )
1093  extension->UpdateBorder( annot.border, fB );
1094  continue;
1095  }
1096 
1097  QVector2D pA;
1098  QVector2D pB;
1099  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1100  {
1101  pA = annot.position + fA * vector;
1102  pB = annot.position + fB * vector;
1103  }
1104  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1105  {
1106  pA = annot.position - fA * vector;
1107  pB = annot.position - fB * vector;
1108  }
1109  else // InteriorExteriorTicks
1110  {
1111  pA = annot.position - fB * vector;
1112  pB = annot.position + ( fB - 2.0 * mEvaluatedGridFrameMargin ) * vector;
1113  }
1114  p->drawLine( QLineF( pA.toPointF(), pB.toPointF() ) );
1115 
1116  }
1117  }
1118 }
1119 
1120 void QgsLayoutItemMapGrid::drawGridFrameLine( QPainter *p, GridExtension *extension ) const
1121 {
1122  if ( !mMap )
1123  {
1124  return;
1125  }
1126 
1127  if ( p )
1128  {
1129  //set pen to current frame pen
1130  QPen framePen = QPen( mGridFramePenColor );
1131  framePen.setWidthF( mEvaluatedGridFrameLineThickness );
1132  framePen.setCapStyle( Qt::SquareCap );
1133  p->setBrush( Qt::NoBrush );
1134  p->setPen( framePen );
1135  }
1136 
1137  const bool drawDiagonals = mGridFrameStyle == LineBorderNautical && !qgsDoubleNear( mEvaluatedGridFrameMargin, 0.0 );
1138 
1140  {
1141  if ( extension )
1142  extension->UpdateBorder( QgsLayoutItemMapGrid::Left, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1143  else
1144  p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1145  }
1146 
1148  {
1149  if ( extension )
1150  extension->UpdateBorder( QgsLayoutItemMapGrid::Right, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1151  else
1152  p->drawLine( QLineF( mMap->rect().width() + mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1153  }
1154 
1156  {
1157  if ( extension )
1158  extension->UpdateBorder( QgsLayoutItemMapGrid::Top, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1159  else
1160  p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin ) );
1161  }
1162 
1164  {
1165  if ( extension )
1166  extension->UpdateBorder( QgsLayoutItemMapGrid::Bottom, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1167  else
1168  p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1169  }
1170 
1171  if ( ! extension && drawDiagonals )
1172  {
1174  {
1175  //corner left-top
1176  const double X1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0;
1177  const double Y1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0;
1178  p->drawLine( QLineF( 0, 0, X1, Y1 ) );
1179  }
1181  {
1182  //corner right-bottom
1183  const double X1 = mMap->rect().width() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1184  const double Y1 = mMap->rect().height() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1185  p->drawLine( QLineF( mMap->rect().width(), mMap->rect().height(), X1, Y1 ) );
1186  }
1188  {
1189  //corner right-top
1190  const double X1 = mMap->rect().width() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1191  const double Y1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 ;
1192  p->drawLine( QLineF( mMap->rect().width(), 0, X1, Y1 ) );
1193  }
1195  {
1196  //corner left-bottom
1197  const double X1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 ;
1198  const double Y1 = mMap->rect().height() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1199  p->drawLine( QLineF( 0, mMap->rect().height(), X1, Y1 ) );
1200  }
1201  }
1202 }
1203 
1204 void QgsLayoutItemMapGrid::drawCoordinateAnnotations( QgsRenderContext &context, QgsExpressionContext &expressionContext,
1205  GridExtension *extension ) const
1206 {
1207  QString currentAnnotationString;
1208  QList< GridLine >::const_iterator it = mGridLines.constBegin();
1209  for ( ; it != mGridLines.constEnd(); ++it )
1210  {
1211  currentAnnotationString = gridAnnotationString( it->coordinate, it->coordinateType, expressionContext );
1212  drawCoordinateAnnotation( context, it->startAnnotation, currentAnnotationString, it->coordinateType, extension );
1213  drawCoordinateAnnotation( context, it->endAnnotation, currentAnnotationString, it->coordinateType, extension );
1214  }
1215 }
1216 
1217 void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QgsRenderContext &context, GridLineAnnotation annot, const QString &annotationString, const AnnotationCoordinate coordinateType, GridExtension *extension ) const
1218 {
1219  if ( !mMap )
1220  {
1221  return;
1222  }
1223 
1224  if ( ! shouldShowAnnotationForSide( coordinateType, annot.border ) )
1225  return;
1226 
1227  const QgsLayoutItemMapGrid::BorderSide frameBorder = annot.border;
1228  double textWidth = QgsTextRenderer::textWidth( context, mAnnotationFormat, QStringList() << annotationString ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1229  if ( extension )
1230  textWidth *= 1.1; // little bit of extra padding when we are calculating the bounding rect, to account for antialiasing
1231 
1232  //relevant for annotations is the height of digits
1233  const double textHeight = ( extension ? ( QgsTextRenderer::textHeight( context, mAnnotationFormat, QChar(), true ) )
1234  : ( QgsTextRenderer::textHeight( context, mAnnotationFormat, '0', false ) ) ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
1235 
1236  double xpos = annot.position.x();
1237  double ypos = annot.position.y();
1238  QPointF anchor = QPointF();
1239  int rotation = 0;
1240 
1241  const AnnotationPosition anotPos = annotationPosition( frameBorder );
1242  const AnnotationDirection anotDir = annotationDirection( frameBorder );
1243 
1244  // If the angle is below the threshold, we don't draw the annotation
1245  if ( abs( annot.angle ) / M_PI * 180.0 > 90.0 - mRotatedAnnotationsMinimumAngle + 0.0001 )
1246  return;
1247 
1248  const QVector2D normalVector = borderToNormal2D( annot.border );
1249  const QVector2D vector = ( mRotatedAnnotationsEnabled ) ? annot.vector : normalVector;
1250 
1251  // Distance to frame
1252  double f = mEvaluatedAnnotationFrameDistance;
1253 
1254  // Adapt distance to frame using the frame width and line thickness into account
1255  const bool isOverTick = ( anotDir == QgsLayoutItemMapGrid::AboveTick || anotDir == QgsLayoutItemMapGrid::OnTick || anotDir == QgsLayoutItemMapGrid::UnderTick );
1256  const bool hasInteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks );
1257  const bool hasExteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical );
1258  const bool hasBorderWidth = ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::LineBorder || mGridFrameStyle == QgsLayoutItemMapGrid::LineBorderNautical );
1259  if ( ( anotPos == QgsLayoutItemMapGrid::InsideMapFrame && hasInteriorMargin ) || ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame && hasExteriorMargin ) )
1260  f += mEvaluatedGridFrameWidth;
1261  if ( hasBorderWidth )
1262  f += mEvaluatedGridFrameLineThickness / 2.0;
1263 
1264  if ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame )
1265  f *= -1;
1266 
1267  if ( mRotatedAnnotationsEnabled && mRotatedAnnotationsLengthMode == OrthogonalTicks )
1268  {
1269  f /= QVector2D::dotProduct( vector, normalVector );
1270  }
1271 
1272  const QVector2D pos = annot.position + f * vector;
1273  xpos = pos.x();
1274  ypos = pos.y();
1275 
1276  const bool outside = ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame );
1277 
1278  if (
1279  anotDir == QgsLayoutItemMapGrid::AboveTick ||
1280  anotDir == QgsLayoutItemMapGrid::OnTick ||
1282  )
1283  {
1284 
1285  rotation = atan2( vector.y(), vector.x() ) / M_PI * 180;
1286 
1287  if ( rotation <= -90 || rotation > 90 )
1288  {
1289  rotation += 180;
1290  anchor.setX( outside ? 0 : textWidth ); // left / right
1291  }
1292  else
1293  {
1294  anchor.setX( outside ? textWidth : 0 ); // right / left
1295  }
1296 
1297  if ( anotDir == QgsLayoutItemMapGrid::AboveTick )
1298  anchor.setY( 0.5 * textHeight ); // bottom
1299  else if ( anotDir == QgsLayoutItemMapGrid::UnderTick )
1300  anchor.setY( -1.5 * textHeight ); // top
1301  else // OnTick
1302  anchor.setY( -0.5 * textHeight ); // middle
1303 
1304  }
1305  else if ( anotDir == QgsLayoutItemMapGrid::Horizontal )
1306  {
1307  rotation = 0;
1308  anchor.setX( 0.5 * textWidth ); // center
1309  anchor.setY( -0.5 * textHeight ); // middle
1310  if ( frameBorder == QgsLayoutItemMapGrid::Top )
1311  anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1312  else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1313  anchor.setX( outside ? 0 : textWidth ); // left / right
1314  else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1315  anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1316  else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1317  anchor.setX( outside ? textWidth : 0 ); // right / left
1318  }
1319  else if ( anotDir == QgsLayoutItemMapGrid::Vertical )
1320  {
1321  rotation = -90;
1322  anchor.setX( 0.5 * textWidth ); // center
1323  anchor.setY( -0.5 * textHeight ); // middle
1324  if ( frameBorder == QgsLayoutItemMapGrid::Top )
1325  anchor.setX( outside ? 0 : textWidth ); // left / right
1326  else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1327  anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1328  else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1329  anchor.setX( outside ? textWidth : 0 ); // right / left
1330  else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1331  anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1332  }
1333  else if ( anotDir == QgsLayoutItemMapGrid::VerticalDescending )
1334  {
1335  rotation = 90;
1336  anchor.setX( 0.5 * textWidth ); // center
1337  anchor.setY( -0.5 * textHeight ); // middle
1338  if ( frameBorder == QgsLayoutItemMapGrid::Top )
1339  anchor.setX( outside ? textWidth : 0 ); // right / left
1340  else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1341  anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1342  else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1343  anchor.setX( outside ? 0 : textWidth ); // left / right
1344  else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1345  anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1346  }
1347  else // ( anotDir == QgsLayoutItemMapGrid::BoundaryDirection )
1348  {
1349  const QVector2D borderVector = borderToVector2D( annot.border );
1350  rotation = atan2( borderVector.y(), borderVector.x() ) / M_PI * 180;
1351  anchor.setX( 0.5 * textWidth ); // center
1352  if ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame )
1353  anchor.setY( -textHeight ); // top
1354  else
1355  anchor.setY( 0 ); // bottom
1356  }
1357 
1358  // extents isn't computed accurately
1359  if ( extension && anotPos == QgsLayoutItemMapGrid::OutsideMapFrame )
1360  {
1361  extension->UpdateBorder( frameBorder, -f + textWidth );
1362  // We also add a general margin, can be useful for labels near corners
1363  extension->UpdateAll( textWidth / 2.0 );
1364  }
1365 
1366  if ( extension || !context.painter() )
1367  return;
1368 
1369  // Skip outwards facing annotations that are below mRotatedAnnotationsMarginToCorner
1370  bool facingLeft = ( annot.angle < 0 );
1371  bool facingRight = ( annot.angle > 0 );
1372  if ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame )
1373  {
1374  facingLeft = !facingLeft;
1375  facingRight = !facingRight;
1376  }
1377  if ( annot.border == BorderSide::Top && ( ( facingLeft && annot.position.x() < mRotatedAnnotationsMarginToCorner ) ||
1378  ( facingRight && annot.position.x() > mMap->rect().width() - mRotatedAnnotationsMarginToCorner ) ) )
1379  return;
1380  if ( annot.border == BorderSide::Bottom && ( ( facingLeft && annot.position.x() > mMap->rect().width() - mRotatedAnnotationsMarginToCorner ) ||
1381  ( facingRight && annot.position.x() < mRotatedAnnotationsMarginToCorner ) ) )
1382  return;
1383  if ( annot.border == BorderSide::Left && ( ( facingLeft && annot.position.y() > mMap->rect().height() - mRotatedAnnotationsMarginToCorner ) ||
1384  ( facingRight && annot.position.y() < mRotatedAnnotationsMarginToCorner ) ) )
1385  return;
1386  if ( annot.border == BorderSide::Right && ( ( facingLeft && annot.position.y() < mRotatedAnnotationsMarginToCorner ) ||
1387  ( facingRight && annot.position.y() > mMap->rect().height() - mRotatedAnnotationsMarginToCorner ) ) )
1388  return;
1389 
1390  const QgsScopedQPainterState painterState( context.painter() );
1391  context.painter()->translate( QPointF( xpos, ypos ) );
1392  context.painter()->rotate( rotation );
1393  context.painter()->translate( -anchor );
1394  const QgsScopedRenderContextScaleToPixels scale( context );
1395  QgsTextRenderer::drawText( QPointF( 0, 0 ), 0, QgsTextRenderer::AlignLeft, QStringList() << annotationString, context, mAnnotationFormat );
1396 }
1397 
1398 QString QgsLayoutItemMapGrid::gridAnnotationString( double value, QgsLayoutItemMapGrid::AnnotationCoordinate coord, QgsExpressionContext &expressionContext ) const
1399 {
1400  //check if we are using degrees (ie, geographic crs)
1401  bool geographic = false;
1402  if ( mCRS.isValid() )
1403  {
1404  geographic = mCRS.isGeographic();
1405  }
1406  else if ( mMap && mMap->layout() )
1407  {
1408  geographic = mMap->crs().isGeographic();
1409  }
1410 
1411  if ( geographic && coord == QgsLayoutItemMapGrid::Longitude &&
1412  ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal || mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix ) )
1413  {
1414  // wrap around longitudes > 180 or < -180 degrees, so that, e.g., "190E" -> "170W"
1415  const double wrappedX = std::fmod( value, 360.0 );
1416  if ( wrappedX > 180.0 )
1417  {
1418  value = wrappedX - 360.0;
1419  }
1420  else if ( wrappedX < -180.0 )
1421  {
1422  value = wrappedX + 360.0;
1423  }
1424  }
1425 
1426  if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal )
1427  {
1428  return QString::number( value, 'f', mGridAnnotationPrecision );
1429  }
1430  else if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix )
1431  {
1432  QString hemisphere;
1433 
1434  const double coordRounded = qgsRound( value, mGridAnnotationPrecision );
1435  if ( coord == QgsLayoutItemMapGrid::Longitude )
1436  {
1437  //don't use E/W suffixes if ambiguous (e.g., 180 degrees)
1438  if ( !geographic || ( coordRounded != 180.0 && coordRounded != 0.0 ) )
1439  {
1440  hemisphere = value < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
1441  }
1442  }
1443  else
1444  {
1445  //don't use N/S suffixes if ambiguous (e.g., 0 degrees)
1446  if ( !geographic || coordRounded != 0.0 )
1447  {
1448  hemisphere = value < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
1449  }
1450  }
1451  if ( geographic )
1452  {
1453  //insert degree symbol for geographic coordinates
1454  return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + QChar( 176 ) + hemisphere;
1455  }
1456  else
1457  {
1458  return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + hemisphere;
1459  }
1460  }
1461  else if ( mGridAnnotationFormat == CustomFormat )
1462  {
1463  expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), value, true ) );
1464  expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), coord == QgsLayoutItemMapGrid::Longitude ? "x" : "y", true ) );
1465  if ( !mGridAnnotationExpression )
1466  {
1467  mGridAnnotationExpression.reset( new QgsExpression( mGridAnnotationExpressionString ) );
1468  mGridAnnotationExpression->prepare( &expressionContext );
1469  }
1470  return mGridAnnotationExpression->evaluate( &expressionContext ).toString();
1471  }
1472 
1474  QgsCoordinateFormatter::FormatFlags flags = QgsCoordinateFormatter::FormatFlags();
1475  switch ( mGridAnnotationFormat )
1476  {
1477  case Decimal:
1478  case DecimalWithSuffix:
1479  case CustomFormat:
1480  break; // already handled above
1481 
1482  case DegreeMinute:
1485  break;
1486 
1487  case DegreeMinuteSecond:
1490  break;
1491 
1492  case DegreeMinuteNoSuffix:
1494  flags = QgsCoordinateFormatter::FormatFlags();
1495  break;
1496 
1497  case DegreeMinutePadded:
1500  break;
1501 
1504  flags = QgsCoordinateFormatter::FormatFlags();
1505  break;
1506 
1510  break;
1511  }
1512 
1513  switch ( coord )
1514  {
1515  case Longitude:
1516  return QgsCoordinateFormatter::formatX( value, format, mGridAnnotationPrecision, flags );
1517 
1518  case Latitude:
1519  return QgsCoordinateFormatter::formatY( value, format, mGridAnnotationPrecision, flags );
1520  }
1521 
1522  return QString(); // no warnings
1523 }
1524 
1525 int QgsLayoutItemMapGrid::xGridLines() const
1526 {
1527  if ( !mMap || mEvaluatedIntervalY <= 0.0 )
1528  {
1529  return 1;
1530  }
1531 
1532 
1533  QPolygonF mapPolygon = mMap->transformedMapPolygon();
1534  QRectF mapBoundingRect = mapPolygon.boundingRect();
1535  double gridIntervalY = mEvaluatedIntervalY;
1536  double gridOffsetY = mEvaluatedOffsetY;
1537  double annotationScale = 1.0;
1538  switch ( mGridUnit )
1539  {
1540  case CM:
1541  case MM:
1542  {
1543  mapBoundingRect = mMap->rect();
1544  mapPolygon = QPolygonF( mMap->rect() );
1545  if ( mGridUnit == CM )
1546  {
1547  annotationScale = 0.1;
1548  gridIntervalY *= 10;
1549  gridOffsetY *= 10;
1550  }
1551  break;
1552  }
1553 
1554  case MapUnit:
1555  case DynamicPageSizeBased:
1556  break;
1557  }
1558 
1559  //consider to round up to the next step in case the left boundary is > 0
1560  const double roundCorrection = mapBoundingRect.top() > 0 ? 1.0 : 0.0;
1561  double currentLevel = static_cast< int >( ( mapBoundingRect.top() - gridOffsetY ) / gridIntervalY + roundCorrection ) * gridIntervalY + gridOffsetY;
1562 
1563  int gridLineCount = 0;
1564  if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || ( mGridUnit != MapUnit && mGridUnit != DynamicPageSizeBased ) )
1565  {
1566  //no rotation. Do it 'the easy way'
1567 
1568  double yCanvasCoord;
1569  while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1570  {
1571  yCanvasCoord = mMap->rect().height() * ( 1 - ( currentLevel - mapBoundingRect.top() ) / mapBoundingRect.height() );
1572  GridLine newLine;
1573  newLine.coordinate = currentLevel * annotationScale;
1574  newLine.coordinateType = AnnotationCoordinate::Latitude;
1575  newLine.line = QPolygonF() << QPointF( 0, yCanvasCoord ) << QPointF( mMap->rect().width(), yCanvasCoord );
1576  mGridLines.append( newLine );
1577  currentLevel += gridIntervalY;
1578  gridLineCount++;
1579  }
1580  return 0;
1581  }
1582 
1583  //the four border lines
1584  QVector<QLineF> borderLines;
1585  borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1586  borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1587  borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1588  borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1589 
1590  QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1591 
1592  while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1593  {
1594  intersectionList.clear();
1595  const QLineF gridLine( mapBoundingRect.left(), currentLevel, mapBoundingRect.right(), currentLevel );
1596 
1597  QVector<QLineF>::const_iterator it = borderLines.constBegin();
1598  for ( ; it != borderLines.constEnd(); ++it )
1599  {
1600  QPointF intersectionPoint;
1601 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
1602  if ( it->intersect( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1603 #else
1604  if ( it->intersects( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1605 #endif
1606  {
1607  intersectionList.push_back( intersectionPoint );
1608  if ( intersectionList.size() >= 2 )
1609  {
1610  break; //we already have two intersections, skip further tests
1611  }
1612  }
1613  }
1614 
1615  if ( intersectionList.size() >= 2 )
1616  {
1617  GridLine newLine;
1618  newLine.coordinate = currentLevel;
1619  newLine.coordinateType = AnnotationCoordinate::Latitude;
1620  newLine.line = QPolygonF() << mMap->mapToItemCoords( intersectionList.at( 0 ) ) << mMap->mapToItemCoords( intersectionList.at( 1 ) );
1621  mGridLines.append( newLine );
1622  gridLineCount++;
1623  }
1624  currentLevel += gridIntervalY;
1625  }
1626 
1627 
1628  return 0;
1629 }
1630 
1631 int QgsLayoutItemMapGrid::yGridLines() const
1632 {
1633  if ( !mMap || mEvaluatedIntervalX <= 0.0 )
1634  {
1635  return 1;
1636  }
1637 
1638  QPolygonF mapPolygon = mMap->transformedMapPolygon();
1639  QRectF mapBoundingRect = mapPolygon.boundingRect();
1640  double gridIntervalX = mEvaluatedIntervalX;
1641  double gridOffsetX = mEvaluatedOffsetX;
1642  double annotationScale = 1.0;
1643  switch ( mGridUnit )
1644  {
1645  case CM:
1646  case MM:
1647  {
1648  mapBoundingRect = mMap->rect();
1649  mapPolygon = QPolygonF( mMap->rect() );
1650  if ( mGridUnit == CM )
1651  {
1652  annotationScale = 0.1;
1653  gridIntervalX *= 10;
1654  gridOffsetX *= 10;
1655  }
1656  break;
1657  }
1658 
1659  case MapUnit:
1660  case DynamicPageSizeBased:
1661  break;
1662  }
1663 
1664  //consider to round up to the next step in case the left boundary is > 0
1665  const double roundCorrection = mapBoundingRect.left() > 0 ? 1.0 : 0.0;
1666  double currentLevel = static_cast< int >( ( mapBoundingRect.left() - gridOffsetX ) / gridIntervalX + roundCorrection ) * gridIntervalX + gridOffsetX;
1667 
1668  int gridLineCount = 0;
1669  if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || ( mGridUnit != MapUnit && mGridUnit != DynamicPageSizeBased ) )
1670  {
1671  //no rotation. Do it 'the easy way'
1672  double xCanvasCoord;
1673  while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1674  {
1675  xCanvasCoord = mMap->rect().width() * ( currentLevel - mapBoundingRect.left() ) / mapBoundingRect.width();
1676 
1677  GridLine newLine;
1678  newLine.coordinate = currentLevel * annotationScale;
1679  newLine.coordinateType = AnnotationCoordinate::Longitude;
1680  newLine.line = QPolygonF() << QPointF( xCanvasCoord, 0 ) << QPointF( xCanvasCoord, mMap->rect().height() );
1681  mGridLines.append( newLine );
1682  currentLevel += gridIntervalX;
1683  gridLineCount++;
1684  }
1685  return 0;
1686  }
1687 
1688  //the four border lines
1689  QVector<QLineF> borderLines;
1690  borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1691  borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1692  borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1693  borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1694 
1695  QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1696 
1697  while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1698  {
1699  intersectionList.clear();
1700  const QLineF gridLine( currentLevel, mapBoundingRect.bottom(), currentLevel, mapBoundingRect.top() );
1701 
1702  QVector<QLineF>::const_iterator it = borderLines.constBegin();
1703  for ( ; it != borderLines.constEnd(); ++it )
1704  {
1705  QPointF intersectionPoint;
1706 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
1707  if ( it->intersect( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1708 #else
1709  if ( it->intersects( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1710 #endif
1711  {
1712  intersectionList.push_back( intersectionPoint );
1713  if ( intersectionList.size() >= 2 )
1714  {
1715  break; //we already have two intersections, skip further tests
1716  }
1717  }
1718  }
1719 
1720  if ( intersectionList.size() >= 2 )
1721  {
1722  GridLine newLine;
1723  newLine.coordinate = currentLevel;
1724  newLine.coordinateType = AnnotationCoordinate::Longitude;
1725  newLine.line = QPolygonF() << mMap->mapToItemCoords( intersectionList.at( 0 ) ) << mMap->mapToItemCoords( intersectionList.at( 1 ) );
1726  mGridLines.append( newLine );
1727  gridLineCount++;
1728  }
1729  currentLevel += gridIntervalX;
1730  }
1731 
1732  return 0;
1733 }
1734 
1735 int QgsLayoutItemMapGrid::xGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t ) const
1736 {
1737  if ( !mMap || mEvaluatedIntervalY <= 0.0 )
1738  {
1739  return 1;
1740  }
1741 
1742  const double roundCorrection = bbox.yMaximum() > 0 ? 1.0 : 0.0;
1743  double currentLevel = static_cast< int >( ( bbox.yMaximum() - mEvaluatedOffsetY ) / mEvaluatedIntervalY + roundCorrection ) * mEvaluatedIntervalY + mEvaluatedOffsetY;
1744 
1745  const double minX = bbox.xMinimum();
1746  const double maxX = bbox.xMaximum();
1747  double step = ( maxX - minX ) / 20;
1748 
1749  bool crosses180 = false;
1750  bool crossed180 = false;
1751  if ( mCRS.isGeographic() && ( minX > maxX ) )
1752  {
1753  //handle 180 degree longitude crossover
1754  crosses180 = true;
1755  step = ( maxX + 360.0 - minX ) / 20;
1756  }
1757 
1758  if ( qgsDoubleNear( step, 0.0 ) )
1759  return 1;
1760 
1761  int gridLineCount = 0;
1762  while ( currentLevel >= bbox.yMinimum() && gridLineCount < MAX_GRID_LINES )
1763  {
1764  QPolygonF gridLine;
1765  double currentX = minX;
1766  bool cont = true;
1767  while ( cont )
1768  {
1769  if ( ( !crosses180 || crossed180 ) && ( currentX > maxX ) )
1770  {
1771  cont = false;
1772  }
1773 
1774  try
1775  {
1776  const QgsPointXY mapPoint = t.transform( currentX, currentLevel ); //transform back to map crs
1777  gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) ); //transform back to composer coords
1778  }
1779  catch ( QgsCsException &cse )
1780  {
1781  Q_UNUSED( cse )
1782  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1783  }
1784 
1785  currentX += step;
1786  if ( crosses180 && currentX > 180.0 )
1787  {
1788  currentX -= 360.0;
1789  crossed180 = true;
1790  }
1791  }
1792  crossed180 = false;
1793 
1794  const QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1795  QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1796  for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1797  {
1798  if ( !( *lineIt ).isEmpty() )
1799  {
1800  GridLine newLine;
1801  newLine.coordinate = currentLevel;
1802  newLine.coordinateType = AnnotationCoordinate::Latitude;
1803  newLine.line = QPolygonF( *lineIt );
1804  mGridLines.append( newLine );
1805  gridLineCount++;
1806  }
1807  }
1808  currentLevel -= mEvaluatedIntervalY;
1809  }
1810 
1811  return 0;
1812 }
1813 
1814 int QgsLayoutItemMapGrid::yGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t ) const
1815 {
1816  if ( !mMap || mEvaluatedIntervalX <= 0.0 )
1817  {
1818  return 1;
1819  }
1820 
1821  const double roundCorrection = bbox.xMinimum() > 0 ? 1.0 : 0.0;
1822  double currentLevel = static_cast< int >( ( bbox.xMinimum() - mEvaluatedOffsetX ) / mEvaluatedIntervalX + roundCorrection ) * mEvaluatedIntervalX + mEvaluatedOffsetX;
1823 
1824  const double minY = bbox.yMinimum();
1825  const double maxY = bbox.yMaximum();
1826  const double step = ( maxY - minY ) / 20;
1827 
1828  if ( qgsDoubleNear( step, 0.0 ) )
1829  return 1;
1830 
1831  bool crosses180 = false;
1832  bool crossed180 = false;
1833  if ( mCRS.isGeographic() && ( bbox.xMinimum() > bbox.xMaximum() ) )
1834  {
1835  //handle 180 degree longitude crossover
1836  crosses180 = true;
1837  }
1838 
1839  int gridLineCount = 0;
1840  while ( ( currentLevel <= bbox.xMaximum() || ( crosses180 && !crossed180 ) ) && gridLineCount < MAX_GRID_LINES )
1841  {
1842  QPolygonF gridLine;
1843  double currentY = minY;
1844  bool cont = true;
1845  while ( cont )
1846  {
1847  if ( currentY > maxY )
1848  {
1849  cont = false;
1850  }
1851  try
1852  {
1853  //transform back to map crs
1854  const QgsPointXY mapPoint = t.transform( currentLevel, currentY );
1855  //transform back to composer coords
1856  gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) );
1857  }
1858  catch ( QgsCsException &cse )
1859  {
1860  Q_UNUSED( cse )
1861  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1862  }
1863 
1864  currentY += step;
1865  }
1866  //clip grid line to map polygon
1867  const QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1868  QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1869  for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1870  {
1871  if ( !( *lineIt ).isEmpty() )
1872  {
1873  GridLine newLine;
1874  newLine.coordinate = currentLevel;
1875  newLine.coordinateType = AnnotationCoordinate::Longitude;
1876  newLine.line = QPolygonF( *lineIt );
1877  mGridLines.append( newLine );
1878  gridLineCount++;
1879  }
1880  }
1881  currentLevel += mEvaluatedIntervalX;
1882  if ( crosses180 && currentLevel > 180.0 )
1883  {
1884  currentLevel -= 360.0;
1885  crossed180 = true;
1886  }
1887  }
1888 
1889  return 0;
1890 }
1891 
1892 bool QgsLayoutItemMapGrid::shouldShowDivisionForSide( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::BorderSide side ) const
1893 {
1894  switch ( side )
1895  {
1897  return testFrameSideFlag( QgsLayoutItemMapGrid::FrameLeft ) && shouldShowForDisplayMode( coordinate, mEvaluatedLeftFrameDivisions );
1899  return testFrameSideFlag( QgsLayoutItemMapGrid::FrameRight ) && shouldShowForDisplayMode( coordinate, mEvaluatedRightFrameDivisions );
1901  return testFrameSideFlag( QgsLayoutItemMapGrid::FrameTop ) && shouldShowForDisplayMode( coordinate, mEvaluatedTopFrameDivisions );
1903  return testFrameSideFlag( QgsLayoutItemMapGrid::FrameBottom ) && shouldShowForDisplayMode( coordinate, mEvaluatedBottomFrameDivisions );
1904  }
1905  return false; // no warnings
1906 }
1907 
1908 bool QgsLayoutItemMapGrid::shouldShowAnnotationForSide( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::BorderSide side ) const
1909 {
1910  switch ( side )
1911  {
1913  return shouldShowForDisplayMode( coordinate, mEvaluatedLeftGridAnnotationDisplay );
1915  return shouldShowForDisplayMode( coordinate, mEvaluatedRightGridAnnotationDisplay );
1917  return shouldShowForDisplayMode( coordinate, mEvaluatedTopGridAnnotationDisplay );
1919  return shouldShowForDisplayMode( coordinate, mEvaluatedBottomGridAnnotationDisplay );
1920  }
1921  return false; // no warnings
1922 }
1923 
1924 bool QgsLayoutItemMapGrid::shouldShowForDisplayMode( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::DisplayMode mode ) const
1925 {
1926  return mode == QgsLayoutItemMapGrid::ShowAll
1929 }
1930 
1931 
1933 {
1934  if ( ddValue.compare( QLatin1String( "x_only" ), Qt::CaseInsensitive ) == 0 )
1936  else if ( ddValue.compare( QLatin1String( "y_only" ), Qt::CaseInsensitive ) == 0 )
1938  else if ( ddValue.compare( QLatin1String( "disabled" ), Qt::CaseInsensitive ) == 0 )
1940  else if ( ddValue.compare( QLatin1String( "all" ), Qt::CaseInsensitive ) == 0 )
1942  else
1943  return defValue;
1944 }
1945 
1946 
1947 void QgsLayoutItemMapGrid::refreshDataDefinedProperties()
1948 {
1950 
1951  // if we are changing the grid interval or offset, then we also have to mark the transform as dirty
1952  mTransformDirty = mTransformDirty
1957 
1959  switch ( mGridUnit )
1960  {
1961  case MapUnit:
1962  case MM:
1963  case CM:
1964  {
1965  mEvaluatedIntervalX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalX, context, mGridIntervalX );
1966  mEvaluatedIntervalY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalY, context, mGridIntervalY );
1967  break;
1968  }
1969 
1970  case DynamicPageSizeBased:
1971  {
1972  if ( mMaximumIntervalWidth < mMinimumIntervalWidth )
1973  {
1974  mEvaluatedEnabled = false;
1975  }
1976  else
1977  {
1978  const double mapWidthMm = mLayout->renderContext().measurementConverter().convert( mMap->sizeWithUnits(), QgsUnitTypes::LayoutMillimeters ).width();
1979  const double mapWidthMapUnits = mapWidth();
1980  const double minUnitsPerSeg = ( mMinimumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1981  const double maxUnitsPerSeg = ( mMaximumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1982  const double interval = QgsLayoutUtils::calculatePrettySize( minUnitsPerSeg, maxUnitsPerSeg );
1983  mEvaluatedIntervalX = interval;
1984  mEvaluatedIntervalY = interval;
1985  mTransformDirty = true;
1986  }
1987  break;
1988  }
1989  }
1990  mEvaluatedOffsetX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetX, context, mGridOffsetX );
1991  mEvaluatedOffsetY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetY, context, mGridOffsetY );
1992  mEvaluatedGridFrameWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameSize, context, mGridFrameWidth );
1993  mEvaluatedGridFrameMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameMargin, context, mGridFrameMargin );
1994  mEvaluatedAnnotationFrameDistance = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridLabelDistance, context, mAnnotationFrameDistance );
1995  mEvaluatedCrossLength = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridCrossSize, context, mCrossLength );
1996  mEvaluatedGridFrameLineThickness = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameLineThickness, context, mGridFramePenThickness );
1997  mEvaluatedLeftGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayLeft, context ), mLeftGridAnnotationDisplay );
1998  mEvaluatedRightGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayRight, context ), mRightGridAnnotationDisplay );
1999  mEvaluatedTopGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayTop, context ), mTopGridAnnotationDisplay );
2000  mEvaluatedBottomGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayBottom, context ), mBottomGridAnnotationDisplay );
2001  mEvaluatedLeftFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsLeft, context ), mLeftFrameDivisions );
2002  mEvaluatedRightFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsRight, context ), mRightFrameDivisions );
2003  mEvaluatedTopFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsTop, context ), mTopFrameDivisions );
2004  mEvaluatedBottomFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsBottom, context ), mBottomFrameDivisions );
2005 
2006 }
2007 
2008 double QgsLayoutItemMapGrid::mapWidth() const
2009 {
2010  if ( !mMap )
2011  {
2012  return 0.0;
2013  }
2014 
2015  const QgsRectangle mapExtent = mMap->extent();
2016  const QgsUnitTypes::DistanceUnit distanceUnit = mCRS.isValid() ? mCRS.mapUnits() : mMap->crs().mapUnits();
2017  if ( distanceUnit == QgsUnitTypes::DistanceUnknownUnit )
2018  {
2019  return mapExtent.width();
2020  }
2021  else
2022  {
2023  QgsDistanceArea da;
2024 
2025  da.setSourceCrs( mMap->crs(), mLayout->project()->transformContext() );
2026  da.setEllipsoid( mLayout->project()->ellipsoid() );
2027 
2029  double measure = da.measureLine( QgsPointXY( mapExtent.xMinimum(), mapExtent.yMinimum() ),
2030  QgsPointXY( mapExtent.xMaximum(), mapExtent.yMinimum() ) );
2031  measure /= QgsUnitTypes::fromUnitToUnitFactor( distanceUnit, units );
2032  return measure;
2033  }
2034 }
2035 
2036 bool sortByDistance( QPair<qreal, QgsLayoutItemMapGrid::BorderSide> a, QPair<qreal, QgsLayoutItemMapGrid::BorderSide> b )
2037 {
2038  return a.first < b.first;
2039 }
2040 
2041 QgsLayoutItemMapGrid::BorderSide QgsLayoutItemMapGrid::borderForLineCoord( QPointF p, const AnnotationCoordinate coordinateType ) const
2042 {
2043  if ( !mMap )
2044  {
2046  }
2047 
2048  const double tolerance = std::max( mMap->frameEnabled() ? mMap->pen().widthF() : 0.0, 1.0 );
2049 
2050  //check for corner coordinates
2051  if ( ( p.y() <= tolerance && p.x() <= tolerance ) // top left
2052  || ( p.y() <= tolerance && p.x() >= ( mMap->rect().width() - tolerance ) ) //top right
2053  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() <= tolerance ) //bottom left
2054  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() >= ( mMap->rect().width() - tolerance ) ) //bottom right
2055  )
2056  {
2057  //coordinate is in corner - fall back to preferred side for coordinate type
2058  if ( coordinateType == QgsLayoutItemMapGrid::Latitude )
2059  {
2060  if ( p.x() <= tolerance )
2061  {
2063  }
2064  else
2065  {
2067  }
2068  }
2069  else
2070  {
2071  if ( p.y() <= tolerance )
2072  {
2074  }
2075  else
2076  {
2078  }
2079  }
2080  }
2081 
2082  //otherwise, guess side based on closest map side to point
2083  QList< QPair<qreal, QgsLayoutItemMapGrid::BorderSide > > distanceToSide;
2084  distanceToSide << qMakePair( p.x(), QgsLayoutItemMapGrid::Left );
2085  distanceToSide << qMakePair( mMap->rect().width() - p.x(), QgsLayoutItemMapGrid::Right );
2086  distanceToSide << qMakePair( p.y(), QgsLayoutItemMapGrid::Top );
2087  distanceToSide << qMakePair( mMap->rect().height() - p.y(), QgsLayoutItemMapGrid::Bottom );
2088 
2089  std::sort( distanceToSide.begin(), distanceToSide.end(), sortByDistance );
2090  return distanceToSide.at( 0 ).second;
2091 }
2092 
2094 {
2095  mGridLineSymbol.reset( symbol );
2096 }
2097 
2099 {
2100  return mGridLineSymbol.get();
2101 }
2102 
2104 {
2105  return mGridLineSymbol.get();
2106 }
2107 
2109 {
2110  mGridMarkerSymbol.reset( symbol );
2111 }
2112 
2114 {
2115  return mGridMarkerSymbol.get();
2116 }
2117 
2119 {
2120  return mGridMarkerSymbol.get();
2121 }
2122 
2124 {
2125  mAnnotationFormat.setFont( font );
2126  if ( font.pointSizeF() > 0 )
2127  {
2128  mAnnotationFormat.setSize( font.pointSizeF() );
2129  mAnnotationFormat.setSizeUnit( QgsUnitTypes::RenderPoints );
2130  }
2131  else if ( font.pixelSize() > 0 )
2132  {
2133  mAnnotationFormat.setSize( font.pixelSize() );
2134  mAnnotationFormat.setSizeUnit( QgsUnitTypes::RenderPixels );
2135  }
2136 }
2137 
2139 {
2140  return mAnnotationFormat.toQFont();
2141 }
2142 
2144 {
2145  mAnnotationFormat.setColor( color );
2146 }
2147 
2149 {
2150  return mAnnotationFormat.color();
2151 }
2152 
2154 {
2155  switch ( border )
2156  {
2158  mLeftGridAnnotationDisplay = display;
2159  break;
2161  mRightGridAnnotationDisplay = display;
2162  break;
2164  mTopGridAnnotationDisplay = display;
2165  break;
2167  mBottomGridAnnotationDisplay = display;
2168  break;
2169  }
2170 
2171  refreshDataDefinedProperties();
2172 
2173  if ( mMap )
2174  {
2176  mMap->update();
2177  }
2178 }
2179 
2181 {
2182  switch ( border )
2183  {
2185  return mLeftGridAnnotationDisplay;
2187  return mRightGridAnnotationDisplay;
2189  return mTopGridAnnotationDisplay;
2191  return mBottomGridAnnotationDisplay;
2192  }
2193  return mBottomGridAnnotationDisplay; // no warnings
2194 }
2195 
2197 {
2198  double top = 0.0;
2199  double right = 0.0;
2200  double bottom = 0.0;
2201  double left = 0.0;
2202  calculateMaxExtension( top, right, bottom, left );
2203  return std::max( std::max( std::max( top, right ), bottom ), left );
2204 }
2205 
2206 void QgsLayoutItemMapGrid::calculateMaxExtension( double &top, double &right, double &bottom, double &left ) const
2207 {
2208  top = 0.0;
2209  right = 0.0;
2210  bottom = 0.0;
2211  left = 0.0;
2212 
2213  if ( !mMap || !mEvaluatedEnabled )
2214  {
2215  return;
2216  }
2217 
2218  //setup render context
2220  const QgsExpressionContext expressionContext = createExpressionContext();
2221  context.setExpressionContext( expressionContext );
2222 
2223  GridExtension extension;
2224 
2225  //collect grid lines
2226  switch ( mGridUnit )
2227  {
2228  case MapUnit:
2229  case DynamicPageSizeBased:
2230  {
2231  if ( mCRS.isValid() && mCRS != mMap->crs() )
2232  {
2233  drawGridCrsTransform( context, 0, true );
2234  break;
2235  }
2236  }
2237  FALLTHROUGH
2238  case CM:
2239  case MM:
2240  drawGridNoTransform( context, 0, true );
2241  break;
2242  }
2243 
2244  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame || mShowGridAnnotation )
2245  updateGridLinesAnnotationsPositions();
2246 
2247  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
2248  {
2249  drawGridFrame( nullptr, &extension );
2250  }
2251 
2252  if ( mShowGridAnnotation )
2253  {
2254  drawCoordinateAnnotations( context, context.expressionContext(), &extension );
2255  }
2256 
2257  top = extension.top;
2258  right = extension.right;
2259  bottom = extension.bottom;
2260  left = extension.left;
2261 }
2262 
2264 {
2266  refreshDataDefinedProperties();
2267 }
2268 
2270 {
2271  if ( unit == mGridUnit )
2272  {
2273  return;
2274  }
2275  mGridUnit = unit;
2276  mTransformDirty = true;
2277 }
2278 
2279 void QgsLayoutItemMapGrid::setIntervalX( const double interval )
2280 {
2281  if ( qgsDoubleNear( interval, mGridIntervalX ) )
2282  {
2283  return;
2284  }
2285  mGridIntervalX = interval;
2286  mTransformDirty = true;
2287  refreshDataDefinedProperties();
2288 }
2289 
2290 void QgsLayoutItemMapGrid::setIntervalY( const double interval )
2291 {
2292  if ( qgsDoubleNear( interval, mGridIntervalY ) )
2293  {
2294  return;
2295  }
2296  mGridIntervalY = interval;
2297  mTransformDirty = true;
2298  refreshDataDefinedProperties();
2299 }
2300 
2301 void QgsLayoutItemMapGrid::setOffsetX( const double offset )
2302 {
2303  if ( qgsDoubleNear( offset, mGridOffsetX ) )
2304  {
2305  return;
2306  }
2307  mGridOffsetX = offset;
2308  mTransformDirty = true;
2309  refreshDataDefinedProperties();
2310 }
2311 
2312 void QgsLayoutItemMapGrid::setOffsetY( const double offset )
2313 {
2314  if ( qgsDoubleNear( offset, mGridOffsetY ) )
2315  {
2316  return;
2317  }
2318  mGridOffsetY = offset;
2319  mTransformDirty = true;
2320  refreshDataDefinedProperties();
2321 }
2322 
2324 {
2325  if ( qgsDoubleNear( minWidth, mMinimumIntervalWidth ) )
2326  {
2327  return;
2328  }
2329  mMinimumIntervalWidth = minWidth;
2330  mTransformDirty = true;
2331  refreshDataDefinedProperties();
2332 }
2333 
2335 {
2336  if ( qgsDoubleNear( maxWidth, mMaximumIntervalWidth ) )
2337  {
2338  return;
2339  }
2340  mMaximumIntervalWidth = maxWidth;
2341  mTransformDirty = true;
2342  refreshDataDefinedProperties();
2343 }
2344 
2346 {
2347  if ( style == mGridStyle )
2348  {
2349  return;
2350  }
2351  mGridStyle = style;
2352  mTransformDirty = true;
2353 }
2354 
2355 void QgsLayoutItemMapGrid::setCrossLength( const double length )
2356 {
2357  mCrossLength = length;
2358  refreshDataDefinedProperties();
2359 }
2360 
2362 {
2363  switch ( border )
2364  {
2366  mLeftGridAnnotationDirection = direction;
2367  break;
2369  mRightGridAnnotationDirection = direction;
2370  break;
2372  mTopGridAnnotationDirection = direction;
2373  break;
2375  mBottomGridAnnotationDirection = direction;
2376  break;
2377  }
2378 
2379  if ( mMap )
2380  {
2382  mMap->update();
2383  }
2384 }
2385 
2386 void QgsLayoutItemMapGrid::setFrameSideFlags( FrameSideFlags flags )
2387 {
2388  mGridFrameSides = flags;
2389 }
2390 
2392 {
2393  if ( on )
2394  mGridFrameSides |= flag;
2395  else
2396  mGridFrameSides &= ~flag;
2397 }
2398 
2399 QgsLayoutItemMapGrid::FrameSideFlags QgsLayoutItemMapGrid::frameSideFlags() const
2400 {
2401  return mGridFrameSides;
2402 }
2403 
2405 {
2407  context.appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
2408  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), 0, true ) );
2409  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), "x", true ) );
2410  context.setHighlightedVariables( QStringList() << QStringLiteral( "grid_number" ) << QStringLiteral( "grid_axis" ) );
2411  return context;
2412 }
2413 
2415 {
2416  if ( mGridLineSymbol )
2417  {
2418  QgsStyleSymbolEntity entity( mGridLineSymbol.get() );
2419  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2420  return false;
2421  }
2422  if ( mGridMarkerSymbol )
2423  {
2424  QgsStyleSymbolEntity entity( mGridMarkerSymbol.get() );
2425  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2426  return false;
2427  }
2428 
2429  return true;
2430 }
2431 
2433 {
2434  mTransformDirty = true;
2435  refreshDataDefinedProperties();
2437  mMap->update();
2438 }
2439 
2441 {
2442  return mGridFrameSides.testFlag( flag );
2443 }
2444 
2445 void QgsLayoutItemMapGrid::setFrameWidth( const double width )
2446 {
2447  mGridFrameWidth = width;
2448  refreshDataDefinedProperties();
2449 }
2450 
2451 void QgsLayoutItemMapGrid::setFrameMargin( const double margin )
2452 {
2453  mGridFrameMargin = margin;
2454  refreshDataDefinedProperties();
2455 }
2456 
2457 void QgsLayoutItemMapGrid::setFramePenSize( const double width )
2458 {
2459  mGridFramePenThickness = width;
2460  refreshDataDefinedProperties();
2461 }
2462 
2464 {
2465  mLeftGridAnnotationDirection = direction;
2466  mRightGridAnnotationDirection = direction;
2467  mTopGridAnnotationDirection = direction;
2468  mBottomGridAnnotationDirection = direction;
2469 }
2470 
2472 {
2473  switch ( border )
2474  {
2476  mLeftGridAnnotationPosition = position;
2477  break;
2479  mRightGridAnnotationPosition = position;
2480  break;
2482  mTopGridAnnotationPosition = position;
2483  break;
2485  mBottomGridAnnotationPosition = position;
2486  break;
2487  }
2488 
2489  if ( mMap )
2490  {
2492  mMap->update();
2493  }
2494 }
2495 
2497 {
2498  switch ( border )
2499  {
2501  return mLeftGridAnnotationPosition;
2503  return mRightGridAnnotationPosition;
2505  return mTopGridAnnotationPosition;
2507  return mBottomGridAnnotationPosition;
2508  }
2509  return mLeftGridAnnotationPosition; // no warnings
2510 }
2511 
2513 {
2514  mAnnotationFrameDistance = distance;
2515  refreshDataDefinedProperties();
2516 }
2517 
2519 {
2520  if ( !mMap )
2521  {
2522  return mLeftGridAnnotationDirection;
2523  }
2524 
2525  switch ( border )
2526  {
2528  return mLeftGridAnnotationDirection;
2530  return mRightGridAnnotationDirection;
2532  return mTopGridAnnotationDirection;
2534  return mBottomGridAnnotationDirection;
2535  }
2536  return mLeftGridAnnotationDirection; // no warnings
2537 }
2538 
2540 {
2541  switch ( border )
2542  {
2544  mLeftFrameDivisions = divisions;
2545  break;
2547  mRightFrameDivisions = divisions;
2548  break;
2550  mTopFrameDivisions = divisions;
2551  break;
2553  mBottomFrameDivisions = divisions;
2554  break;
2555  }
2556 
2557  refreshDataDefinedProperties();
2558 
2559  if ( mMap )
2560  {
2561  mMap->update();
2562  }
2563 }
2564 
2566 {
2567  switch ( border )
2568  {
2570  return mLeftFrameDivisions;
2572  return mRightFrameDivisions;
2574  return mTopFrameDivisions;
2576  return mBottomFrameDivisions;
2577  }
2578  return mLeftFrameDivisions; // no warnings
2579 }
2580 
2581 int QgsLayoutItemMapGrid::crsGridParams( QgsRectangle &crsRect, QgsCoordinateTransform &inverseTransform ) const
2582 {
2583  if ( !mMap )
2584  {
2585  return 1;
2586  }
2587 
2588  try
2589  {
2590  const QgsCoordinateTransform tr( mMap->crs(), mCRS, mLayout->project() );
2591  const QPolygonF mapPolygon = mMap->transformedMapPolygon();
2592  const QRectF mbr = mapPolygon.boundingRect();
2593  const QgsRectangle mapBoundingRect( mbr.left(), mbr.bottom(), mbr.right(), mbr.top() );
2594 
2595 
2596  if ( mCRS.isGeographic() )
2597  {
2598  //handle crossing the 180 degree longitude line
2599  QgsPointXY lowerLeft( mapBoundingRect.xMinimum(), mapBoundingRect.yMinimum() );
2600  QgsPointXY upperRight( mapBoundingRect.xMaximum(), mapBoundingRect.yMaximum() );
2601 
2602  lowerLeft = tr.transform( lowerLeft.x(), lowerLeft.y() );
2603  upperRight = tr.transform( upperRight.x(), upperRight.y() );
2604 
2605  if ( lowerLeft.x() > upperRight.x() )
2606  {
2607  //we've crossed the line
2608  crsRect = tr.transformBoundingBox( mapBoundingRect, Qgis::TransformDirection::Forward, true );
2609  }
2610  else
2611  {
2612  //didn't cross the line
2613  crsRect = tr.transformBoundingBox( mapBoundingRect );
2614  }
2615  }
2616  else
2617  {
2618  crsRect = tr.transformBoundingBox( mapBoundingRect );
2619  }
2620 
2621  inverseTransform = QgsCoordinateTransform( mCRS, mMap->crs(), mLayout->project() );
2622  }
2623  catch ( QgsCsException &cse )
2624  {
2625  Q_UNUSED( cse )
2626  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
2627  return 1;
2628  }
2629  return 0;
2630 }
2631 
2632 QList<QPolygonF> QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, const QgsRectangle &rect )
2633 {
2634  const QgsGeometry lineGeom = QgsGeometry::fromQPolygonF( line );
2635  const QgsGeometry rectGeom = QgsGeometry::fromRect( rect );
2636 
2637  const QgsGeometry intersected = lineGeom.intersection( rectGeom );
2638  const QVector<QgsGeometry> intersectedParts = intersected.asGeometryCollection();
2639 
2640  QList<QPolygonF> trimmedLines;
2641  QVector<QgsGeometry>::const_iterator geomIt = intersectedParts.constBegin();
2642  for ( ; geomIt != intersectedParts.constEnd(); ++geomIt )
2643  {
2644  trimmedLines << ( *geomIt ).asQPolygonF();
2645  }
2646  return trimmedLines;
2647 }
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
bool valueAsBool(int key, const QgsExpressionContext &context, bool defaultValue=false, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as an boolean.
double valueAsDouble(int key, const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a double.
QString valueAsString(int key, const QgsExpressionContext &context, const QString &defaultString=QString(), bool *ok=nullptr) const
Calculates the current value of the property with the specified key and interprets it as a string.
Format
Available formats for displaying coordinates.
@ FormatDecimalDegrees
Decimal degrees, eg 30.7555 degrees.
@ FormatDegreesMinutes
Degrees and decimal minutes, eg 30degrees 45.55'.
@ FormatDegreesMinutesSeconds
Degrees, minutes and seconds, eg 30 degrees 45'30".
static QString formatY(double y, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats a y coordinate value according to the specified parameters.
@ FlagDegreesUseStringSuffix
Include a direction suffix (eg 'N', 'E', 'S' or 'W'), otherwise a "-" prefix is used for west and sou...
@ FlagDegreesPadMinutesSeconds
Pad minute and second values with leading zeros, eg '05' instead of '5'.
static QString formatX(double x, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats an x coordinate value according to the specified parameters.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
Class for doing transforms between two map coordinate systems.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
A general purpose distance and area calculator, capable of performing ellipsoid based calculations.
double measureLine(const QVector< QgsPointXY > &points) const
Measures the length of a line with multiple segments.
void setSourceCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets source spatial reference system crs.
bool setEllipsoid(const QString &ellipsoid)
Sets the ellipsoid by its acronym.
QgsUnitTypes::DistanceUnit lengthUnits() const
Returns the units of distance for length calculations made by this object.
QString what() const
Definition: qgsexception.h:48
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user.
Class for parsing and evaluation of expressions (formerly called "search strings").
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:125
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
static QgsGeometry fromPolylineXY(const QgsPolylineXY &polyline)
Creates a new LineString geometry from a list of QgsPointXY points.
QgsPoint vertexAt(int atVertex) const
Returns coordinates of a vertex.
QVector< QgsGeometry > asGeometryCollection() const
Returns contents of the geometry as a list of geometries.
Q_GADGET bool isNull
Definition: qgsgeometry.h:127
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
QgsGeometry intersection(const QgsGeometry &geometry) const
Returns a geometry representing the points shared by this geometry and other.
QList< QgsLayoutItemMapGrid * > asList() const
Returns a list of QgsLayoutItemMapGrids contained by the stack.
void calculateMaxGridExtension(double &top, double &right, double &bottom, double &left) const
Calculates the maximum distance grids within the stack extend beyond the QgsLayoutItemMap's item rect...
void removeGrid(const QString &gridId)
Removes a grid with matching gridId from the stack and deletes the corresponding QgsLayoutItemMapGrid...
double maxGridExtension() const
Calculates the maximum distance grids within the stack extend beyond the QgsLayoutItemMap's item rect...
void addGrid(QgsLayoutItemMapGrid *grid)
Adds a new map grid to the stack and takes ownership of the grid.
QgsLayoutItemMapGrid * grid(const QString &gridId) const
Returns a reference to a grid with matching gridId within the stack.
bool readXml(const QDomElement &elem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets the item stack's state from a DOM document, where element is a DOM node corresponding to a 'Layo...
QgsLayoutItemMapGrid & operator[](int index)
Returns a reference to a grid at the specified index within the stack.
void moveGridUp(const QString &gridId)
Moves a grid with matching gridId up the stack, causing it to be rendered above other grids.
void moveGridDown(const QString &gridId)
Moves a grid with matching gridId down the stack, causing it to be rendered below other grids.
QgsLayoutItemMapGridStack(QgsLayoutItemMap *map)
Constructor for QgsLayoutItemMapGridStack, attached to the specified map.
An individual grid which is drawn above the map content in a QgsLayoutItemMap.
void setFrameSideFlags(QgsLayoutItemMapGrid::FrameSideFlags flags)
Sets flags for grid frame sides.
GridStyle
Grid drawing style.
@ Markers
Draw markers at intersections of grid lines.
@ Cross
Draw line crosses at intersections of grid lines.
@ FrameAnnotationsOnly
No grid lines over the map, only draw frame and annotations.
void calculateMaxExtension(double &top, double &right, double &bottom, double &left) const
Calculates the maximum distance the grid extends beyond the QgsLayoutItemMap's item rect.
GridUnit
Unit for grid values.
@ CM
Grid units in centimeters.
@ MM
Grid units in millimeters.
@ DynamicPageSizeBased
Dynamically sized, based on a on-page size range.
@ MapUnit
Grid units follow map units.
bool writeXml(QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context) const override
Stores map item state in a DOM element, where element is the DOM element corresponding to a 'LayoutMa...
void refresh() override
Refreshes the object, causing a recalculation of any property overrides.
GridStyle style() const
Returns the grid's style, which controls how the grid is drawn over the map's contents.
void setFrameSideFlag(QgsLayoutItemMapGrid::FrameSideFlag flag, bool on=true)
Sets whether the grid frame is drawn for a certain side of the map item.
FrameSideFlag
Flags for controlling which side of the map a frame is drawn on.
@ FrameTop
Top side of map.
@ FrameBottom
Bottom side of map.
@ FrameLeft
Left side of map.
@ FrameRight
Right side of map.
Q_DECL_DEPRECATED void setAnnotationFontColor(const QColor &color)
Sets the font color used for drawing grid annotations.
void draw(QPainter *painter) override
Draws the item on to a destination painter.
void setIntervalY(double interval)
Sets the interval between grid lines in the y-direction.
Q_DECL_DEPRECATED QColor annotationFontColor() const
Returns the font color used for drawing grid annotations.
void setFramePenSize(const double width)
Sets the width of the stroke drawn in the grid frame.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified style entity visitor, causing it to visit all style entities associated with th...
void setAnnotationDisplay(DisplayMode display, BorderSide border)
Sets what types of grid annotations should be drawn for a specified side of the map frame,...
Q_DECL_DEPRECATED QFont annotationFont() const
Returns the font used for drawing grid annotations.
double maxExtension() const
Calculates the maximum distance the grid extends beyond the QgsLayoutItemMap's item rect (in layout u...
AnnotationPosition
Position for grid annotations.
@ InsideMapFrame
Draw annotations inside the map frame.
@ OutsideMapFrame
Draw annotations outside the map frame.
void setAnnotationPosition(AnnotationPosition position, BorderSide side)
Sets the position for the grid annotations on a specified side of the map frame.
AnnotationPosition annotationPosition(BorderSide side) const
Returns the position for the grid annotations on a specified side of the map frame.
void setUnits(GridUnit unit)
Sets the unit to use for grid measurements such as the interval and offset for grid lines.
QgsLayoutItemMapGrid::FrameSideFlags frameSideFlags() const
Returns the flags which control which sides of the map item the grid frame is drawn on.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context) override
Sets the map item state from a DOM document, where element is the DOM node corresponding to a 'Layout...
bool testFrameSideFlag(FrameSideFlag flag) const
Tests whether the grid frame should be drawn on a specified side of the map item.
void setFrameDivisions(DisplayMode divisions, BorderSide side)
Sets what type of grid divisions should be used for frames on a specified side of the map.
void setMinimumIntervalWidth(double width)
Sets the minimum width (in millimeters) for grid segments.
AnnotationCoordinate
Annotation coordinate type.
@ Latitude
Coordinate is a latitude value.
@ Longitude
Coordinate is a longitude value.
void setIntervalX(double interval)
Sets the interval between grid lines in the x-direction.
void setCrossLength(const double length)
Sets the length (in layout units) of the cross segments drawn for the grid.
void setEnabled(bool enabled) override
Controls whether the item will be drawn.
DisplayMode
Display settings for grid annotations and frames.
@ LongitudeOnly
Show longitude/x annotations/divisions only.
@ ShowAll
Show both latitude and longitude annotations/divisions.
@ LatitudeOnly
Show latitude/y annotations/divisions only.
void setAnnotationFrameDistance(const double distance)
Sets the distance between the map frame and annotations.
void crsChanged()
Emitted whenever the grid's CRS is changed.
void setFrameMargin(const double margin)
Sets the grid frame margin (in layout units).
QgsLayoutItemMapGrid(const QString &name, QgsLayoutItemMap *map)
Constructor for QgsLayoutItemMapGrid.
~QgsLayoutItemMapGrid() override
void setMarkerSymbol(QgsMarkerSymbol *symbol)
Sets the marker symbol used for drawing grid points.
void setMaximumIntervalWidth(double width)
Sets the maximum width (in millimeters) for grid segments.
bool usesAdvancedEffects() const override
Returns true if the item is drawn using advanced effects, such as blend modes.
Q_DECL_DEPRECATED void setAnnotationFont(const QFont &font)
Sets the font used for drawing grid annotations.
void setLineSymbol(QgsLineSymbol *symbol)
Sets the line symbol used for drawing grid lines.
QgsCoordinateReferenceSystem crs() const
Retrieves the CRS for the grid.
TickLengthMode
Tick length mode (useful for rotated grids)
@ OrthogonalTicks
Align ticks orthogonaly.
AnnotationFormat
Format for displaying grid annotations.
@ DegreeMinuteSecondNoSuffix
Degree/minutes/seconds, use - for S/W coordinates.
@ DegreeMinuteSecondPadded
Degree/minutes/seconds, with minutes using leading zeros where required.
@ DegreeMinuteSecond
Degree/minutes/seconds, use NSEW suffix.
@ DecimalWithSuffix
Decimal degrees, use NSEW suffix.
@ DegreeMinute
Degree/minutes, use NSEW suffix.
@ DegreeMinuteNoSuffix
Degree/minutes, use - for S/W coordinates.
@ Decimal
Decimal degrees, use - for S/W coordinates.
@ DegreeMinutePadded
Degree/minutes, with minutes using leading zeros where required.
@ CustomFormat
Custom expression-based format.
DisplayMode frameDivisions(BorderSide side) const
Returns the type of grid divisions which are used for frames on a specified side of the map.
AnnotationDirection
Direction of grid annotations.
@ OnTick
Draw annotations parallel to tick (on the line)
@ Horizontal
Draw annotations horizontally.
@ Vertical
Draw annotations vertically, ascending.
@ AboveTick
Draw annotations parallel to tick (above the line)
@ UnderTick
Draw annotations parallel to tick (under the line)
@ VerticalDescending
Draw annotations vertically, descending.
GridUnit units() const
Returns the units used for grid measurements such as the interval and offset for grid lines.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs for the grid.
void setOffsetY(double offset)
Sets the offset for grid lines in the y-direction.
const QgsMarkerSymbol * markerSymbol() const
Returns the marker symbol used for drawing grid points.
const QgsLineSymbol * lineSymbol() const
Returns the line symbol used for drawing grid lines.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
FrameStyle
Style for grid frame.
@ Zebra
Black/white pattern.
@ InteriorTicks
Tick markers drawn inside map frame.
@ LineBorder
Simple solid line frame.
@ InteriorExteriorTicks
Tick markers drawn both inside and outside the map frame.
@ LineBorderNautical
Simple solid line frame, with nautical style diagonals on corners.
@ ExteriorTicks
Tick markers drawn outside map frame.
@ NoFrame
Disable grid frame.
@ ZebraNautical
Black/white pattern, with nautical style diagonals on corners.
void setFrameWidth(const double width)
Sets the grid frame width (in layout units).
DisplayMode annotationDisplay(BorderSide border) const
Returns the display mode for the grid annotations on a specified side of the map frame.
void setOffsetX(double offset)
Sets the offset for grid lines in the x-direction.
BorderSide
Border sides for annotations.
AnnotationDirection annotationDirection(BorderSide border) const
Returns the direction for drawing frame annotations, on the specified side of the map.
void setAnnotationDirection(AnnotationDirection direction, BorderSide side)
Sets the direction for drawing frame annotations for the specified map side.
void setGridLineColor(const QColor &color)
Sets the color of grid lines.
void setGridLineWidth(double width)
Sets the width of grid lines (in layout units).
void setStyle(GridStyle style)
Sets the grid style, which controls how the grid is drawn over the map's contents.
A collection of map items which are drawn above the map content in a QgsLayoutItemMap.
void addItem(QgsLayoutItemMapItem *item)
Adds a new map item to the stack and takes ownership of the item.
void removeItem(const QString &itemId)
Removes an item which matching itemId from the stack and deletes the corresponding QgsLayoutItemMapIt...
QgsLayoutItemMapItem * item(int index) const
Returns a reference to the item at the specified index within the stack.
void moveItemUp(const QString &itemId)
Moves an item which matching itemId up the stack, causing it to be rendered above other items.
void removeItems()
Clears the item stack and deletes all QgsLayoutItemMapItems contained by the stack.
QList< QgsLayoutItemMapItem * > mItems
void moveItemDown(const QString &itemId)
Moves an item which matching itemId up the stack, causing it to be rendered above other items.
An item which is drawn inside a QgsLayoutItemMap, e.g., a grid or map overview.
QgsLayoutItemMap * mMap
Associated map.
virtual bool readXml(const QDomElement &element, const QDomDocument &doc, const QgsReadWriteContext &context)
Sets the map item state from a DOM document, where element is the DOM node corresponding to a 'Layout...
virtual bool writeXml(QDomElement &element, QDomDocument &document, const QgsReadWriteContext &context) const
Stores map item state in a DOM element, where element is the DOM element corresponding to a 'LayoutMa...
virtual void setEnabled(bool enabled)
Controls whether the item will be drawn.
bool enabled() const
Returns whether the item will be drawn.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Layout graphical items for displaying a map.
void extentChanged()
Emitted when the map's extent changes.
QPointF mapToItemCoords(QPointF mapCoords) const
Transforms map coordinates to item coordinates (considering rotation and move offset)
void updateBoundingRect()
Updates the bounding rect of this item. Call this function before doing any changes related to annota...
void mapRotationChanged(double newRotation)
Emitted when the map's rotation changes.
void crsChanged()
Emitted when the map's coordinate reference system is changed.
QPolygonF transformedMapPolygon() const
Returns extent that considers rotation and shift with mOffsetX / mOffsetY.
double mapRotation(QgsLayoutObject::PropertyValueType valueType=QgsLayoutObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the layout item, in degrees clockwise.
QgsRectangle extent() const
Returns the current map extent.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
QgsLayoutSize sizeWithUnits() const
Returns the item's current size, including units.
bool frameEnabled() const
Returns true if the item includes a frame.
QgsPropertyCollection mDataDefinedProperties
const QgsLayout * layout() const
Returns the layout the object is attached to.
QPointer< QgsLayout > mLayout
@ MapGridIntervalX
Map grid interval X.
@ MapGridAnnotationDisplayBottom
Map annotation display bottom.
@ MapGridIntervalY
Map grid interval Y.
@ MapGridFrameSize
Map grid frame size.
@ MapGridFrameDivisionsBottom
Map frame division display bottom.
@ MapGridAnnotationDisplayRight
Map annotation display right.
@ MapGridFrameMargin
Map grid frame margin.
@ MapGridOffsetX
Map grid offset X.
@ MapGridLabelDistance
Map grid label distance.
@ MapGridAnnotationDisplayLeft
Map annotation display left.
@ MapGridFrameDivisionsLeft
Map frame division display left.
@ MapGridEnabled
Map grid enabled.
@ MapGridFrameLineThickness
Map grid frame line thickness.
@ MapGridFrameDivisionsRight
Map frame division display right.
@ MapGridFrameDivisionsTop
Map frame division display top.
@ MapGridCrossSize
Map grid cross size.
@ MapGridOffsetY
Map grid offset Y.
@ MapGridAnnotationDisplayTop
Map annotation display top.
QgsLayoutRenderContext::Flags flags() const
Returns the current combination of flags used for rendering the layout.
@ FlagAntialiasing
Use antialiasing when drawing items.
static QgsRenderContext createRenderContextForLayout(QgsLayout *layout, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified layout and painter destination.
static double calculatePrettySize(double minimumSize, double maximumSize)
Calculates a "pretty" size which falls between the range [minimumSize, maximumSize].
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout's render context, which stores information relating to the current ...
Definition: qgslayout.cpp:359
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
static QgsLineSymbol * createSimple(const QVariantMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties.
A marker symbol type, for rendering Point and MultiPoint geometries.
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Scoped object for saving and restoring a QPainter object's state.
Scoped object for temporary scaling of a QgsRenderContext for pixel based rendering.
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition: qgsstyle.h:1219
static QColor decodeColor(const QString &str)
static QPointF pointOnLineWithDistance(QPointF startPoint, QPointF directionPoint, double distance)
Returns a point on the line from startPoint to directionPoint that is a certain distance away from th...
static QString encodeColor(const QColor &color)
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
void setColor(const QColor &color)
Sets the color that text will be rendered in.
void setSize(double size)
Sets the size for rendered text.
void setFont(const QFont &font)
Sets the font used for rendering text.
void readXml(const QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
QFont toQFont() const
Returns a QFont matching the relevant settings from this text format.
void setSizeUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the size of rendered text.
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context) const
Write settings into a DOM element.
QColor color() const
Returns the color that text will be rendered in.
static double textWidth(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, QFontMetricsF *fontMetrics=nullptr)
Returns the width of a text based on a given format.
static void drawText(const QRectF &rect, double rotation, HAlignment alignment, const QStringList &textLines, QgsRenderContext &context, const QgsTextFormat &format, bool drawAsOutlines=true, VAlignment vAlignment=AlignTop, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags())
Draws text within a rectangle using the specified settings.
@ AlignLeft
Left align.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr, Qgis::TextRendererFlags flags=Qgis::TextRendererFlags(), double maxLineWidth=0)
Returns the height of a text based on a given format.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceUnknownUnit
Unknown distance unit.
Definition: qgsunittypes.h:78
@ LayoutMillimeters
Millimeters.
Definition: qgsunittypes.h:183
static Q_INVOKABLE double fromUnitToUnitFactor(QgsUnitTypes::DistanceUnit fromUnit, QgsUnitTypes::DistanceUnit toUnit)
Returns the conversion factor between the specified distance units.
@ RenderPoints
Points (e.g., for font sizes)
Definition: qgsunittypes.h:173
@ RenderPixels
Pixels.
Definition: qgsunittypes.h:171
@ RenderMillimeters
Millimeters.
Definition: qgsunittypes.h:169
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define FALLTHROUGH
Definition: qgis.h:2027
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:1456
double qgsRound(double number, int places)
Returns a double number, rounded (as close as possible) to the specified number of places.
Definition: qgis.h:1558
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1504
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:52
QgsLayoutItemMapGrid::DisplayMode gridAnnotationDisplayModeFromDD(QString ddValue, QgsLayoutItemMapGrid::DisplayMode defValue)
#define MAX_GRID_LINES
bool sortByDistance(QPair< qreal, QgsLayoutItemMapGrid::BorderSide > a, QPair< qreal, QgsLayoutItemMapGrid::BorderSide > b)
QVector2D borderToNormal2D(QgsLayoutItemMapGrid::BorderSide border)
QVector2D borderToVector2D(QgsLayoutItemMapGrid::BorderSide border)
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Single variable definition for use within a QgsExpressionContextScope.
Contains information relating to the style entity currently being visited.