QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  QDomNodeList mapGridNodeList = elem.elementsByTagName( QStringLiteral( "ComposerMapGrid" ) );
115  for ( int i = 0; i < mapGridNodeList.size(); ++i )
116  {
117  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  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  QgsSettings settings;
195  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  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  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  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  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  QDomElement lineStyleElem = itemElem.firstChildElement( QStringLiteral( "lineStyle" ) );
368  if ( !lineStyleElem.isNull() )
369  {
370  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  QDomElement markerStyleElem = itemElem.firstChildElement( QStringLiteral( "markerStyle" ) );
387  if ( !markerStyleElem.isNull() )
388  {
389  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  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  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  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  double maxX = mMap->rect().width();
503  double maxY = mMap->rect().height();
504 
505  QList< QgsPointXY >::const_iterator intersectionIt = mTransformedIntersections.constBegin();
506  for ( ; intersectionIt != mTransformedIntersections.constEnd(); ++intersectionIt )
507  {
508  double x = intersectionIt->x();
509  double y = intersectionIt->y();
510  if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
511  {
512  //ensure that crosses don't overshoot the map item bounds
513  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  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  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  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  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  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  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  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  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  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  QVector2D normalVector = borderToNormal2D( annot.border );
1078  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  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  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  AnnotationPosition anotPos = annotationPosition( frameBorder );
1242  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  QVector2D normalVector = borderToNormal2D( annot.border );
1249  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  bool isOverTick = ( anotDir == QgsLayoutItemMapGrid::AboveTick || anotDir == QgsLayoutItemMapGrid::OnTick || anotDir == QgsLayoutItemMapGrid::UnderTick );
1256  bool hasInteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks );
1257  bool hasExteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical );
1258  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  QVector2D pos = annot.position + f * vector;
1273  xpos = pos.x();
1274  ypos = pos.y();
1275 
1276  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  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  QgsScopedQPainterState painterState( context.painter() );
1391  context.painter()->translate( QPointF( xpos, ypos ) );
1392  context.painter()->rotate( rotation );
1393  context.painter()->translate( -anchor );
1394  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  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  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  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  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  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  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  double roundCorrection = bbox.yMaximum() > 0 ? 1.0 : 0.0;
1743  double currentLevel = static_cast< int >( ( bbox.yMaximum() - mEvaluatedOffsetY ) / mEvaluatedIntervalY + roundCorrection ) * mEvaluatedIntervalY + mEvaluatedOffsetY;
1744 
1745  double minX = bbox.xMinimum();
1746  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  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  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  double roundCorrection = bbox.xMinimum() > 0 ? 1.0 : 0.0;
1822  double currentLevel = static_cast< int >( ( bbox.xMinimum() - mEvaluatedOffsetX ) / mEvaluatedIntervalX + roundCorrection ) * mEvaluatedIntervalX + mEvaluatedOffsetX;
1823 
1824  double minY = bbox.yMinimum();
1825  double maxY = bbox.yMaximum();
1826  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  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  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 
1952  switch ( mGridUnit )
1953  {
1954  case MapUnit:
1955  case MM:
1956  case CM:
1957  {
1958  mEvaluatedIntervalX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalX, context, mGridIntervalX );
1959  mEvaluatedIntervalY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalY, context, mGridIntervalY );
1960  break;
1961  }
1962 
1963  case DynamicPageSizeBased:
1964  {
1965  if ( mMaximumIntervalWidth < mMinimumIntervalWidth )
1966  {
1967  mEvaluatedEnabled = false;
1968  }
1969  else
1970  {
1971  const double mapWidthMm = mLayout->renderContext().measurementConverter().convert( mMap->sizeWithUnits(), QgsUnitTypes::LayoutMillimeters ).width();
1972  const double mapWidthMapUnits = mapWidth();
1973  double minUnitsPerSeg = ( mMinimumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1974  double maxUnitsPerSeg = ( mMaximumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1975  const double interval = QgsLayoutUtils::calculatePrettySize( minUnitsPerSeg, maxUnitsPerSeg );
1976  mEvaluatedIntervalX = interval;
1977  mEvaluatedIntervalY = interval;
1978  }
1979  break;
1980  }
1981  }
1982  mEvaluatedOffsetX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetX, context, mGridOffsetX );
1983  mEvaluatedOffsetY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetY, context, mGridOffsetY );
1984  mEvaluatedGridFrameWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameSize, context, mGridFrameWidth );
1985  mEvaluatedGridFrameMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameMargin, context, mGridFrameMargin );
1986  mEvaluatedAnnotationFrameDistance = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridLabelDistance, context, mAnnotationFrameDistance );
1987  mEvaluatedCrossLength = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridCrossSize, context, mCrossLength );
1988  mEvaluatedGridFrameLineThickness = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameLineThickness, context, mGridFramePenThickness );
1989  mEvaluatedLeftGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayLeft, context ), mLeftGridAnnotationDisplay );
1990  mEvaluatedRightGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayRight, context ), mRightGridAnnotationDisplay );
1991  mEvaluatedTopGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayTop, context ), mTopGridAnnotationDisplay );
1992  mEvaluatedBottomGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayBottom, context ), mBottomGridAnnotationDisplay );
1993  mEvaluatedLeftFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsLeft, context ), mLeftFrameDivisions );
1994  mEvaluatedRightFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsRight, context ), mRightFrameDivisions );
1995  mEvaluatedTopFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsTop, context ), mTopFrameDivisions );
1996  mEvaluatedBottomFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsBottom, context ), mBottomFrameDivisions );
1997 
1998 }
1999 
2000 double QgsLayoutItemMapGrid::mapWidth() const
2001 {
2002  if ( !mMap )
2003  {
2004  return 0.0;
2005  }
2006 
2007  QgsRectangle mapExtent = mMap->extent();
2008  const QgsUnitTypes::DistanceUnit distanceUnit = mCRS.isValid() ? mCRS.mapUnits() : mMap->crs().mapUnits();
2009  if ( distanceUnit == QgsUnitTypes::DistanceUnknownUnit )
2010  {
2011  return mapExtent.width();
2012  }
2013  else
2014  {
2015  QgsDistanceArea da;
2016 
2017  da.setSourceCrs( mMap->crs(), mLayout->project()->transformContext() );
2018  da.setEllipsoid( mLayout->project()->ellipsoid() );
2019 
2021  double measure = da.measureLine( QgsPointXY( mapExtent.xMinimum(), mapExtent.yMinimum() ),
2022  QgsPointXY( mapExtent.xMaximum(), mapExtent.yMinimum() ) );
2023  measure /= QgsUnitTypes::fromUnitToUnitFactor( distanceUnit, units );
2024  return measure;
2025  }
2026 }
2027 
2028 bool sortByDistance( QPair<qreal, QgsLayoutItemMapGrid::BorderSide> a, QPair<qreal, QgsLayoutItemMapGrid::BorderSide> b )
2029 {
2030  return a.first < b.first;
2031 }
2032 
2033 QgsLayoutItemMapGrid::BorderSide QgsLayoutItemMapGrid::borderForLineCoord( QPointF p, const AnnotationCoordinate coordinateType ) const
2034 {
2035  if ( !mMap )
2036  {
2038  }
2039 
2040  double tolerance = std::max( mMap->frameEnabled() ? mMap->pen().widthF() : 0.0, 1.0 );
2041 
2042  //check for corner coordinates
2043  if ( ( p.y() <= tolerance && p.x() <= tolerance ) // top left
2044  || ( p.y() <= tolerance && p.x() >= ( mMap->rect().width() - tolerance ) ) //top right
2045  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() <= tolerance ) //bottom left
2046  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() >= ( mMap->rect().width() - tolerance ) ) //bottom right
2047  )
2048  {
2049  //coordinate is in corner - fall back to preferred side for coordinate type
2050  if ( coordinateType == QgsLayoutItemMapGrid::Latitude )
2051  {
2052  if ( p.x() <= tolerance )
2053  {
2055  }
2056  else
2057  {
2059  }
2060  }
2061  else
2062  {
2063  if ( p.y() <= tolerance )
2064  {
2066  }
2067  else
2068  {
2070  }
2071  }
2072  }
2073 
2074  //otherwise, guess side based on closest map side to point
2075  QList< QPair<qreal, QgsLayoutItemMapGrid::BorderSide > > distanceToSide;
2076  distanceToSide << qMakePair( p.x(), QgsLayoutItemMapGrid::Left );
2077  distanceToSide << qMakePair( mMap->rect().width() - p.x(), QgsLayoutItemMapGrid::Right );
2078  distanceToSide << qMakePair( p.y(), QgsLayoutItemMapGrid::Top );
2079  distanceToSide << qMakePair( mMap->rect().height() - p.y(), QgsLayoutItemMapGrid::Bottom );
2080 
2081  std::sort( distanceToSide.begin(), distanceToSide.end(), sortByDistance );
2082  return distanceToSide.at( 0 ).second;
2083 }
2084 
2086 {
2087  mGridLineSymbol.reset( symbol );
2088 }
2089 
2091 {
2092  return mGridLineSymbol.get();
2093 }
2094 
2096 {
2097  return mGridLineSymbol.get();
2098 }
2099 
2101 {
2102  mGridMarkerSymbol.reset( symbol );
2103 }
2104 
2106 {
2107  return mGridMarkerSymbol.get();
2108 }
2109 
2111 {
2112  return mGridMarkerSymbol.get();
2113 }
2114 
2116 {
2117  mAnnotationFormat.setFont( font );
2118  if ( font.pointSizeF() > 0 )
2119  {
2120  mAnnotationFormat.setSize( font.pointSizeF() );
2121  mAnnotationFormat.setSizeUnit( QgsUnitTypes::RenderPoints );
2122  }
2123  else if ( font.pixelSize() > 0 )
2124  {
2125  mAnnotationFormat.setSize( font.pixelSize() );
2126  mAnnotationFormat.setSizeUnit( QgsUnitTypes::RenderPixels );
2127  }
2128 }
2129 
2131 {
2132  return mAnnotationFormat.toQFont();
2133 }
2134 
2136 {
2137  mAnnotationFormat.setColor( color );
2138 }
2139 
2141 {
2142  return mAnnotationFormat.color();
2143 }
2144 
2146 {
2147  switch ( border )
2148  {
2150  mLeftGridAnnotationDisplay = display;
2151  break;
2153  mRightGridAnnotationDisplay = display;
2154  break;
2156  mTopGridAnnotationDisplay = display;
2157  break;
2159  mBottomGridAnnotationDisplay = display;
2160  break;
2161  }
2162 
2163  refreshDataDefinedProperties();
2164 
2165  if ( mMap )
2166  {
2168  mMap->update();
2169  }
2170 }
2171 
2173 {
2174  switch ( border )
2175  {
2177  return mLeftGridAnnotationDisplay;
2179  return mRightGridAnnotationDisplay;
2181  return mTopGridAnnotationDisplay;
2183  return mBottomGridAnnotationDisplay;
2184  }
2185  return mBottomGridAnnotationDisplay; // no warnings
2186 }
2187 
2189 {
2190  double top = 0.0;
2191  double right = 0.0;
2192  double bottom = 0.0;
2193  double left = 0.0;
2194  calculateMaxExtension( top, right, bottom, left );
2195  return std::max( std::max( std::max( top, right ), bottom ), left );
2196 }
2197 
2198 void QgsLayoutItemMapGrid::calculateMaxExtension( double &top, double &right, double &bottom, double &left ) const
2199 {
2200  top = 0.0;
2201  right = 0.0;
2202  bottom = 0.0;
2203  left = 0.0;
2204 
2205  if ( !mMap || !mEvaluatedEnabled )
2206  {
2207  return;
2208  }
2209 
2210  //setup render context
2212  QgsExpressionContext expressionContext = createExpressionContext();
2213  context.setExpressionContext( expressionContext );
2214 
2215  GridExtension extension;
2216 
2217  //collect grid lines
2218  switch ( mGridUnit )
2219  {
2220  case MapUnit:
2221  case DynamicPageSizeBased:
2222  {
2223  if ( mCRS.isValid() && mCRS != mMap->crs() )
2224  {
2225  drawGridCrsTransform( context, 0, true );
2226  break;
2227  }
2228  }
2229  FALLTHROUGH
2230  case CM:
2231  case MM:
2232  drawGridNoTransform( context, 0, true );
2233  break;
2234  }
2235 
2236  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame || mShowGridAnnotation )
2237  updateGridLinesAnnotationsPositions();
2238 
2239  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
2240  {
2241  drawGridFrame( nullptr, &extension );
2242  }
2243 
2244  if ( mShowGridAnnotation )
2245  {
2246  drawCoordinateAnnotations( context, context.expressionContext(), &extension );
2247  }
2248 
2249  top = extension.top;
2250  right = extension.right;
2251  bottom = extension.bottom;
2252  left = extension.left;
2253 }
2254 
2256 {
2258  refreshDataDefinedProperties();
2259 }
2260 
2262 {
2263  if ( unit == mGridUnit )
2264  {
2265  return;
2266  }
2267  mGridUnit = unit;
2268  mTransformDirty = true;
2269 }
2270 
2271 void QgsLayoutItemMapGrid::setIntervalX( const double interval )
2272 {
2273  if ( qgsDoubleNear( interval, mGridIntervalX ) )
2274  {
2275  return;
2276  }
2277  mGridIntervalX = interval;
2278  mTransformDirty = true;
2279  refreshDataDefinedProperties();
2280 }
2281 
2282 void QgsLayoutItemMapGrid::setIntervalY( const double interval )
2283 {
2284  if ( qgsDoubleNear( interval, mGridIntervalY ) )
2285  {
2286  return;
2287  }
2288  mGridIntervalY = interval;
2289  mTransformDirty = true;
2290  refreshDataDefinedProperties();
2291 }
2292 
2293 void QgsLayoutItemMapGrid::setOffsetX( const double offset )
2294 {
2295  if ( qgsDoubleNear( offset, mGridOffsetX ) )
2296  {
2297  return;
2298  }
2299  mGridOffsetX = offset;
2300  mTransformDirty = true;
2301  refreshDataDefinedProperties();
2302 }
2303 
2304 void QgsLayoutItemMapGrid::setOffsetY( const double offset )
2305 {
2306  if ( qgsDoubleNear( offset, mGridOffsetY ) )
2307  {
2308  return;
2309  }
2310  mGridOffsetY = offset;
2311  mTransformDirty = true;
2312  refreshDataDefinedProperties();
2313 }
2314 
2316 {
2317  if ( qgsDoubleNear( minWidth, mMinimumIntervalWidth ) )
2318  {
2319  return;
2320  }
2321  mMinimumIntervalWidth = minWidth;
2322  mTransformDirty = true;
2323  refreshDataDefinedProperties();
2324 }
2325 
2327 {
2328  if ( qgsDoubleNear( maxWidth, mMaximumIntervalWidth ) )
2329  {
2330  return;
2331  }
2332  mMaximumIntervalWidth = maxWidth;
2333  mTransformDirty = true;
2334  refreshDataDefinedProperties();
2335 }
2336 
2338 {
2339  if ( style == mGridStyle )
2340  {
2341  return;
2342  }
2343  mGridStyle = style;
2344  mTransformDirty = true;
2345 }
2346 
2347 void QgsLayoutItemMapGrid::setCrossLength( const double length )
2348 {
2349  mCrossLength = length;
2350  refreshDataDefinedProperties();
2351 }
2352 
2354 {
2355  switch ( border )
2356  {
2358  mLeftGridAnnotationDirection = direction;
2359  break;
2361  mRightGridAnnotationDirection = direction;
2362  break;
2364  mTopGridAnnotationDirection = direction;
2365  break;
2367  mBottomGridAnnotationDirection = direction;
2368  break;
2369  }
2370 
2371  if ( mMap )
2372  {
2374  mMap->update();
2375  }
2376 }
2377 
2378 void QgsLayoutItemMapGrid::setFrameSideFlags( FrameSideFlags flags )
2379 {
2380  mGridFrameSides = flags;
2381 }
2382 
2384 {
2385  if ( on )
2386  mGridFrameSides |= flag;
2387  else
2388  mGridFrameSides &= ~flag;
2389 }
2390 
2391 QgsLayoutItemMapGrid::FrameSideFlags QgsLayoutItemMapGrid::frameSideFlags() const
2392 {
2393  return mGridFrameSides;
2394 }
2395 
2397 {
2399  context.appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
2400  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), 0, true ) );
2401  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), "x", true ) );
2402  context.setHighlightedVariables( QStringList() << QStringLiteral( "grid_number" ) << QStringLiteral( "grid_axis" ) );
2403  return context;
2404 }
2405 
2407 {
2408  if ( mGridLineSymbol )
2409  {
2410  QgsStyleSymbolEntity entity( mGridLineSymbol.get() );
2411  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2412  return false;
2413  }
2414  if ( mGridMarkerSymbol )
2415  {
2416  QgsStyleSymbolEntity entity( mGridMarkerSymbol.get() );
2417  if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2418  return false;
2419  }
2420 
2421  return true;
2422 }
2423 
2425 {
2426  mTransformDirty = true;
2427  refreshDataDefinedProperties();
2429  mMap->update();
2430 }
2431 
2433 {
2434  return mGridFrameSides.testFlag( flag );
2435 }
2436 
2437 void QgsLayoutItemMapGrid::setFrameWidth( const double width )
2438 {
2439  mGridFrameWidth = width;
2440  refreshDataDefinedProperties();
2441 }
2442 
2443 void QgsLayoutItemMapGrid::setFrameMargin( const double margin )
2444 {
2445  mGridFrameMargin = margin;
2446  refreshDataDefinedProperties();
2447 }
2448 
2449 void QgsLayoutItemMapGrid::setFramePenSize( const double width )
2450 {
2451  mGridFramePenThickness = width;
2452  refreshDataDefinedProperties();
2453 }
2454 
2456 {
2457  mLeftGridAnnotationDirection = direction;
2458  mRightGridAnnotationDirection = direction;
2459  mTopGridAnnotationDirection = direction;
2460  mBottomGridAnnotationDirection = direction;
2461 }
2462 
2464 {
2465  switch ( border )
2466  {
2468  mLeftGridAnnotationPosition = position;
2469  break;
2471  mRightGridAnnotationPosition = position;
2472  break;
2474  mTopGridAnnotationPosition = position;
2475  break;
2477  mBottomGridAnnotationPosition = position;
2478  break;
2479  }
2480 
2481  if ( mMap )
2482  {
2484  mMap->update();
2485  }
2486 }
2487 
2489 {
2490  switch ( border )
2491  {
2493  return mLeftGridAnnotationPosition;
2495  return mRightGridAnnotationPosition;
2497  return mTopGridAnnotationPosition;
2499  return mBottomGridAnnotationPosition;
2500  }
2501  return mLeftGridAnnotationPosition; // no warnings
2502 }
2503 
2505 {
2506  mAnnotationFrameDistance = distance;
2507  refreshDataDefinedProperties();
2508 }
2509 
2511 {
2512  if ( !mMap )
2513  {
2514  return mLeftGridAnnotationDirection;
2515  }
2516 
2517  switch ( border )
2518  {
2520  return mLeftGridAnnotationDirection;
2522  return mRightGridAnnotationDirection;
2524  return mTopGridAnnotationDirection;
2526  return mBottomGridAnnotationDirection;
2527  }
2528  return mLeftGridAnnotationDirection; // no warnings
2529 }
2530 
2532 {
2533  switch ( border )
2534  {
2536  mLeftFrameDivisions = divisions;
2537  break;
2539  mRightFrameDivisions = divisions;
2540  break;
2542  mTopFrameDivisions = divisions;
2543  break;
2545  mBottomFrameDivisions = divisions;
2546  break;
2547  }
2548 
2549  refreshDataDefinedProperties();
2550 
2551  if ( mMap )
2552  {
2553  mMap->update();
2554  }
2555 }
2556 
2558 {
2559  switch ( border )
2560  {
2562  return mLeftFrameDivisions;
2564  return mRightFrameDivisions;
2566  return mTopFrameDivisions;
2568  return mBottomFrameDivisions;
2569  }
2570  return mLeftFrameDivisions; // no warnings
2571 }
2572 
2573 int QgsLayoutItemMapGrid::crsGridParams( QgsRectangle &crsRect, QgsCoordinateTransform &inverseTransform ) const
2574 {
2575  if ( !mMap )
2576  {
2577  return 1;
2578  }
2579 
2580  try
2581  {
2582  QgsCoordinateTransform tr( mMap->crs(), mCRS, mLayout->project() );
2583  QPolygonF mapPolygon = mMap->transformedMapPolygon();
2584  QRectF mbr = mapPolygon.boundingRect();
2585  QgsRectangle mapBoundingRect( mbr.left(), mbr.bottom(), mbr.right(), mbr.top() );
2586 
2587 
2588  if ( mCRS.isGeographic() )
2589  {
2590  //handle crossing the 180 degree longitude line
2591  QgsPointXY lowerLeft( mapBoundingRect.xMinimum(), mapBoundingRect.yMinimum() );
2592  QgsPointXY upperRight( mapBoundingRect.xMaximum(), mapBoundingRect.yMaximum() );
2593 
2594  lowerLeft = tr.transform( lowerLeft.x(), lowerLeft.y() );
2595  upperRight = tr.transform( upperRight.x(), upperRight.y() );
2596 
2597  if ( lowerLeft.x() > upperRight.x() )
2598  {
2599  //we've crossed the line
2600  crsRect = tr.transformBoundingBox( mapBoundingRect, QgsCoordinateTransform::ForwardTransform, true );
2601  }
2602  else
2603  {
2604  //didn't cross the line
2605  crsRect = tr.transformBoundingBox( mapBoundingRect );
2606  }
2607  }
2608  else
2609  {
2610  crsRect = tr.transformBoundingBox( mapBoundingRect );
2611  }
2612 
2613  inverseTransform = QgsCoordinateTransform( mCRS, mMap->crs(), mLayout->project() );
2614  }
2615  catch ( QgsCsException &cse )
2616  {
2617  Q_UNUSED( cse )
2618  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
2619  return 1;
2620  }
2621  return 0;
2622 }
2623 
2624 QList<QPolygonF> QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, const QgsRectangle &rect )
2625 {
2626  QgsGeometry lineGeom = QgsGeometry::fromQPolygonF( line );
2627  QgsGeometry rectGeom = QgsGeometry::fromRect( rect );
2628 
2629  QgsGeometry intersected = lineGeom.intersection( rectGeom );
2630  QVector<QgsGeometry> intersectedParts = intersected.asGeometryCollection();
2631 
2632  QList<QPolygonF> trimmedLines;
2633  QVector<QgsGeometry>::const_iterator geomIt = intersectedParts.constBegin();
2634  for ( ; geomIt != intersectedParts.constEnd(); ++geomIt )
2635  {
2636  trimmedLines << ( *geomIt ).asQPolygonF();
2637  }
2638  return trimmedLines;
2639 }
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, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
@ ForwardTransform
Transform from source to 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:124
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:126
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
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.
void setFlag(Flag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
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.
@ AlignLeft
Left align.
static double textHeight(const QgsRenderContext &context, const QgsTextFormat &format, const QStringList &textLines, DrawMode mode=Point, QFontMetricsF *fontMetrics=nullptr)
Returns the height 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)
Draws text within a rectangle using the specified settings.
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:1111
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:550
double qgsRound(double number, int places)
Returns a double number, rounded (as close as possible) to the specified number of places.
Definition: qgis.h:652
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:598
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:51
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.