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