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