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