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