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