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