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