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