QGIS API Documentation  3.6.0-Noosa (5873452)
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 
38 #include <QPainter>
39 #include <QPen>
40 
41 #define MAX_GRID_LINES 1000 //maximum number of horizontal or vertical grid lines to draw
42 
45 {
46 
47 }
48 
50 {
52 }
53 
54 void QgsLayoutItemMapGridStack::removeGrid( const QString &gridId )
55 {
57 }
58 
59 void QgsLayoutItemMapGridStack::moveGridUp( const QString &gridId )
60 {
62 }
63 
64 void QgsLayoutItemMapGridStack::moveGridDown( const QString &gridId )
65 {
67 }
68 
70 {
72  return qobject_cast<QgsLayoutItemMapGrid *>( item );
73 }
74 
76 {
78  return qobject_cast<QgsLayoutItemMapGrid *>( item );
79 }
80 
81 QList<QgsLayoutItemMapGrid *> QgsLayoutItemMapGridStack::asList() const
82 {
83  QList< QgsLayoutItemMapGrid * > list;
85  {
86  if ( QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item ) )
87  {
88  list.append( grid );
89  }
90  }
91  return list;
92 }
93 
95 {
96  QgsLayoutItemMapItem *item = mItems.at( idx );
97  QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item );
98  return *grid;
99 }
100 
101 bool QgsLayoutItemMapGridStack::readXml( const QDomElement &elem, const QDomDocument &doc, const QgsReadWriteContext &context )
102 {
103  removeItems();
104 
105  //read grid stack
106  QDomNodeList mapGridNodeList = elem.elementsByTagName( QStringLiteral( "ComposerMapGrid" ) );
107  for ( int i = 0; i < mapGridNodeList.size(); ++i )
108  {
109  QDomElement mapGridElem = mapGridNodeList.at( i ).toElement();
110  QgsLayoutItemMapGrid *mapGrid = new QgsLayoutItemMapGrid( mapGridElem.attribute( QStringLiteral( "name" ) ), mMap );
111  mapGrid->readXml( mapGridElem, doc, context );
112  mItems.append( mapGrid );
113  }
114 
115  return true;
116 }
117 
119 {
120  double top = 0.0;
121  double right = 0.0;
122  double bottom = 0.0;
123  double left = 0.0;
124  calculateMaxGridExtension( top, right, bottom, left );
125  return std::max( std::max( std::max( top, right ), bottom ), left );
126 }
127 
128 void QgsLayoutItemMapGridStack::calculateMaxGridExtension( double &top, double &right, double &bottom, double &left ) const
129 {
130  top = 0.0;
131  right = 0.0;
132  bottom = 0.0;
133  left = 0.0;
134 
135  for ( QgsLayoutItemMapItem *item : mItems )
136  {
137  if ( QgsLayoutItemMapGrid *grid = qobject_cast<QgsLayoutItemMapGrid *>( item ) )
138  {
139  double gridTop = 0.0;
140  double gridRight = 0.0;
141  double gridBottom = 0.0;
142  double gridLeft = 0.0;
143  grid->calculateMaxExtension( gridTop, gridRight, gridBottom, gridLeft );
144  top = std::max( top, gridTop );
145  right = std::max( right, gridRight );
146  bottom = std::max( bottom, gridBottom );
147  left = std::max( left, gridLeft );
148  }
149  }
150 }
151 
152 
153 //
154 // QgsLayoutItemMapGrid
155 //
156 
157 
159  : QgsLayoutItemMapItem( name, map )
160  , mGridFrameSides( QgsLayoutItemMapGrid::FrameLeft | QgsLayoutItemMapGrid::FrameRight |
161  QgsLayoutItemMapGrid::FrameTop | QgsLayoutItemMapGrid::FrameBottom )
162 {
163  //get default layout font from settings
164  QgsSettings settings;
165  QString defaultFontString = settings.value( QStringLiteral( "LayoutDesigner/defaultFont" ), QVariant(), QgsSettings::Gui ).toString();
166  if ( !defaultFontString.isEmpty() )
167  {
168  mGridAnnotationFont.setFamily( defaultFontString );
169  }
170 
171  createDefaultGridLineSymbol();
172  createDefaultGridMarkerSymbol();
173 }
174 
175 void QgsLayoutItemMapGrid::createDefaultGridLineSymbol()
176 {
177  QgsStringMap properties;
178  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
179  properties.insert( QStringLiteral( "width" ), QStringLiteral( "0.3" ) );
180  properties.insert( QStringLiteral( "capstyle" ), QStringLiteral( "flat" ) );
181  mGridLineSymbol.reset( QgsLineSymbol::createSimple( properties ) );
182 }
183 
184 void QgsLayoutItemMapGrid::createDefaultGridMarkerSymbol()
185 {
186  QgsStringMap properties;
187  properties.insert( QStringLiteral( "name" ), QStringLiteral( "circle" ) );
188  properties.insert( QStringLiteral( "size" ), QStringLiteral( "2.0" ) );
189  properties.insert( QStringLiteral( "color" ), QStringLiteral( "0,0,0,255" ) );
190  mGridMarkerSymbol.reset( QgsMarkerSymbol::createSimple( properties ) );
191 }
192 
193 void QgsLayoutItemMapGrid::setGridLineWidth( const double width )
194 {
195  if ( mGridLineSymbol )
196  {
197  mGridLineSymbol->setWidth( width );
198  }
199 }
200 
202 {
203  if ( mGridLineSymbol )
204  {
205  mGridLineSymbol->setColor( c );
206  }
207 }
208 
209 bool QgsLayoutItemMapGrid::writeXml( QDomElement &elem, QDomDocument &doc, const QgsReadWriteContext &context ) const
210 {
211  if ( elem.isNull() )
212  {
213  return false;
214  }
215 
216  QDomElement mapGridElem = doc.createElement( QStringLiteral( "ComposerMapGrid" ) );
217  mapGridElem.setAttribute( QStringLiteral( "gridStyle" ), mGridStyle );
218  mapGridElem.setAttribute( QStringLiteral( "intervalX" ), qgsDoubleToString( mGridIntervalX ) );
219  mapGridElem.setAttribute( QStringLiteral( "intervalY" ), qgsDoubleToString( mGridIntervalY ) );
220  mapGridElem.setAttribute( QStringLiteral( "offsetX" ), qgsDoubleToString( mGridOffsetX ) );
221  mapGridElem.setAttribute( QStringLiteral( "offsetY" ), qgsDoubleToString( mGridOffsetY ) );
222  mapGridElem.setAttribute( QStringLiteral( "crossLength" ), qgsDoubleToString( mCrossLength ) );
223 
224  QDomElement lineStyleElem = doc.createElement( QStringLiteral( "lineStyle" ) );
225  QDomElement gridLineStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mGridLineSymbol.get(), doc, context );
226  lineStyleElem.appendChild( gridLineStyleElem );
227  mapGridElem.appendChild( lineStyleElem );
228 
229  QDomElement markerStyleElem = doc.createElement( QStringLiteral( "markerStyle" ) );
230  QDomElement gridMarkerStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mGridMarkerSymbol.get(), doc, context );
231  markerStyleElem.appendChild( gridMarkerStyleElem );
232  mapGridElem.appendChild( markerStyleElem );
233 
234  mapGridElem.setAttribute( QStringLiteral( "gridFrameStyle" ), mGridFrameStyle );
235  mapGridElem.setAttribute( QStringLiteral( "gridFrameSideFlags" ), mGridFrameSides );
236  mapGridElem.setAttribute( QStringLiteral( "gridFrameWidth" ), qgsDoubleToString( mGridFrameWidth ) );
237  mapGridElem.setAttribute( QStringLiteral( "gridFrameMargin" ), qgsDoubleToString( mGridFrameMargin ) );
238  mapGridElem.setAttribute( QStringLiteral( "gridFramePenThickness" ), qgsDoubleToString( mGridFramePenThickness ) );
239  mapGridElem.setAttribute( QStringLiteral( "gridFramePenColor" ), QgsSymbolLayerUtils::encodeColor( mGridFramePenColor ) );
240  mapGridElem.setAttribute( QStringLiteral( "frameFillColor1" ), QgsSymbolLayerUtils::encodeColor( mGridFrameFillColor1 ) );
241  mapGridElem.setAttribute( QStringLiteral( "frameFillColor2" ), QgsSymbolLayerUtils::encodeColor( mGridFrameFillColor2 ) );
242  mapGridElem.setAttribute( QStringLiteral( "leftFrameDivisions" ), mLeftFrameDivisions );
243  mapGridElem.setAttribute( QStringLiteral( "rightFrameDivisions" ), mRightFrameDivisions );
244  mapGridElem.setAttribute( QStringLiteral( "topFrameDivisions" ), mTopFrameDivisions );
245  mapGridElem.setAttribute( QStringLiteral( "bottomFrameDivisions" ), mBottomFrameDivisions );
246  if ( mCRS.isValid() )
247  {
248  mCRS.writeXml( mapGridElem, doc );
249  }
250 
251  mapGridElem.setAttribute( QStringLiteral( "annotationFormat" ), mGridAnnotationFormat );
252  mapGridElem.setAttribute( QStringLiteral( "showAnnotation" ), mShowGridAnnotation );
253  mapGridElem.setAttribute( QStringLiteral( "annotationExpression" ), mGridAnnotationExpressionString );
254  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationDisplay" ), mLeftGridAnnotationDisplay );
255  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationDisplay" ), mRightGridAnnotationDisplay );
256  mapGridElem.setAttribute( QStringLiteral( "topAnnotationDisplay" ), mTopGridAnnotationDisplay );
257  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationDisplay" ), mBottomGridAnnotationDisplay );
258  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationPosition" ), mLeftGridAnnotationPosition );
259  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationPosition" ), mRightGridAnnotationPosition );
260  mapGridElem.setAttribute( QStringLiteral( "topAnnotationPosition" ), mTopGridAnnotationPosition );
261  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationPosition" ), mBottomGridAnnotationPosition );
262  mapGridElem.setAttribute( QStringLiteral( "leftAnnotationDirection" ), mLeftGridAnnotationDirection );
263  mapGridElem.setAttribute( QStringLiteral( "rightAnnotationDirection" ), mRightGridAnnotationDirection );
264  mapGridElem.setAttribute( QStringLiteral( "topAnnotationDirection" ), mTopGridAnnotationDirection );
265  mapGridElem.setAttribute( QStringLiteral( "bottomAnnotationDirection" ), mBottomGridAnnotationDirection );
266  mapGridElem.setAttribute( QStringLiteral( "frameAnnotationDistance" ), QString::number( mAnnotationFrameDistance ) );
267  mapGridElem.appendChild( QgsFontUtils::toXmlElement( mGridAnnotationFont, doc, QStringLiteral( "annotationFontProperties" ) ) );
268  mapGridElem.setAttribute( QStringLiteral( "annotationFontColor" ), QgsSymbolLayerUtils::encodeColor( mGridAnnotationFontColor ) );
269  mapGridElem.setAttribute( QStringLiteral( "annotationPrecision" ), mGridAnnotationPrecision );
270  mapGridElem.setAttribute( QStringLiteral( "unit" ), mGridUnit );
271  mapGridElem.setAttribute( QStringLiteral( "blendMode" ), mBlendMode );
272 
273  bool ok = QgsLayoutItemMapItem::writeXml( mapGridElem, doc, context );
274  elem.appendChild( mapGridElem );
275  return ok;
276 }
277 
278 bool QgsLayoutItemMapGrid::readXml( const QDomElement &itemElem, const QDomDocument &doc, const QgsReadWriteContext &context )
279 {
280  Q_UNUSED( doc );
281  if ( itemElem.isNull() )
282  {
283  return false;
284  }
285 
286  bool ok = QgsLayoutItemMapItem::readXml( itemElem, doc, context );
287 
288  //grid
289  mGridStyle = QgsLayoutItemMapGrid::GridStyle( itemElem.attribute( QStringLiteral( "gridStyle" ), QStringLiteral( "0" ) ).toInt() );
290  mGridIntervalX = itemElem.attribute( QStringLiteral( "intervalX" ), QStringLiteral( "0" ) ).toDouble();
291  mGridIntervalY = itemElem.attribute( QStringLiteral( "intervalY" ), QStringLiteral( "0" ) ).toDouble();
292  mGridOffsetX = itemElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble();
293  mGridOffsetY = itemElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble();
294  mCrossLength = itemElem.attribute( QStringLiteral( "crossLength" ), QStringLiteral( "3" ) ).toDouble();
295  mGridFrameStyle = static_cast< QgsLayoutItemMapGrid::FrameStyle >( itemElem.attribute( QStringLiteral( "gridFrameStyle" ), QStringLiteral( "0" ) ).toInt() );
296  mGridFrameSides = static_cast< QgsLayoutItemMapGrid::FrameSideFlags >( itemElem.attribute( QStringLiteral( "gridFrameSideFlags" ), QStringLiteral( "15" ) ).toInt() );
297  mGridFrameWidth = itemElem.attribute( QStringLiteral( "gridFrameWidth" ), QStringLiteral( "2.0" ) ).toDouble();
298  mGridFrameMargin = itemElem.attribute( QStringLiteral( "gridFrameMargin" ), QStringLiteral( "0.0" ) ).toDouble();
299  mGridFramePenThickness = itemElem.attribute( QStringLiteral( "gridFramePenThickness" ), QStringLiteral( "0.3" ) ).toDouble();
300  mGridFramePenColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridFramePenColor" ), QStringLiteral( "0,0,0" ) ) );
301  mGridFrameFillColor1 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "frameFillColor1" ), QStringLiteral( "255,255,255,255" ) ) );
302  mGridFrameFillColor2 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "frameFillColor2" ), QStringLiteral( "0,0,0,255" ) ) );
303  mLeftFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "leftFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
304  mRightFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "rightFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
305  mTopFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "topFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
306  mBottomFrameDivisions = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "bottomFrameDivisions" ), QStringLiteral( "0" ) ).toInt() );
307 
308  QDomElement lineStyleElem = itemElem.firstChildElement( QStringLiteral( "lineStyle" ) );
309  if ( !lineStyleElem.isNull() )
310  {
311  QDomElement symbolElem = lineStyleElem.firstChildElement( QStringLiteral( "symbol" ) );
312  if ( !symbolElem.isNull() )
313  {
314  mGridLineSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( symbolElem, context ) );
315  }
316  }
317  else
318  {
319  //old project file, read penWidth /penColorRed, penColorGreen, penColorBlue
320  mGridLineSymbol.reset( QgsLineSymbol::createSimple( QgsStringMap() ) );
321  mGridLineSymbol->setWidth( itemElem.attribute( QStringLiteral( "penWidth" ), QStringLiteral( "0" ) ).toDouble() );
322  mGridLineSymbol->setColor( QColor( itemElem.attribute( QStringLiteral( "penColorRed" ), QStringLiteral( "0" ) ).toInt(),
323  itemElem.attribute( QStringLiteral( "penColorGreen" ), QStringLiteral( "0" ) ).toInt(),
324  itemElem.attribute( QStringLiteral( "penColorBlue" ), QStringLiteral( "0" ) ).toInt() ) );
325  }
326 
327  QDomElement markerStyleElem = itemElem.firstChildElement( QStringLiteral( "markerStyle" ) );
328  if ( !markerStyleElem.isNull() )
329  {
330  QDomElement symbolElem = markerStyleElem.firstChildElement( QStringLiteral( "symbol" ) );
331  if ( !symbolElem.isNull() )
332  {
333  mGridMarkerSymbol.reset( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context ) );
334  }
335  }
336 
337  if ( !mCRS.readXml( itemElem ) )
339 
340  mBlendMode = static_cast< QPainter::CompositionMode >( itemElem.attribute( QStringLiteral( "blendMode" ), QStringLiteral( "0" ) ).toUInt() );
341 
342  //annotation
343  mShowGridAnnotation = ( itemElem.attribute( QStringLiteral( "showAnnotation" ), QStringLiteral( "0" ) ) != QLatin1String( "0" ) );
344  mGridAnnotationFormat = QgsLayoutItemMapGrid::AnnotationFormat( itemElem.attribute( QStringLiteral( "annotationFormat" ), QStringLiteral( "0" ) ).toInt() );
345  mGridAnnotationExpressionString = itemElem.attribute( QStringLiteral( "annotationExpression" ) );
346  mGridAnnotationExpression.reset();
347  mLeftGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "leftAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
348  mRightGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "rightAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
349  mTopGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "topAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
350  mBottomGridAnnotationPosition = QgsLayoutItemMapGrid::AnnotationPosition( itemElem.attribute( QStringLiteral( "bottomAnnotationPosition" ), QStringLiteral( "0" ) ).toInt() );
351  mLeftGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "leftAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
352  mRightGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "rightAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
353  mTopGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "topAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
354  mBottomGridAnnotationDisplay = QgsLayoutItemMapGrid::DisplayMode( itemElem.attribute( QStringLiteral( "bottomAnnotationDisplay" ), QStringLiteral( "0" ) ).toInt() );
355 
356  mLeftGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "leftAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
357  mRightGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "rightAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
358  mTopGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "topAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
359  mBottomGridAnnotationDirection = QgsLayoutItemMapGrid::AnnotationDirection( itemElem.attribute( QStringLiteral( "bottomAnnotationDirection" ), QStringLiteral( "0" ) ).toInt() );
360  mAnnotationFrameDistance = itemElem.attribute( QStringLiteral( "frameAnnotationDistance" ), QStringLiteral( "0" ) ).toDouble();
361  if ( !QgsFontUtils::setFromXmlChildNode( mGridAnnotationFont, itemElem, QStringLiteral( "annotationFontProperties" ) ) )
362  {
363  mGridAnnotationFont.fromString( itemElem.attribute( QStringLiteral( "annotationFont" ), QString() ) );
364  }
365  mGridAnnotationFontColor = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "annotationFontColor" ), QStringLiteral( "0,0,0,255" ) ) );
366  mGridAnnotationPrecision = itemElem.attribute( QStringLiteral( "annotationPrecision" ), QStringLiteral( "3" ) ).toInt();
367  int gridUnitInt = itemElem.attribute( QStringLiteral( "unit" ), QString::number( MapUnit ) ).toInt();
368  mGridUnit = ( gridUnitInt <= static_cast< int >( CM ) ) ? static_cast< GridUnit >( gridUnitInt ) : MapUnit;
369  return ok;
370 }
371 
373 {
374  mCRS = crs;
375  mTransformDirty = true;
376 }
377 
379 {
380  return mBlendMode != QPainter::CompositionMode_SourceOver;
381 }
382 
383 QPolygonF QgsLayoutItemMapGrid::scalePolygon( const QPolygonF &polygon, const double scale ) const
384 {
385  QTransform t = QTransform::fromScale( scale, scale );
386  return t.map( polygon );
387 }
388 
389 void QgsLayoutItemMapGrid::drawGridCrsTransform( QgsRenderContext &context, double dotsPerMM, QList< QPair< double, QLineF > > &horizontalLines,
390  QList< QPair< double, QLineF > > &verticalLines, bool calculateLinesOnly ) const
391 {
392  if ( !mMap || !mEnabled )
393  {
394  return;
395  }
396 
397  //has map extent/scale changed?
398  QPolygonF mapPolygon = mMap->transformedMapPolygon();
399  if ( mapPolygon != mPrevMapPolygon )
400  {
401  mTransformDirty = true;
402  mPrevMapPolygon = mapPolygon;
403  }
404 
405  if ( mTransformDirty )
406  {
407  calculateCrsTransformLines();
408  }
409 
410  //draw lines
411  if ( !calculateLinesOnly )
412  {
413  if ( mGridStyle == QgsLayoutItemMapGrid::Solid )
414  {
415  QList< QPair< double, QPolygonF > >::const_iterator xGridIt = mTransformedXLines.constBegin();
416  for ( ; xGridIt != mTransformedXLines.constEnd(); ++xGridIt )
417  {
418  drawGridLine( scalePolygon( xGridIt->second, dotsPerMM ), context );
419  }
420 
421  QList< QPair< double, QPolygonF > >::const_iterator yGridIt = mTransformedYLines.constBegin();
422  for ( ; yGridIt != mTransformedYLines.constEnd(); ++yGridIt )
423  {
424  drawGridLine( scalePolygon( yGridIt->second, dotsPerMM ), context );
425  }
426  }
427  else if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
428  {
429  double maxX = mMap->rect().width();
430  double maxY = mMap->rect().height();
431 
432  QList< QgsPointXY >::const_iterator intersectionIt = mTransformedIntersections.constBegin();
433  for ( ; intersectionIt != mTransformedIntersections.constEnd(); ++intersectionIt )
434  {
435  double x = intersectionIt->x();
436  double y = intersectionIt->y();
437  if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
438  {
439  //ensure that crosses don't overshoot the map item bounds
440  QLineF line1 = QLineF( x - mCrossLength, y, x + mCrossLength, y );
441  line1.p1().rx() = line1.p1().x() < 0 ? 0 : line1.p1().x();
442  line1.p2().rx() = line1.p2().x() > maxX ? maxX : line1.p2().x();
443  QLineF line2 = QLineF( x, y - mCrossLength, x, y + mCrossLength );
444  line2.p1().ry() = line2.p1().y() < 0 ? 0 : line2.p1().y();
445  line2.p2().ry() = line2.p2().y() > maxY ? maxY : line2.p2().y();
446 
447  //draw line using coordinates scaled to dots
448  drawGridLine( QLineF( line1.p1() * dotsPerMM, line1.p2() * dotsPerMM ), context );
449  drawGridLine( QLineF( line2.p1() * dotsPerMM, line2.p2() * dotsPerMM ), context );
450  }
451  else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
452  {
453  drawGridMarker( QPointF( x, y ) * dotsPerMM, context );
454  }
455  }
456  }
457  }
458 
459  //convert QPolygonF to QLineF to draw grid frames and annotations
460  QList< QPair< double, QPolygonF > >::const_iterator yGridLineIt = mTransformedYLines.constBegin();
461  for ( ; yGridLineIt != mTransformedYLines.constEnd(); ++yGridLineIt )
462  {
463  verticalLines.push_back( qMakePair( yGridLineIt->first, QLineF( yGridLineIt->second.first(), yGridLineIt->second.last() ) ) );
464  }
465  QList< QPair< double, QPolygonF > >::const_iterator xGridLineIt = mTransformedXLines.constBegin();
466  for ( ; xGridLineIt != mTransformedXLines.constEnd(); ++xGridLineIt )
467  {
468  horizontalLines.push_back( qMakePair( xGridLineIt->first, QLineF( xGridLineIt->second.first(), xGridLineIt->second.last() ) ) );
469  }
470 }
471 
472 void QgsLayoutItemMapGrid::calculateCrsTransformLines() const
473 {
474  QgsRectangle crsBoundingRect;
475  QgsCoordinateTransform inverseTr;
476  if ( crsGridParams( crsBoundingRect, inverseTr ) != 0 )
477  {
478  return;
479  }
480 
481  //calculate x grid lines
482  mTransformedXLines.clear();
483  xGridLinesCrsTransform( crsBoundingRect, inverseTr, mTransformedXLines );
484 
485  //calculate y grid lines
486  mTransformedYLines.clear();
487  yGridLinesCrsTransform( crsBoundingRect, inverseTr, mTransformedYLines );
488 
489  if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
490  {
491  //cross or markers style - we also need to calculate intersections of lines
492 
493  //first convert lines to QgsGeometry
494  QList< QgsGeometry > yLines;
495  QList< QPair< double, QPolygonF > >::const_iterator yGridIt = mTransformedYLines.constBegin();
496  for ( ; yGridIt != mTransformedYLines.constEnd(); ++yGridIt )
497  {
498  QgsPolylineXY yLine;
499  for ( int i = 0; i < ( *yGridIt ).second.size(); ++i )
500  {
501  yLine.append( QgsPointXY( ( *yGridIt ).second.at( i ).x(), ( *yGridIt ).second.at( i ).y() ) );
502  }
503  yLines << QgsGeometry::fromPolylineXY( yLine );
504  }
505  QList< QgsGeometry > xLines;
506  QList< QPair< double, QPolygonF > >::const_iterator xGridIt = mTransformedXLines.constBegin();
507  for ( ; xGridIt != mTransformedXLines.constEnd(); ++xGridIt )
508  {
509  QgsPolylineXY xLine;
510  for ( int i = 0; i < ( *xGridIt ).second.size(); ++i )
511  {
512  xLine.append( QgsPointXY( ( *xGridIt ).second.at( i ).x(), ( *xGridIt ).second.at( i ).y() ) );
513  }
514  xLines << QgsGeometry::fromPolylineXY( xLine );
515  }
516 
517  //now, loop through geometries and calculate intersection points
518  mTransformedIntersections.clear();
519  QList< QgsGeometry >::const_iterator yLineIt = yLines.constBegin();
520  for ( ; yLineIt != yLines.constEnd(); ++yLineIt )
521  {
522  QList< QgsGeometry >::const_iterator xLineIt = xLines.constBegin();
523  for ( ; xLineIt != xLines.constEnd(); ++xLineIt )
524  {
525  //look for intersections between lines
526  QgsGeometry intersects = ( *yLineIt ).intersection( ( *xLineIt ) );
527  if ( intersects.isNull() )
528  continue;
529 
530  //go through all intersections and draw grid markers/crosses
531  int i = 0;
532  QgsPointXY vertex = intersects.vertexAt( i );
533  while ( vertex != QgsPointXY( 0, 0 ) )
534  {
535  mTransformedIntersections << vertex;
536  i = i + 1;
537  vertex = intersects.vertexAt( i );
538  }
539  }
540  }
541  }
542 
543  mTransformDirty = false;
544 }
545 
546 void QgsLayoutItemMapGrid::draw( QPainter *p )
547 {
548  if ( !mMap || !mEnabled )
549  {
550  return;
551  }
552  QPaintDevice *paintDevice = p->device();
553  if ( !paintDevice )
554  {
555  return;
556  }
557 
558  p->save();
559  p->setCompositionMode( mBlendMode );
560  p->setRenderHint( QPainter::Antialiasing, mMap->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
561 
562  QRectF thisPaintRect = QRectF( 0, 0, mMap->rect().width(), mMap->rect().height() );
563  p->setClipRect( thisPaintRect );
564  if ( thisPaintRect != mPrevPaintRect )
565  {
566  //rect has changed, so need to recalculate transform
567  mTransformDirty = true;
568  mPrevPaintRect = thisPaintRect;
569  }
570 
571  //setup painter scaling to dots so that raster symbology is drawn to scale
572  double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
573  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); //scale painter from mm to dots
574 
575  //setup render context
577  context.setForceVectorOutput( true );
578  QgsExpressionContext expressionContext = createExpressionContext();
579  context.setExpressionContext( expressionContext );
580 
581  QList< QPair< double, QLineF > > verticalLines;
582  QList< QPair< double, QLineF > > horizontalLines;
583 
584  //is grid in a different crs than map?
585  if ( mGridUnit == MapUnit && mCRS.isValid() && mCRS != mMap->crs() )
586  {
587  drawGridCrsTransform( context, dotsPerMM, horizontalLines, verticalLines );
588  }
589  else
590  {
591  drawGridNoTransform( context, dotsPerMM, horizontalLines, verticalLines );
592  }
593 
594  p->restore();
595 
596  p->setClipping( false );
597 #ifdef Q_OS_MAC
598  //QPainter::setClipping(false) seems to be broken on OSX (#12747). So we hack around it by
599  //setting a larger clip rect
600  p->setClipRect( mMap->mapRectFromScene( mMap->sceneBoundingRect() ).adjusted( -10, -10, 10, 10 ) );
601 #endif
602 
603  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
604  {
605  drawGridFrame( p, horizontalLines, verticalLines );
606  }
607 
608  if ( mShowGridAnnotation )
609  {
610  drawCoordinateAnnotations( p, horizontalLines, verticalLines, context.expressionContext() );
611  }
612 }
613 
614 void QgsLayoutItemMapGrid::drawGridNoTransform( QgsRenderContext &context, double dotsPerMM, QList< QPair< double, QLineF > > &horizontalLines,
615  QList< QPair< double, QLineF > > &verticalLines, bool calculateLinesOnly ) const
616 {
617  //get line positions
618  yGridLines( verticalLines );
619  xGridLines( horizontalLines );
620 
621  if ( calculateLinesOnly )
622  return;
623 
624  QList< QPair< double, QLineF > >::const_iterator vIt = verticalLines.constBegin();
625  QList< QPair< double, QLineF > >::const_iterator hIt = horizontalLines.constBegin();
626 
627  //simple approach: draw vertical lines first, then horizontal ones
628  if ( mGridStyle == QgsLayoutItemMapGrid::Solid )
629  {
630  //we need to scale line coordinates to dots, rather than mm, since the painter has already been scaled to dots
631  //this is done by multiplying each line coordinate by dotsPerMM
632  QLineF line;
633  for ( ; vIt != verticalLines.constEnd(); ++vIt )
634  {
635  line = QLineF( vIt->second.p1() * dotsPerMM, vIt->second.p2() * dotsPerMM );
636  drawGridLine( line, context );
637  }
638 
639  for ( ; hIt != horizontalLines.constEnd(); ++hIt )
640  {
641  line = QLineF( hIt->second.p1() * dotsPerMM, hIt->second.p2() * dotsPerMM );
642  drawGridLine( line, context );
643  }
644  }
645  else if ( mGridStyle != QgsLayoutItemMapGrid::FrameAnnotationsOnly ) //cross or markers
646  {
647  QPointF intersectionPoint, crossEnd1, crossEnd2;
648  for ( ; vIt != verticalLines.constEnd(); ++vIt )
649  {
650  //test for intersection with every horizontal line
651  hIt = horizontalLines.constBegin();
652  for ( ; hIt != horizontalLines.constEnd(); ++hIt )
653  {
654  if ( hIt->second.intersect( vIt->second, &intersectionPoint ) == QLineF::BoundedIntersection )
655  {
656  if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
657  {
658  //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
659  crossEnd1 = ( ( intersectionPoint - vIt->second.p1() ).manhattanLength() > 0.01 ) ?
660  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, vIt->second.p1(), mCrossLength ) : intersectionPoint;
661  crossEnd2 = ( ( intersectionPoint - vIt->second.p2() ).manhattanLength() > 0.01 ) ?
662  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, vIt->second.p2(), mCrossLength ) : intersectionPoint;
663  //draw line using coordinates scaled to dots
664  drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
665  }
666  else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
667  {
668  drawGridMarker( intersectionPoint * dotsPerMM, context );
669  }
670  }
671  }
672  }
673  if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
674  {
675  //markers mode, so we have no need to process horizontal lines (we've already
676  //drawn markers on the intersections between horizontal and vertical lines)
677  return;
678  }
679 
680  hIt = horizontalLines.constBegin();
681  for ( ; hIt != horizontalLines.constEnd(); ++hIt )
682  {
683  vIt = verticalLines.constBegin();
684  for ( ; vIt != verticalLines.constEnd(); ++vIt )
685  {
686  if ( vIt->second.intersect( hIt->second, &intersectionPoint ) == QLineF::BoundedIntersection )
687  {
688  //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
689  crossEnd1 = ( ( intersectionPoint - hIt->second.p1() ).manhattanLength() > 0.01 ) ?
690  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, hIt->second.p1(), mCrossLength ) : intersectionPoint;
691  crossEnd2 = ( ( intersectionPoint - hIt->second.p2() ).manhattanLength() > 0.01 ) ?
692  QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, hIt->second.p2(), mCrossLength ) : intersectionPoint;
693  //draw line using coordinates scaled to dots
694  drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
695  }
696  }
697  }
698  }
699 }
700 
701 void QgsLayoutItemMapGrid::drawGridFrame( QPainter *p, const QList< QPair< double, QLineF > > &hLines, const QList< QPair< double, QLineF > > &vLines, GridExtension *extension ) const
702 {
703  if ( p )
704  {
705  p->save();
706  p->setRenderHint( QPainter::Antialiasing );
707  }
708 
709  //Sort the coordinate positions for each side
710  QMap< double, double > leftGridFrame;
711  QMap< double, double > rightGridFrame;
712  QMap< double, double > topGridFrame;
713  QMap< double, double > bottomGridFrame;
714 
715  sortGridLinesOnBorders( hLines, vLines, leftGridFrame, rightGridFrame, topGridFrame, bottomGridFrame );
716 
718  {
719  drawGridFrameBorder( p, leftGridFrame, QgsLayoutItemMapGrid::Left, extension ? &extension->left : nullptr );
720  }
722  {
723  drawGridFrameBorder( p, rightGridFrame, QgsLayoutItemMapGrid::Right, extension ? &extension->right : nullptr );
724  }
726  {
727  drawGridFrameBorder( p, topGridFrame, QgsLayoutItemMapGrid::Top, extension ? &extension->top : nullptr );
728  }
730  {
731  drawGridFrameBorder( p, bottomGridFrame, QgsLayoutItemMapGrid::Bottom, extension ? &extension->bottom : nullptr );
732  }
733  if ( p )
734  p->restore();
735 }
736 
737 void QgsLayoutItemMapGrid::drawGridLine( const QLineF &line, QgsRenderContext &context ) const
738 {
739  QPolygonF poly;
740  poly << line.p1() << line.p2();
741  drawGridLine( poly, context );
742 }
743 
744 void QgsLayoutItemMapGrid::drawGridLine( const QPolygonF &line, QgsRenderContext &context ) const
745 {
746  if ( !mMap || !mMap->layout() || !mGridLineSymbol )
747  {
748  return;
749  }
750 
751  mGridLineSymbol->startRender( context );
752  mGridLineSymbol->renderPolyline( line, nullptr, context );
753  mGridLineSymbol->stopRender( context );
754 }
755 
756 void QgsLayoutItemMapGrid::drawGridMarker( QPointF point, QgsRenderContext &context ) const
757 {
758  if ( !mMap || !mMap->layout() || !mGridMarkerSymbol )
759  {
760  return;
761  }
762 
763  mGridMarkerSymbol->startRender( context );
764  mGridMarkerSymbol->renderPoint( point, nullptr, context );
765  mGridMarkerSymbol->stopRender( context );
766 }
767 
768 void QgsLayoutItemMapGrid::drawGridFrameBorder( QPainter *p, const QMap< double, double > &borderPos, QgsLayoutItemMapGrid::BorderSide border, double *extension ) const
769 {
770  if ( !mMap )
771  {
772  return;
773  }
774 
775  switch ( mGridFrameStyle )
776  {
779  drawGridFrameZebraBorder( p, borderPos, border, extension );
780  break;
784  drawGridFrameTicks( p, borderPos, border, extension );
785  break;
786 
789  drawGridFrameLineBorder( p, border, extension );
790  break;
791 
793  break;
794  }
795 
796 }
797 
798 void QgsLayoutItemMapGrid::drawGridFrameZebraBorder( QPainter *p, const QMap< double, double > &borderPos, QgsLayoutItemMapGrid::BorderSide border, double *extension ) const
799 {
800  if ( !mMap )
801  {
802  return;
803  }
804 
805  if ( extension )
806  {
807  *extension = mGridFrameMargin + mGridFrameWidth + mGridFramePenThickness / 2.0;
808  return;
809  }
810 
811  QMap< double, double > pos = borderPos;
812 
813  double currentCoord = 0.0;
814  bool color1 = false;
815  double x = 0;
816  double y = 0;
817  double width = 0;
818  double height = 0;
819 
820  bool drawTLBox = false;
821  bool drawTRBox = false;
822  bool drawBLBox = false;
823  bool drawBRBox = false;
824 
825  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
826  {
827  pos.insert( mMap->rect().height(), mMap->rect().height() );
829  {
830  drawBLBox = border == QgsLayoutItemMapGrid::Left;
831  drawBRBox = border == QgsLayoutItemMapGrid::Right;
832  }
834  {
835  drawTLBox = border == QgsLayoutItemMapGrid::Left;
836  drawTRBox = border == QgsLayoutItemMapGrid::Right;
837  }
838  if ( !drawTLBox && border == QgsLayoutItemMapGrid::Left )
839  color1 = true;
840  }
841  else if ( border == QgsLayoutItemMapGrid::Top || border == QgsLayoutItemMapGrid::Bottom )
842  {
843  pos.insert( mMap->rect().width(), mMap->rect().width() );
844  }
845 
846  //set pen to current frame pen
847  QPen framePen = QPen( mGridFramePenColor );
848  framePen.setWidthF( mGridFramePenThickness );
849  framePen.setJoinStyle( Qt::MiterJoin );
850  p->setPen( framePen );
851 
852  QMap< double, double >::const_iterator posIt = pos.constBegin();
853  for ( ; posIt != pos.constEnd(); ++posIt )
854  {
855  p->setBrush( QBrush( color1 ? mGridFrameFillColor1 : mGridFrameFillColor2 ) );
856  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
857  {
858  height = posIt.key() - currentCoord;
859  width = mGridFrameWidth;
860  x = ( border == QgsLayoutItemMapGrid::Left ) ? -( mGridFrameWidth + mGridFrameMargin ) : mMap->rect().width() + mGridFrameMargin;
861  y = currentCoord;
862  }
863  else //top or bottom
864  {
865  height = mGridFrameWidth;
866  width = posIt.key() - currentCoord;
867  x = currentCoord;
868  y = ( border == QgsLayoutItemMapGrid::Top ) ? -( mGridFrameWidth + mGridFrameMargin ) : mMap->rect().height() + mGridFrameMargin;
869  }
870  p->drawRect( QRectF( x, y, width, height ) );
871  currentCoord = posIt.key();
872  color1 = !color1;
873  }
874 
875  if ( mGridFrameStyle == ZebraNautical || qgsDoubleNear( mGridFrameMargin, 0.0 ) )
876  {
877  //draw corners
878  width = height = ( mGridFrameWidth + mGridFrameMargin ) ;
879  p->setBrush( QBrush( mGridFrameFillColor1 ) );
880  if ( drawTLBox )
881  p->drawRect( QRectF( -( mGridFrameWidth + mGridFrameMargin ), -( mGridFrameWidth + mGridFrameMargin ), width, height ) );
882  if ( drawTRBox )
883  p->drawRect( QRectF( mMap->rect().width(), -( mGridFrameWidth + mGridFrameMargin ), width, height ) );
884  if ( drawBLBox )
885  p->drawRect( QRectF( -( mGridFrameWidth + mGridFrameMargin ), mMap->rect().height(), width, height ) );
886  if ( drawBRBox )
887  p->drawRect( QRectF( mMap->rect().width(), mMap->rect().height(), width, height ) );
888  }
889 }
890 
891 void QgsLayoutItemMapGrid::drawGridFrameTicks( QPainter *p, const QMap< double, double > &borderPos, QgsLayoutItemMapGrid::BorderSide border, double *extension ) const
892 {
893  if ( !mMap )
894  {
895  return;
896  }
897 
898  if ( extension )
899  {
900  if ( mGridFrameStyle != QgsLayoutItemMapGrid::InteriorTicks )
901  *extension = mGridFrameMargin + mGridFrameWidth;
902  return;
903  }
904 
905  double x = 0;
906  double y = 0;
907  double width = 0;
908  double height = 0;
909 
910  //set pen to current frame pen
911  QPen framePen = QPen( mGridFramePenColor );
912  framePen.setWidthF( mGridFramePenThickness );
913  framePen.setCapStyle( Qt::FlatCap );
914  p->setBrush( Qt::NoBrush );
915  p->setPen( framePen );
916 
917  QMap< double, double >::const_iterator posIt = borderPos.constBegin();
918  for ( ; posIt != borderPos.constEnd(); ++posIt )
919  {
920  if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
921  {
922  y = posIt.key();
923  height = 0;
924  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
925  {
926  width = mGridFrameWidth;
927  x = ( border == QgsLayoutItemMapGrid::Left ) ? 0 + mGridFrameMargin : mMap->rect().width() - mGridFrameWidth - mGridFrameMargin;
928  }
929  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
930  {
931  width = mGridFrameWidth;
932  x = ( border == QgsLayoutItemMapGrid::Left ) ? - mGridFrameWidth - mGridFrameMargin : mMap->rect().width() + mGridFrameMargin;
933  }
934  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks )
935  {
936  width = mGridFrameWidth * 2;
937  x = ( border == QgsLayoutItemMapGrid::Left ) ? - mGridFrameWidth - mGridFrameMargin : mMap->rect().width() - mGridFrameWidth + mGridFrameMargin;
938  }
939  }
940  else //top or bottom
941  {
942  x = posIt.key();
943  width = 0;
944  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
945  {
946  height = mGridFrameWidth;
947  y = ( border == QgsLayoutItemMapGrid::Top ) ? 0 + mGridFrameMargin : mMap->rect().height() - mGridFrameWidth - mGridFrameMargin;
948  }
949  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
950  {
951  height = mGridFrameWidth;
952  y = ( border == QgsLayoutItemMapGrid::Top ) ? -mGridFrameWidth - mGridFrameMargin : mMap->rect().height() + mGridFrameMargin;
953  }
954  else if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks )
955  {
956  height = mGridFrameWidth * 2;
957  y = ( border == QgsLayoutItemMapGrid::Top ) ? -mGridFrameWidth - mGridFrameMargin : mMap->rect().height() - mGridFrameWidth + mGridFrameMargin;
958  }
959  }
960  p->drawLine( QLineF( x, y, x + width, y + height ) );
961  }
962 }
963 
964 void QgsLayoutItemMapGrid::drawGridFrameLineBorder( QPainter *p, QgsLayoutItemMapGrid::BorderSide border, double *extension ) const
965 {
966  if ( !mMap )
967  {
968  return;
969  }
970 
971  if ( extension )
972  {
973  *extension = mGridFrameMargin + mGridFramePenThickness / 2.0;
974  return;
975  }
976 
977  //set pen to current frame pen
978  QPen framePen = QPen( mGridFramePenColor );
979  framePen.setWidthF( mGridFramePenThickness );
980  framePen.setCapStyle( Qt::SquareCap );
981  p->setBrush( Qt::NoBrush );
982  p->setPen( framePen );
983 
984  const bool drawDiagonals = mGridFrameStyle == LineBorderNautical && !qgsDoubleNear( mGridFrameMargin, 0.0 );
985 
986  switch ( border )
987  {
989  p->drawLine( QLineF( 0 - mGridFrameMargin, 0 - mGridFrameMargin, 0 - mGridFrameMargin, mMap->rect().height() + mGridFrameMargin ) );
990  //corner left-top
991  if ( drawDiagonals )
992  {
993  const double X1 = 0 - mGridFrameMargin + mGridFramePenThickness / 2.0;
994  const double Y1 = 0 - mGridFrameMargin + mGridFramePenThickness / 2.0;
995  p->drawLine( QLineF( 0, 0, X1, Y1 ) );
996  }
997  break;
999  p->drawLine( QLineF( mMap->rect().width() + mGridFrameMargin, 0 - mGridFrameMargin, mMap->rect().width() + mGridFrameMargin, mMap->rect().height() + mGridFrameMargin ) );
1000  //corner right-bottom
1001  if ( drawDiagonals )
1002  {
1003  const double X1 = mMap->rect().width() + mGridFrameMargin - mGridFramePenThickness / 2.0 ;
1004  const double Y1 = mMap->rect().height() + mGridFrameMargin - mGridFramePenThickness / 2.0 ;
1005  p->drawLine( QLineF( mMap->rect().width(), mMap->rect().height(), X1, Y1 ) );
1006  }
1007  break;
1009  p->drawLine( QLineF( 0 - mGridFrameMargin, 0 - mGridFrameMargin, mMap->rect().width() + mGridFrameMargin, 0 - mGridFrameMargin ) );
1010  //corner right-top
1011  if ( drawDiagonals )
1012  {
1013  const double X1 = mMap->rect().width() + mGridFrameMargin - mGridFramePenThickness / 2.0 ;
1014  const double Y1 = 0 - mGridFrameMargin + mGridFramePenThickness / 2.0 ;
1015  p->drawLine( QLineF( mMap->rect().width(), 0, X1, Y1 ) );
1016  }
1017  break;
1019  p->drawLine( QLineF( 0 - mGridFrameMargin, mMap->rect().height() + mGridFrameMargin, mMap->rect().width() + mGridFrameMargin, mMap->rect().height() + mGridFrameMargin ) );
1020  //corner left-bottom
1021  if ( drawDiagonals )
1022  {
1023  const double X1 = 0 - mGridFrameMargin + mGridFramePenThickness / 2.0 ;
1024  const double Y1 = mMap->rect().height() + mGridFrameMargin - mGridFramePenThickness / 2.0 ;
1025  p->drawLine( QLineF( 0, mMap->rect().height(), X1, Y1 ) );
1026  }
1027  break;
1028  }
1029 }
1030 
1031 void QgsLayoutItemMapGrid::drawCoordinateAnnotations( QPainter *p, const QList< QPair< double, QLineF > > &hLines, const QList< QPair< double, QLineF > > &vLines, QgsExpressionContext &expressionContext,
1032  GridExtension *extension ) const
1033 {
1034  QString currentAnnotationString;
1035  QList< QPair< double, QLineF > >::const_iterator it = hLines.constBegin();
1036  for ( ; it != hLines.constEnd(); ++it )
1037  {
1038  currentAnnotationString = gridAnnotationString( it->first, QgsLayoutItemMapGrid::Latitude, expressionContext );
1039  drawCoordinateAnnotation( p, it->second.p1(), currentAnnotationString, QgsLayoutItemMapGrid::Latitude, extension );
1040  drawCoordinateAnnotation( p, it->second.p2(), currentAnnotationString, QgsLayoutItemMapGrid::Latitude, extension );
1041  }
1042 
1043  it = vLines.constBegin();
1044  for ( ; it != vLines.constEnd(); ++it )
1045  {
1046  currentAnnotationString = gridAnnotationString( it->first, QgsLayoutItemMapGrid::Longitude, expressionContext );
1047  drawCoordinateAnnotation( p, it->second.p1(), currentAnnotationString, QgsLayoutItemMapGrid::Longitude, extension );
1048  drawCoordinateAnnotation( p, it->second.p2(), currentAnnotationString, QgsLayoutItemMapGrid::Longitude, extension );
1049  }
1050 }
1051 
1052 void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QPainter *p, QPointF pos, const QString &annotationString, const AnnotationCoordinate coordinateType, GridExtension *extension ) const
1053 {
1054  if ( !mMap )
1055  {
1056  return;
1057  }
1058 
1059  QgsLayoutItemMapGrid::BorderSide frameBorder = borderForLineCoord( pos, coordinateType );
1060  double textWidth = QgsLayoutUtils::textWidthMM( mGridAnnotationFont, annotationString );
1061  //relevant for annotations is the height of digits
1062  double textHeight = extension ? QgsLayoutUtils::fontAscentMM( mGridAnnotationFont )
1063  : QgsLayoutUtils::fontHeightCharacterMM( mGridAnnotationFont, QChar( '0' ) );
1064  double xpos = pos.x();
1065  double ypos = pos.y();
1066  int rotation = 0;
1067 
1068  double gridFrameDistance = 0;
1069  switch ( mGridFrameStyle )
1070  {
1071  case InteriorTicks:
1072  case ExteriorTicks:
1073  case InteriorExteriorTicks:
1074  gridFrameDistance = mGridFrameWidth;
1075  break;
1076 
1081  gridFrameDistance += ( mGridFramePenThickness / 2.0 );
1082  break;
1083 
1085  break;
1086  }
1087 
1088  if ( frameBorder == QgsLayoutItemMapGrid::Left )
1089  {
1090  if ( mLeftGridAnnotationDisplay == QgsLayoutItemMapGrid::HideAll ||
1091  ( coordinateType == Longitude && mLeftGridAnnotationDisplay == QgsLayoutItemMapGrid::LatitudeOnly ) ||
1092  ( coordinateType == Latitude && mLeftGridAnnotationDisplay == QgsLayoutItemMapGrid::LongitudeOnly ) )
1093  {
1094  return;
1095  }
1097  {
1098  gridFrameDistance = 0;
1099  }
1100 
1101  if ( mLeftGridAnnotationPosition == QgsLayoutItemMapGrid::InsideMapFrame )
1102  {
1103  if ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1104  {
1105  gridFrameDistance = 0;
1106  }
1107  if ( mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::Vertical || mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1108  {
1109  xpos += textHeight + mAnnotationFrameDistance + gridFrameDistance;
1110  ypos += textWidth / 2.0;
1111  rotation = 270;
1112  }
1113  else if ( mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1114  {
1115  xpos += ( mAnnotationFrameDistance + gridFrameDistance );
1116  ypos -= textWidth / 2.0;
1117  rotation = 90;
1118  }
1119  else
1120  {
1121  xpos += mAnnotationFrameDistance + gridFrameDistance;
1122  ypos += textHeight / 2.0;
1123  }
1124  }
1125  else if ( mLeftGridAnnotationPosition == QgsLayoutItemMapGrid::OutsideMapFrame ) //Outside map frame
1126  {
1127  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1128  {
1129  gridFrameDistance = 0;
1130  }
1131  if ( mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::Vertical || mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1132  {
1133  xpos -= ( mAnnotationFrameDistance + gridFrameDistance );
1134  ypos += textWidth / 2.0;
1135  rotation = 270;
1136  if ( extension )
1137  extension->left = std::max( extension->left, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1138  }
1139  else if ( mLeftGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1140  {
1141  xpos -= textHeight + mAnnotationFrameDistance + gridFrameDistance;
1142  ypos -= textWidth / 2.0;
1143  rotation = 90;
1144  if ( extension )
1145  extension->left = std::max( extension->left, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1146  }
1147  else
1148  {
1149  xpos -= ( textWidth + mAnnotationFrameDistance + gridFrameDistance );
1150  ypos += textHeight / 2.0;
1151  if ( extension )
1152  extension->left = std::max( extension->left, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1153  }
1154  }
1155  else
1156  {
1157  return;
1158  }
1159  }
1160  else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1161  {
1162  if ( mRightGridAnnotationDisplay == QgsLayoutItemMapGrid::HideAll ||
1163  ( coordinateType == Longitude && mRightGridAnnotationDisplay == QgsLayoutItemMapGrid::LatitudeOnly ) ||
1164  ( coordinateType == Latitude && mRightGridAnnotationDisplay == QgsLayoutItemMapGrid::LongitudeOnly ) )
1165  {
1166  return;
1167  }
1169  {
1170  gridFrameDistance = 0;
1171  }
1172 
1173  if ( mRightGridAnnotationPosition == QgsLayoutItemMapGrid::InsideMapFrame )
1174  {
1175  if ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1176  {
1177  gridFrameDistance = 0;
1178  }
1179  if ( mRightGridAnnotationDirection == QgsLayoutItemMapGrid::Vertical )
1180  {
1181  xpos -= mAnnotationFrameDistance + gridFrameDistance;
1182  ypos += textWidth / 2.0;
1183  rotation = 270;
1184  }
1185  else if ( mRightGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending || mRightGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1186  {
1187  xpos -= textHeight + mAnnotationFrameDistance + gridFrameDistance;
1188  ypos -= textWidth / 2.0;
1189  rotation = 90;
1190  }
1191  else
1192  {
1193  xpos -= textWidth + mAnnotationFrameDistance + gridFrameDistance;
1194  ypos += textHeight / 2.0;
1195  }
1196  }
1197  else if ( mRightGridAnnotationPosition == QgsLayoutItemMapGrid::OutsideMapFrame )//OutsideMapFrame
1198  {
1199  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1200  {
1201  gridFrameDistance = 0;
1202  }
1203  if ( mRightGridAnnotationDirection == QgsLayoutItemMapGrid::Vertical )
1204  {
1205  xpos += ( textHeight + mAnnotationFrameDistance + gridFrameDistance );
1206  ypos += textWidth / 2.0;
1207  rotation = 270;
1208  if ( extension )
1209  extension->right = std::max( extension->right, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1210  }
1211  else if ( mRightGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending || mRightGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1212  {
1213  xpos += ( mAnnotationFrameDistance + gridFrameDistance );
1214  ypos -= textWidth / 2.0;
1215  rotation = 90;
1216  if ( extension )
1217  extension->right = std::max( extension->right, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1218  }
1219  else //Horizontal
1220  {
1221  xpos += ( mAnnotationFrameDistance + gridFrameDistance );
1222  ypos += textHeight / 2.0;
1223  if ( extension )
1224  extension->right = std::max( extension->right, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1225  }
1226  }
1227  else
1228  {
1229  return;
1230  }
1231  }
1232  else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1233  {
1234  if ( mBottomGridAnnotationDisplay == QgsLayoutItemMapGrid::HideAll ||
1235  ( coordinateType == Longitude && mBottomGridAnnotationDisplay == QgsLayoutItemMapGrid::LatitudeOnly ) ||
1236  ( coordinateType == Latitude && mBottomGridAnnotationDisplay == QgsLayoutItemMapGrid::LongitudeOnly ) )
1237  {
1238  return;
1239  }
1241  {
1242  gridFrameDistance = 0;
1243  }
1244 
1245  if ( mBottomGridAnnotationPosition == QgsLayoutItemMapGrid::InsideMapFrame )
1246  {
1247  if ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1248  {
1249  gridFrameDistance = 0;
1250  }
1251  if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::Horizontal || mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1252  {
1253  ypos -= mAnnotationFrameDistance + gridFrameDistance;
1254  xpos -= textWidth / 2.0;
1255  }
1256  else if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1257  {
1258  xpos -= textHeight / 2.0;
1259  ypos -= textWidth + mAnnotationFrameDistance + gridFrameDistance;
1260  rotation = 90;
1261  }
1262  else //Vertical
1263  {
1264  xpos += textHeight / 2.0;
1265  ypos -= mAnnotationFrameDistance + gridFrameDistance;
1266  rotation = 270;
1267  }
1268  }
1269  else if ( mBottomGridAnnotationPosition == QgsLayoutItemMapGrid::OutsideMapFrame ) //OutsideMapFrame
1270  {
1271  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1272  {
1273  gridFrameDistance = 0;
1274  }
1275  if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::Horizontal || mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1276  {
1277  ypos += ( mAnnotationFrameDistance + textHeight + gridFrameDistance );
1278  xpos -= textWidth / 2.0;
1279  if ( extension )
1280  {
1281  extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1282  extension->left = std::max( extension->left, textWidth / 2.0 ); // annotation at bottom left/right may extend outside the bounds
1283  extension->right = std::max( extension->right, textWidth / 2.0 );
1284  }
1285  }
1286  else if ( mBottomGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1287  {
1288  xpos -= textHeight / 2.0;
1289  ypos += gridFrameDistance + mAnnotationFrameDistance;
1290  rotation = 90;
1291  if ( extension )
1292  extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1293  }
1294  else //Vertical
1295  {
1296  xpos += textHeight / 2.0;
1297  ypos += ( textWidth + mAnnotationFrameDistance + gridFrameDistance );
1298  rotation = 270;
1299  if ( extension )
1300  extension->bottom = std::max( extension->bottom, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1301  }
1302  }
1303  else
1304  {
1305  return;
1306  }
1307  }
1308  else //top
1309  {
1310  if ( mTopGridAnnotationDisplay == QgsLayoutItemMapGrid::HideAll ||
1311  ( coordinateType == Longitude && mTopGridAnnotationDisplay == QgsLayoutItemMapGrid::LatitudeOnly ) ||
1312  ( coordinateType == Latitude && mTopGridAnnotationDisplay == QgsLayoutItemMapGrid::LongitudeOnly ) )
1313  {
1314  return;
1315  }
1317  {
1318  gridFrameDistance = 0;
1319  }
1320 
1321  if ( mTopGridAnnotationPosition == QgsLayoutItemMapGrid::InsideMapFrame )
1322  {
1323  if ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1324  {
1325  gridFrameDistance = 0;
1326  }
1327  if ( mTopGridAnnotationDirection == QgsLayoutItemMapGrid::Horizontal || mTopGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1328  {
1329  xpos -= textWidth / 2.0;
1330  ypos += textHeight + mAnnotationFrameDistance + gridFrameDistance;
1331  }
1332  else if ( mTopGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1333  {
1334  xpos -= textHeight / 2.0;
1335  ypos += mAnnotationFrameDistance + gridFrameDistance;
1336  rotation = 90;
1337  }
1338  else //Vertical
1339  {
1340  xpos += textHeight / 2.0;
1341  ypos += textWidth + mAnnotationFrameDistance + gridFrameDistance;
1342  rotation = 270;
1343  }
1344  }
1345  else if ( mTopGridAnnotationPosition == QgsLayoutItemMapGrid::OutsideMapFrame ) //OutsideMapFrame
1346  {
1347  if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1348  {
1349  gridFrameDistance = 0;
1350  }
1351  if ( mTopGridAnnotationDirection == QgsLayoutItemMapGrid::Horizontal || mTopGridAnnotationDirection == QgsLayoutItemMapGrid::BoundaryDirection )
1352  {
1353  xpos -= textWidth / 2.0;
1354  ypos -= ( mAnnotationFrameDistance + gridFrameDistance );
1355  if ( extension )
1356  extension->top = std::max( extension->top, mAnnotationFrameDistance + gridFrameDistance + textHeight );
1357  }
1358  else if ( mTopGridAnnotationDirection == QgsLayoutItemMapGrid::VerticalDescending )
1359  {
1360  xpos -= textHeight / 2.0;
1361  ypos -= textWidth + mAnnotationFrameDistance + gridFrameDistance;
1362  rotation = 90;
1363  if ( extension )
1364  extension->top = std::max( extension->top, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1365  }
1366  else //Vertical
1367  {
1368  xpos += textHeight / 2.0;
1369  ypos -= ( mAnnotationFrameDistance + gridFrameDistance );
1370  rotation = 270;
1371  if ( extension )
1372  extension->top = std::max( extension->top, mAnnotationFrameDistance + gridFrameDistance + textWidth );
1373  }
1374  }
1375  else
1376  {
1377  return;
1378  }
1379  }
1380 
1381  if ( extension || !p )
1382  return;
1383 
1384  drawAnnotation( p, QPointF( xpos, ypos ), rotation, annotationString );
1385 }
1386 
1387 void QgsLayoutItemMapGrid::drawAnnotation( QPainter *p, QPointF pos, int rotation, const QString &annotationText ) const
1388 {
1389  if ( !mMap )
1390  {
1391  return;
1392  }
1393 
1394  p->save();
1395  p->translate( pos );
1396  p->rotate( rotation );
1397  QgsLayoutUtils::drawText( p, QPointF( 0, 0 ), annotationText, mGridAnnotationFont, mGridAnnotationFontColor );
1398  p->restore();
1399 }
1400 
1401 QString QgsLayoutItemMapGrid::gridAnnotationString( double value, QgsLayoutItemMapGrid::AnnotationCoordinate coord, QgsExpressionContext &expressionContext ) const
1402 {
1403  //check if we are using degrees (ie, geographic crs)
1404  bool geographic = false;
1405  if ( mCRS.isValid() && mCRS.isGeographic() )
1406  {
1407  geographic = true;
1408  }
1409  else if ( mMap && mMap->layout() )
1410  {
1411  geographic = mMap->crs().isGeographic();
1412  }
1413 
1414  if ( geographic && coord == QgsLayoutItemMapGrid::Longitude &&
1415  ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal || mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix ) )
1416  {
1417  // wrap around longitudes > 180 or < -180 degrees, so that, e.g., "190E" -> "170W"
1418  double wrappedX = std::fmod( value, 360.0 );
1419  if ( wrappedX > 180.0 )
1420  {
1421  value = wrappedX - 360.0;
1422  }
1423  else if ( wrappedX < -180.0 )
1424  {
1425  value = wrappedX + 360.0;
1426  }
1427  }
1428 
1429  if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal )
1430  {
1431  return QString::number( value, 'f', mGridAnnotationPrecision );
1432  }
1433  else if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix )
1434  {
1435  QString hemisphere;
1436 
1437  double coordRounded = qgsRound( value, mGridAnnotationPrecision );
1438  if ( coord == QgsLayoutItemMapGrid::Longitude )
1439  {
1440  //don't use E/W suffixes if ambiguous (e.g., 180 degrees)
1441  if ( !geographic || ( coordRounded != 180.0 && coordRounded != 0.0 ) )
1442  {
1443  hemisphere = value < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
1444  }
1445  }
1446  else
1447  {
1448  //don't use N/S suffixes if ambiguous (e.g., 0 degrees)
1449  if ( !geographic || coordRounded != 0.0 )
1450  {
1451  hemisphere = value < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
1452  }
1453  }
1454  if ( geographic )
1455  {
1456  //insert degree symbol for geographic coordinates
1457  return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + QChar( 176 ) + hemisphere;
1458  }
1459  else
1460  {
1461  return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + hemisphere;
1462  }
1463  }
1464  else if ( mGridAnnotationFormat == CustomFormat )
1465  {
1466  expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), value, true ) );
1467  expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), coord == QgsLayoutItemMapGrid::Longitude ? "x" : "y", true ) );
1468  if ( !mGridAnnotationExpression )
1469  {
1470  mGridAnnotationExpression.reset( new QgsExpression( mGridAnnotationExpressionString ) );
1471  mGridAnnotationExpression->prepare( &expressionContext );
1472  }
1473  return mGridAnnotationExpression->evaluate( &expressionContext ).toString();
1474  }
1475 
1477  QgsCoordinateFormatter::FormatFlags flags = nullptr;
1478  switch ( mGridAnnotationFormat )
1479  {
1480  case Decimal:
1481  case DecimalWithSuffix:
1482  case CustomFormat:
1483  break; // already handled above
1484 
1485  case DegreeMinute:
1488  break;
1489 
1490  case DegreeMinuteSecond:
1493  break;
1494 
1495  case DegreeMinuteNoSuffix:
1497  flags = nullptr;
1498  break;
1499 
1500  case DegreeMinutePadded:
1503  break;
1504 
1507  flags = nullptr;
1508  break;
1509 
1513  break;
1514  }
1515 
1516  switch ( coord )
1517  {
1518  case Longitude:
1519  return QgsCoordinateFormatter::formatX( value, format, mGridAnnotationPrecision, flags );
1520 
1521  case Latitude:
1522  return QgsCoordinateFormatter::formatY( value, format, mGridAnnotationPrecision, flags );
1523  }
1524 
1525  return QString(); // no warnings
1526 }
1527 
1528 int QgsLayoutItemMapGrid::xGridLines( QList< QPair< double, QLineF > > &lines ) const
1529 {
1530  lines.clear();
1531  if ( !mMap || mGridIntervalY <= 0.0 )
1532  {
1533  return 1;
1534  }
1535 
1536 
1537  QPolygonF mapPolygon = mMap->transformedMapPolygon();
1538  QRectF mapBoundingRect = mapPolygon.boundingRect();
1539  double gridIntervalY = mGridIntervalY;
1540  double gridOffsetY = mGridOffsetY;
1541  double annotationScale = 1.0;
1542  if ( mGridUnit != MapUnit )
1543  {
1544  mapBoundingRect = mMap->rect();
1545  mapPolygon = QPolygonF( mMap->rect() );
1546  if ( mGridUnit == CM )
1547  {
1548  annotationScale = 0.1;
1549  gridIntervalY *= 10;
1550  gridOffsetY *= 10;
1551  }
1552  }
1553 
1554  //consider to round up to the next step in case the left boundary is > 0
1555  double roundCorrection = mapBoundingRect.top() > 0 ? 1.0 : 0.0;
1556  double currentLevel = static_cast< int >( ( mapBoundingRect.top() - gridOffsetY ) / gridIntervalY + roundCorrection ) * gridIntervalY + gridOffsetY;
1557 
1558  int gridLineCount = 0;
1559  if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || mGridUnit != MapUnit )
1560  {
1561  //no rotation. Do it 'the easy way'
1562 
1563  double yCanvasCoord;
1564  while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1565  {
1566  yCanvasCoord = mMap->rect().height() * ( 1 - ( currentLevel - mapBoundingRect.top() ) / mapBoundingRect.height() );
1567  lines.push_back( qMakePair( currentLevel * annotationScale, QLineF( 0, yCanvasCoord, mMap->rect().width(), yCanvasCoord ) ) );
1568  currentLevel += gridIntervalY;
1569  gridLineCount++;
1570  }
1571  return 0;
1572  }
1573 
1574  //the four border lines
1575  QVector<QLineF> borderLines;
1576  borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1577  borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1578  borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1579  borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1580 
1581  QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1582 
1583  while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1584  {
1585  intersectionList.clear();
1586  QLineF gridLine( mapBoundingRect.left(), currentLevel, mapBoundingRect.right(), currentLevel );
1587 
1588  QVector<QLineF>::const_iterator it = borderLines.constBegin();
1589  for ( ; it != borderLines.constEnd(); ++it )
1590  {
1591  QPointF intersectionPoint;
1592  if ( it->intersect( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1593  {
1594  intersectionList.push_back( intersectionPoint );
1595  if ( intersectionList.size() >= 2 )
1596  {
1597  break; //we already have two intersections, skip further tests
1598  }
1599  }
1600  }
1601 
1602  if ( intersectionList.size() >= 2 )
1603  {
1604  lines.push_back( qMakePair( currentLevel, QLineF( mMap->mapToItemCoords( intersectionList.at( 0 ) ), mMap->mapToItemCoords( intersectionList.at( 1 ) ) ) ) );
1605  gridLineCount++;
1606  }
1607  currentLevel += gridIntervalY;
1608  }
1609 
1610 
1611  return 0;
1612 }
1613 
1614 int QgsLayoutItemMapGrid::yGridLines( QList< QPair< double, QLineF > > &lines ) const
1615 {
1616  lines.clear();
1617  if ( !mMap || mGridIntervalX <= 0.0 )
1618  {
1619  return 1;
1620  }
1621 
1622  QPolygonF mapPolygon = mMap->transformedMapPolygon();
1623  QRectF mapBoundingRect = mapPolygon.boundingRect();
1624  double gridIntervalX = mGridIntervalX;
1625  double gridOffsetX = mGridOffsetX;
1626  double annotationScale = 1.0;
1627  if ( mGridUnit != MapUnit )
1628  {
1629  mapBoundingRect = mMap->rect();
1630  mapPolygon = QPolygonF( mMap->rect() );
1631  if ( mGridUnit == CM )
1632  {
1633  annotationScale = 0.1;
1634  gridIntervalX *= 10;
1635  gridOffsetX *= 10;
1636  }
1637  }
1638 
1639  //consider to round up to the next step in case the left boundary is > 0
1640  double roundCorrection = mapBoundingRect.left() > 0 ? 1.0 : 0.0;
1641  double currentLevel = static_cast< int >( ( mapBoundingRect.left() - gridOffsetX ) / gridIntervalX + roundCorrection ) * gridIntervalX + gridOffsetX;
1642 
1643  int gridLineCount = 0;
1644  if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || mGridUnit != MapUnit )
1645  {
1646  //no rotation. Do it 'the easy way'
1647  double xCanvasCoord;
1648  while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1649  {
1650  xCanvasCoord = mMap->rect().width() * ( currentLevel - mapBoundingRect.left() ) / mapBoundingRect.width();
1651  lines.push_back( qMakePair( currentLevel * annotationScale, QLineF( xCanvasCoord, 0, xCanvasCoord, mMap->rect().height() ) ) );
1652  currentLevel += gridIntervalX;
1653  gridLineCount++;
1654  }
1655  return 0;
1656  }
1657 
1658  //the four border lines
1659  QVector<QLineF> borderLines;
1660  borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1661  borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1662  borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1663  borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1664 
1665  QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1666 
1667  while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1668  {
1669  intersectionList.clear();
1670  QLineF gridLine( currentLevel, mapBoundingRect.bottom(), currentLevel, mapBoundingRect.top() );
1671 
1672  QVector<QLineF>::const_iterator it = borderLines.constBegin();
1673  for ( ; it != borderLines.constEnd(); ++it )
1674  {
1675  QPointF intersectionPoint;
1676  if ( it->intersect( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1677  {
1678  intersectionList.push_back( intersectionPoint );
1679  if ( intersectionList.size() >= 2 )
1680  {
1681  break; //we already have two intersections, skip further tests
1682  }
1683  }
1684  }
1685 
1686  if ( intersectionList.size() >= 2 )
1687  {
1688  lines.push_back( qMakePair( currentLevel, QLineF( mMap->mapToItemCoords( intersectionList.at( 0 ) ), mMap->mapToItemCoords( intersectionList.at( 1 ) ) ) ) );
1689  gridLineCount++;
1690  }
1691  currentLevel += gridIntervalX;
1692  }
1693 
1694  return 0;
1695 }
1696 
1697 int QgsLayoutItemMapGrid::xGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t, QList< QPair< double, QPolygonF > > &lines ) const
1698 {
1699  lines.clear();
1700  if ( !mMap || mGridIntervalY <= 0.0 )
1701  {
1702  return 1;
1703  }
1704 
1705  double roundCorrection = bbox.yMaximum() > 0 ? 1.0 : 0.0;
1706  double currentLevel = static_cast< int >( ( bbox.yMaximum() - mGridOffsetY ) / mGridIntervalY + roundCorrection ) * mGridIntervalY + mGridOffsetY;
1707 
1708  double minX = bbox.xMinimum();
1709  double maxX = bbox.xMaximum();
1710  double step = ( maxX - minX ) / 20;
1711 
1712  bool crosses180 = false;
1713  bool crossed180 = false;
1714  if ( mCRS.isGeographic() && ( minX > maxX ) )
1715  {
1716  //handle 180 degree longitude crossover
1717  crosses180 = true;
1718  step = ( maxX + 360.0 - minX ) / 20;
1719  }
1720 
1721  if ( qgsDoubleNear( step, 0.0 ) )
1722  return 1;
1723 
1724  int gridLineCount = 0;
1725  while ( currentLevel >= bbox.yMinimum() && gridLineCount < MAX_GRID_LINES )
1726  {
1727  QPolygonF gridLine;
1728  double currentX = minX;
1729  bool cont = true;
1730  while ( cont )
1731  {
1732  if ( ( !crosses180 || crossed180 ) && ( currentX > maxX ) )
1733  {
1734  cont = false;
1735  }
1736 
1737  try
1738  {
1739  QgsPointXY mapPoint = t.transform( currentX, currentLevel ); //transform back to map crs
1740  gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) ); //transform back to composer coords
1741  }
1742  catch ( QgsCsException &cse )
1743  {
1744  Q_UNUSED( cse );
1745  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1746  }
1747 
1748  currentX += step;
1749  if ( crosses180 && currentX > 180.0 )
1750  {
1751  currentX -= 360.0;
1752  crossed180 = true;
1753  }
1754  }
1755  crossed180 = false;
1756 
1757  QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1758  QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1759  for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1760  {
1761  if ( !( *lineIt ).isEmpty() )
1762  {
1763  lines.append( qMakePair( currentLevel, *lineIt ) );
1764  gridLineCount++;
1765  }
1766  }
1767  currentLevel -= mGridIntervalY;
1768  }
1769 
1770  return 0;
1771 }
1772 
1773 int QgsLayoutItemMapGrid::yGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t, QList< QPair< double, QPolygonF > > &lines ) const
1774 {
1775  lines.clear();
1776  if ( !mMap || mGridIntervalX <= 0.0 )
1777  {
1778  return 1;
1779  }
1780 
1781  double roundCorrection = bbox.xMinimum() > 0 ? 1.0 : 0.0;
1782  double currentLevel = static_cast< int >( ( bbox.xMinimum() - mGridOffsetX ) / mGridIntervalX + roundCorrection ) * mGridIntervalX + mGridOffsetX;
1783 
1784  double minY = bbox.yMinimum();
1785  double maxY = bbox.yMaximum();
1786  double step = ( maxY - minY ) / 20;
1787 
1788  if ( qgsDoubleNear( step, 0.0 ) )
1789  return 1;
1790 
1791  bool crosses180 = false;
1792  bool crossed180 = false;
1793  if ( mCRS.isGeographic() && ( bbox.xMinimum() > bbox.xMaximum() ) )
1794  {
1795  //handle 180 degree longitude crossover
1796  crosses180 = true;
1797  }
1798 
1799  int gridLineCount = 0;
1800  while ( ( currentLevel <= bbox.xMaximum() || ( crosses180 && !crossed180 ) ) && gridLineCount < MAX_GRID_LINES )
1801  {
1802  QPolygonF gridLine;
1803  double currentY = minY;
1804  bool cont = true;
1805  while ( cont )
1806  {
1807  if ( currentY > maxY )
1808  {
1809  cont = false;
1810  }
1811  try
1812  {
1813  //transform back to map crs
1814  QgsPointXY mapPoint = t.transform( currentLevel, currentY );
1815  //transform back to composer coords
1816  gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) );
1817  }
1818  catch ( QgsCsException &cse )
1819  {
1820  Q_UNUSED( cse );
1821  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1822  }
1823 
1824  currentY += step;
1825  }
1826  //clip grid line to map polygon
1827  QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1828  QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1829  for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1830  {
1831  if ( !( *lineIt ).isEmpty() )
1832  {
1833  lines.append( qMakePair( currentLevel, *lineIt ) );
1834  gridLineCount++;
1835  }
1836  }
1837  currentLevel += mGridIntervalX;
1838  if ( crosses180 && currentLevel > 180.0 )
1839  {
1840  currentLevel -= 360.0;
1841  crossed180 = true;
1842  }
1843  }
1844 
1845  return 0;
1846 }
1847 
1848 void QgsLayoutItemMapGrid::sortGridLinesOnBorders( const QList< QPair< double, QLineF > > &hLines, const QList< QPair< double, QLineF > > &vLines, QMap< double, double > &leftFrameEntries,
1849  QMap< double, double > &rightFrameEntries, QMap< double, double > &topFrameEntries, QMap< double, double > &bottomFrameEntries ) const
1850 {
1851  QList< QgsMapAnnotation > borderPositions;
1852  QList< QPair< double, QLineF > >::const_iterator it = hLines.constBegin();
1853  for ( ; it != hLines.constEnd(); ++it )
1854  {
1855  QgsMapAnnotation p1;
1856  p1.coordinate = it->first;
1857  p1.itemPosition = it->second.p1();
1858  p1.coordinateType = QgsLayoutItemMapGrid::Latitude;
1859  borderPositions << p1;
1860 
1861  QgsMapAnnotation p2;
1862  p2.coordinate = it->first;
1863  p2.itemPosition = it->second.p2();
1864  p2.coordinateType = QgsLayoutItemMapGrid::Latitude;
1865  borderPositions << p2;
1866  }
1867  it = vLines.constBegin();
1868  for ( ; it != vLines.constEnd(); ++it )
1869  {
1870  QgsMapAnnotation p1;
1871  p1.coordinate = it->first;
1872  p1.itemPosition = it->second.p1();
1873  p1.coordinateType = QgsLayoutItemMapGrid::Longitude;
1874  borderPositions << p1;
1875 
1876  QgsMapAnnotation p2;
1877  p2.coordinate = it->first;
1878  p2.itemPosition = it->second.p2();
1879  p2.coordinateType = QgsLayoutItemMapGrid::Longitude;
1880  borderPositions << p2;
1881  }
1882 
1883  QList< QgsMapAnnotation >::const_iterator bIt = borderPositions.constBegin();
1884  for ( ; bIt != borderPositions.constEnd(); ++bIt )
1885  {
1886  QgsLayoutItemMapGrid::BorderSide frameBorder = borderForLineCoord( bIt->itemPosition, bIt->coordinateType );
1887  if ( frameBorder == QgsLayoutItemMapGrid::Left && shouldShowDivisionForSide( bIt->coordinateType, QgsLayoutItemMapGrid::Left ) )
1888  {
1889  leftFrameEntries.insert( bIt->itemPosition.y(), bIt->coordinate );
1890  }
1891  else if ( frameBorder == QgsLayoutItemMapGrid::Right && shouldShowDivisionForSide( bIt->coordinateType, QgsLayoutItemMapGrid::Right ) )
1892  {
1893  rightFrameEntries.insert( bIt->itemPosition.y(), bIt->coordinate );
1894  }
1895  else if ( frameBorder == QgsLayoutItemMapGrid::Top && shouldShowDivisionForSide( bIt->coordinateType, QgsLayoutItemMapGrid::Top ) )
1896  {
1897  topFrameEntries.insert( bIt->itemPosition.x(), bIt->coordinate );
1898  }
1899  else if ( frameBorder == QgsLayoutItemMapGrid::Bottom && shouldShowDivisionForSide( bIt->coordinateType, QgsLayoutItemMapGrid::Bottom ) )
1900  {
1901  bottomFrameEntries.insert( bIt->itemPosition.x(), bIt->coordinate );
1902  }
1903  }
1904 }
1905 
1906 bool QgsLayoutItemMapGrid::shouldShowDivisionForSide( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::BorderSide side ) const
1907 {
1908  switch ( side )
1909  {
1911  return shouldShowDivisionForDisplayMode( coordinate, mLeftFrameDivisions );
1913  return shouldShowDivisionForDisplayMode( coordinate, mRightFrameDivisions );
1915  return shouldShowDivisionForDisplayMode( coordinate, mTopFrameDivisions );
1917  return shouldShowDivisionForDisplayMode( coordinate, mBottomFrameDivisions );
1918  }
1919  return false; // no warnings
1920 }
1921 
1922 bool QgsLayoutItemMapGrid::shouldShowDivisionForDisplayMode( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::DisplayMode mode ) const
1923 {
1924  return mode == QgsLayoutItemMapGrid::ShowAll
1927 }
1928 
1929 bool sortByDistance( QPair<qreal, QgsLayoutItemMapGrid::BorderSide> a, QPair<qreal, QgsLayoutItemMapGrid::BorderSide> b )
1930 {
1931  return a.first < b.first;
1932 }
1933 
1934 QgsLayoutItemMapGrid::BorderSide QgsLayoutItemMapGrid::borderForLineCoord( QPointF p, const AnnotationCoordinate coordinateType ) const
1935 {
1936  if ( !mMap )
1937  {
1939  }
1940 
1941  double tolerance = std::max( mMap->frameEnabled() ? mMap->pen().widthF() : 0.0, 1.0 );
1942 
1943  //check for corner coordinates
1944  if ( ( p.y() <= tolerance && p.x() <= tolerance ) // top left
1945  || ( p.y() <= tolerance && p.x() >= ( mMap->rect().width() - tolerance ) ) //top right
1946  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() <= tolerance ) //bottom left
1947  || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() >= ( mMap->rect().width() - tolerance ) ) //bottom right
1948  )
1949  {
1950  //coordinate is in corner - fall back to preferred side for coordinate type
1951  if ( coordinateType == QgsLayoutItemMapGrid::Latitude )
1952  {
1953  if ( p.x() <= tolerance )
1954  {
1956  }
1957  else
1958  {
1960  }
1961  }
1962  else
1963  {
1964  if ( p.y() <= tolerance )
1965  {
1967  }
1968  else
1969  {
1971  }
1972  }
1973  }
1974 
1975  //otherwise, guess side based on closest map side to point
1976  QList< QPair<qreal, QgsLayoutItemMapGrid::BorderSide > > distanceToSide;
1977  distanceToSide << qMakePair( p.x(), QgsLayoutItemMapGrid::Left );
1978  distanceToSide << qMakePair( mMap->rect().width() - p.x(), QgsLayoutItemMapGrid::Right );
1979  distanceToSide << qMakePair( p.y(), QgsLayoutItemMapGrid::Top );
1980  distanceToSide << qMakePair( mMap->rect().height() - p.y(), QgsLayoutItemMapGrid::Bottom );
1981 
1982  std::sort( distanceToSide.begin(), distanceToSide.end(), sortByDistance );
1983  return distanceToSide.at( 0 ).second;
1984 }
1985 
1987 {
1988  mGridLineSymbol.reset( symbol );
1989 }
1990 
1992 {
1993  return mGridLineSymbol.get();
1994 }
1995 
1997 {
1998  return mGridLineSymbol.get();
1999 }
2000 
2002 {
2003  mGridMarkerSymbol.reset( symbol );
2004 }
2005 
2007 {
2008  return mGridMarkerSymbol.get();
2009 }
2010 
2012 {
2013  return mGridMarkerSymbol.get();
2014 }
2015 
2017 {
2018  switch ( border )
2019  {
2021  mLeftGridAnnotationDisplay = display;
2022  break;
2024  mRightGridAnnotationDisplay = display;
2025  break;
2027  mTopGridAnnotationDisplay = display;
2028  break;
2030  mBottomGridAnnotationDisplay = display;
2031  break;
2032  }
2033 
2034  if ( mMap )
2035  {
2037  mMap->update();
2038  }
2039 }
2040 
2042 {
2043  switch ( border )
2044  {
2046  return mLeftGridAnnotationDisplay;
2048  return mRightGridAnnotationDisplay;
2050  return mTopGridAnnotationDisplay;
2052  return mBottomGridAnnotationDisplay;
2053  }
2054  return mBottomGridAnnotationDisplay; // no warnings
2055 }
2056 
2058 {
2059  double top = 0.0;
2060  double right = 0.0;
2061  double bottom = 0.0;
2062  double left = 0.0;
2063  calculateMaxExtension( top, right, bottom, left );
2064  return std::max( std::max( std::max( top, right ), bottom ), left );
2065 }
2066 
2067 void QgsLayoutItemMapGrid::calculateMaxExtension( double &top, double &right, double &bottom, double &left ) const
2068 {
2069  top = 0.0;
2070  right = 0.0;
2071  bottom = 0.0;
2072  left = 0.0;
2073 
2074  if ( !mMap || !mEnabled )
2075  {
2076  return;
2077  }
2078 
2079  //setup render context
2081  QgsExpressionContext expressionContext = createExpressionContext();
2082  context.setExpressionContext( expressionContext );
2083 
2084  GridExtension extension;
2085 
2086  //collect grid lines
2087  QList< QPair< double, QLineF > > verticalLines;
2088  QList< QPair< double, QLineF > > horizontalLines;
2089  if ( mGridUnit == MapUnit && mCRS.isValid() && mCRS != mMap->crs() )
2090  {
2091  drawGridCrsTransform( context, 0, horizontalLines, verticalLines, true );
2092  }
2093  else
2094  {
2095  drawGridNoTransform( context, 0, horizontalLines, verticalLines, true );
2096  }
2097 
2098  if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
2099  {
2100  drawGridFrame( nullptr, horizontalLines, verticalLines, &extension );
2101  }
2102 
2103  if ( mShowGridAnnotation )
2104  {
2105  drawCoordinateAnnotations( nullptr, horizontalLines, verticalLines, context.expressionContext(), &extension );
2106  }
2107 
2108  top = extension.top;
2109  right = extension.right;
2110  bottom = extension.bottom;
2111  left = extension.left;
2112 }
2113 
2115 {
2116  if ( unit == mGridUnit )
2117  {
2118  return;
2119  }
2120  mGridUnit = unit;
2121  mTransformDirty = true;
2122 }
2123 
2124 void QgsLayoutItemMapGrid::setIntervalX( const double interval )
2125 {
2126  if ( qgsDoubleNear( interval, mGridIntervalX ) )
2127  {
2128  return;
2129  }
2130  mGridIntervalX = interval;
2131  mTransformDirty = true;
2132 }
2133 
2134 void QgsLayoutItemMapGrid::setIntervalY( const double interval )
2135 {
2136  if ( qgsDoubleNear( interval, mGridIntervalY ) )
2137  {
2138  return;
2139  }
2140  mGridIntervalY = interval;
2141  mTransformDirty = true;
2142 }
2143 
2144 void QgsLayoutItemMapGrid::setOffsetX( const double offset )
2145 {
2146  if ( qgsDoubleNear( offset, mGridOffsetX ) )
2147  {
2148  return;
2149  }
2150  mGridOffsetX = offset;
2151  mTransformDirty = true;
2152 }
2153 
2154 void QgsLayoutItemMapGrid::setOffsetY( const double offset )
2155 {
2156  if ( qgsDoubleNear( offset, mGridOffsetY ) )
2157  {
2158  return;
2159  }
2160  mGridOffsetY = offset;
2161  mTransformDirty = true;
2162 }
2163 
2165 {
2166  if ( style == mGridStyle )
2167  {
2168  return;
2169  }
2170  mGridStyle = style;
2171  mTransformDirty = true;
2172 }
2173 
2175 {
2176  switch ( border )
2177  {
2179  mLeftGridAnnotationDirection = direction;
2180  break;
2182  mRightGridAnnotationDirection = direction;
2183  break;
2185  mTopGridAnnotationDirection = direction;
2186  break;
2188  mBottomGridAnnotationDirection = direction;
2189  break;
2190  }
2191 
2192  if ( mMap )
2193  {
2195  mMap->update();
2196  }
2197 }
2198 
2199 void QgsLayoutItemMapGrid::setFrameSideFlags( FrameSideFlags flags )
2200 {
2201  mGridFrameSides = flags;
2202 }
2203 
2205 {
2206  if ( on )
2207  mGridFrameSides |= flag;
2208  else
2209  mGridFrameSides &= ~flag;
2210 }
2211 
2212 QgsLayoutItemMapGrid::FrameSideFlags QgsLayoutItemMapGrid::frameSideFlags() const
2213 {
2214  return mGridFrameSides;
2215 }
2216 
2218 {
2220  context.appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
2221  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), 0, true ) );
2222  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), "x", true ) );
2223  context.setHighlightedVariables( QStringList() << QStringLiteral( "grid_number" ) << QStringLiteral( "grid_axis" ) );
2224  return context;
2225 }
2226 
2228 {
2229  return mGridFrameSides.testFlag( flag );
2230 }
2231 
2233 {
2234  mLeftGridAnnotationDirection = direction;
2235  mRightGridAnnotationDirection = direction;
2236  mTopGridAnnotationDirection = direction;
2237  mBottomGridAnnotationDirection = direction;
2238 }
2239 
2241 {
2242  switch ( border )
2243  {
2245  mLeftGridAnnotationPosition = position;
2246  break;
2248  mRightGridAnnotationPosition = position;
2249  break;
2251  mTopGridAnnotationPosition = position;
2252  break;
2254  mBottomGridAnnotationPosition = position;
2255  break;
2256  }
2257 
2258  if ( mMap )
2259  {
2261  mMap->update();
2262  }
2263 }
2264 
2266 {
2267  switch ( border )
2268  {
2270  return mLeftGridAnnotationPosition;
2272  return mRightGridAnnotationPosition;
2274  return mTopGridAnnotationPosition;
2276  return mBottomGridAnnotationPosition;
2277  }
2278  return mLeftGridAnnotationPosition; // no warnings
2279 }
2280 
2282 {
2283  if ( !mMap )
2284  {
2285  return mLeftGridAnnotationDirection;
2286  }
2287 
2288  switch ( border )
2289  {
2291  return mLeftGridAnnotationDirection;
2293  return mRightGridAnnotationDirection;
2295  return mTopGridAnnotationDirection;
2297  return mBottomGridAnnotationDirection;
2298  }
2299  return mLeftGridAnnotationDirection; // no warnings
2300 }
2301 
2303 {
2304  switch ( border )
2305  {
2307  mLeftFrameDivisions = divisions;
2308  break;
2310  mRightFrameDivisions = divisions;
2311  break;
2313  mTopFrameDivisions = divisions;
2314  break;
2316  mBottomFrameDivisions = divisions;
2317  break;
2318  }
2319 
2320  if ( mMap )
2321  {
2322  mMap->update();
2323  }
2324 }
2325 
2327 {
2328  switch ( border )
2329  {
2331  return mLeftFrameDivisions;
2333  return mRightFrameDivisions;
2335  return mTopFrameDivisions;
2337  return mBottomFrameDivisions;
2338  }
2339  return mLeftFrameDivisions; // no warnings
2340 }
2341 
2342 int QgsLayoutItemMapGrid::crsGridParams( QgsRectangle &crsRect, QgsCoordinateTransform &inverseTransform ) const
2343 {
2344  if ( !mMap )
2345  {
2346  return 1;
2347  }
2348 
2349  try
2350  {
2351  QgsCoordinateTransform tr( mMap->crs(), mCRS, mLayout->project() );
2352  QPolygonF mapPolygon = mMap->transformedMapPolygon();
2353  QRectF mbr = mapPolygon.boundingRect();
2354  QgsRectangle mapBoundingRect( mbr.left(), mbr.bottom(), mbr.right(), mbr.top() );
2355 
2356 
2357  if ( mCRS.isGeographic() )
2358  {
2359  //handle crossing the 180 degree longitude line
2360  QgsPointXY lowerLeft( mapBoundingRect.xMinimum(), mapBoundingRect.yMinimum() );
2361  QgsPointXY upperRight( mapBoundingRect.xMaximum(), mapBoundingRect.yMaximum() );
2362 
2363  lowerLeft = tr.transform( lowerLeft.x(), lowerLeft.y() );
2364  upperRight = tr.transform( upperRight.x(), upperRight.y() );
2365 
2366  if ( lowerLeft.x() > upperRight.x() )
2367  {
2368  //we've crossed the line
2369  crsRect = tr.transformBoundingBox( mapBoundingRect, QgsCoordinateTransform::ForwardTransform, true );
2370  }
2371  else
2372  {
2373  //didn't cross the line
2374  crsRect = tr.transformBoundingBox( mapBoundingRect );
2375  }
2376  }
2377  else
2378  {
2379  crsRect = tr.transformBoundingBox( mapBoundingRect );
2380  }
2381 
2382  inverseTransform = QgsCoordinateTransform( mCRS, mMap->crs(), mLayout->project() );
2383  }
2384  catch ( QgsCsException &cse )
2385  {
2386  Q_UNUSED( cse );
2387  QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
2388  return 1;
2389  }
2390  return 0;
2391 }
2392 
2393 QList<QPolygonF> QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, const QgsRectangle &rect )
2394 {
2395  QgsGeometry lineGeom = QgsGeometry::fromQPolygonF( line );
2396  QgsGeometry rectGeom = QgsGeometry::fromRect( rect );
2397 
2398  QgsGeometry intersected = lineGeom.intersection( rectGeom );
2399  QVector<QgsGeometry> intersectedParts = intersected.asGeometryCollection();
2400 
2401  QList<QPolygonF> trimmedLines;
2402  QVector<QgsGeometry>::const_iterator geomIt = intersectedParts.constBegin();
2403  for ( ; geomIt != intersectedParts.constEnd(); ++geomIt )
2404  {
2405  trimmedLines << ( *geomIt ).asQPolygonF();
2406  }
2407  return trimmedLines;
2408 }
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)
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. ...
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
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the crs for the grid.
QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the objects&#39; current state.
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.
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:1137
#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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
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.
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:106
bool usesAdvancedEffects() const override
Returns true if the item is drawn using advanced effects, such as blend modes.
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.
QgsLayoutRenderContext & renderContext()
Returns a reference to the layout&#39;s render context, which stores information relating to the current ...
Definition: qgslayout.cpp:357
QPolygonF transformedMapPolygon() const
Returns extent that considers rotation and shift with mOffsetX / mOffsetY.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:587
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:732
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgssymbol.h:920
const QgsLineSymbol * lineSymbol() const
Returns the line symbol used for drawing grid lines.
static QString formatX(double x, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats an x coordinate value according to the specified parameters.
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...
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.
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.
bool mEnabled
True if item is to be displayed on map.
Layout graphical items for displaying a map.
void setFrameSideFlags(QgsLayoutItemMapGrid::FrameSideFlags flags)
Sets flags for grid frame sides.
Draw annotations horizontally.
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.
Grid units in centimeters.
QgsLayoutRenderContext::Flags flags() const
Returns the current combination of flags used for rendering the layout.
Decimal degrees, use - for S/W coordinates.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:225
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
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.
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.
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.
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.
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...
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.
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:44
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.
QgsLayoutItemMapItem * item(const QString &itemId) const
Returns a reference to an item which matching itemId within the stack.
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;.
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:304
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.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
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.
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 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:1126
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
Degrees and decimal minutes, eg 30degrees 45.55&#39;.
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.
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.
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.