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