QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
qgslayoutitemmapgrid.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgslayoutitemmapgrid.cpp
3 ----------------------
4 begin : October 2017
5 copyright : (C) 2017 by Marco Hugentobler, Nyall Dawson
6 email : marco dot hugentobler at sourcepole dot ch
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsmessagelog.h"
20#include "qgslayoututils.h"
21#include "qgsgeometry.h"
22#include "qgslayoutitemmap.h"
23#include "qgsreadwritecontext.h"
24#include "qgsrendercontext.h"
25#include "qgssymbollayerutils.h"
26#include "qgssymbol.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
52{
53
54}
55
57{
59}
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
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
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 font.setFamily( 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, [ = ]
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.reset( 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.reset( 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" ), QgsSymbolLayerUtils::encodeColor( mGridFramePenColor ) );
281 mapGridElem.setAttribute( QStringLiteral( "frameFillColor1" ), QgsSymbolLayerUtils::encodeColor( mGridFrameFillColor1 ) );
282 mapGridElem.setAttribute( QStringLiteral( "frameFillColor2" ), QgsSymbolLayerUtils::encodeColor( 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 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "gridFramePenColor" ), QStringLiteral( "0,0,0" ) ) );
351 mGridFrameFillColor1 = QgsSymbolLayerUtils::decodeColor( itemElem.attribute( QStringLiteral( "frameFillColor1" ), QStringLiteral( "255,255,255,255" ) ) );
352 mGridFrameFillColor2 = QgsSymbolLayerUtils::decodeColor( 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.reset( QgsSymbolLayerUtils::loadSymbol<QgsLineSymbol>( symbolElem, context ) );
373 }
374 }
375 else
376 {
377 //old project file, read penWidth /penColorRed, penColorGreen, penColorBlue
378 mGridLineSymbol.reset( 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.reset( 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( QgsSymbolLayerUtils::decodeColor( 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 drawGridLine( scalePolygon( gridIt->line, dotsPerMM ), context );
497 }
498 }
499 else if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
500 {
501 const double maxX = mMap->rect().width();
502 const double maxY = mMap->rect().height();
503
504 QList< QgsPointXY >::const_iterator intersectionIt = mTransformedIntersections.constBegin();
505 for ( ; intersectionIt != mTransformedIntersections.constEnd(); ++intersectionIt )
506 {
507 const double x = intersectionIt->x();
508 const double y = intersectionIt->y();
509 if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
510 {
511 //ensure that crosses don't overshoot the map item bounds
512 const QLineF line1 = QLineF( x - mEvaluatedCrossLength, y, x + mEvaluatedCrossLength, y );
513 line1.p1().rx() = line1.p1().x() < 0 ? 0 : line1.p1().x();
514 line1.p2().rx() = line1.p2().x() > maxX ? maxX : line1.p2().x();
515 const QLineF line2 = QLineF( x, y - mEvaluatedCrossLength, x, y + mEvaluatedCrossLength );
516 line2.p1().ry() = line2.p1().y() < 0 ? 0 : line2.p1().y();
517 line2.p2().ry() = line2.p2().y() > maxY ? maxY : line2.p2().y();
518
519 //draw line using coordinates scaled to dots
520 drawGridLine( QLineF( line1.p1() * dotsPerMM, line1.p2() * dotsPerMM ), context );
521 drawGridLine( QLineF( line2.p1() * dotsPerMM, line2.p2() * dotsPerMM ), context );
522 }
523 else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
524 {
525 drawGridMarker( QPointF( x, y ) * dotsPerMM, context );
526 }
527 }
528 }
529 }
530}
531
532void QgsLayoutItemMapGrid::calculateCrsTransformLines() const
533{
534 QgsRectangle crsBoundingRect;
535 QgsCoordinateTransform inverseTr;
536 if ( crsGridParams( crsBoundingRect, inverseTr ) != 0 )
537 {
538 return;
539 }
540
541 // calculate grid lines
542 mGridLines.clear();
543 xGridLinesCrsTransform( crsBoundingRect, inverseTr );
544 yGridLinesCrsTransform( crsBoundingRect, inverseTr );
545
546 if ( mGridStyle == QgsLayoutItemMapGrid::Cross || mGridStyle == QgsLayoutItemMapGrid::Markers )
547 {
548 //cross or markers style - we also need to calculate intersections of lines
549
550 //first convert lines to QgsGeometry
551 QList< QgsGeometry > xLines;
552 QList< QgsGeometry > yLines;
553 QList< GridLine >::const_iterator gridIt = mGridLines.constBegin();
554 for ( ; gridIt != mGridLines.constEnd(); ++gridIt )
555 {
556
557 QgsPolylineXY line;
558 for ( int i = 0; i < gridIt->line.size(); ++i )
559 {
560 line.append( QgsPointXY( gridIt->line.at( i ).x(), gridIt->line.at( i ).y() ) );
561 }
562 if ( gridIt->coordinateType == AnnotationCoordinate::Longitude )
563 yLines << QgsGeometry::fromPolylineXY( line );
564 else if ( gridIt->coordinateType == AnnotationCoordinate::Latitude )
565 xLines << QgsGeometry::fromPolylineXY( line );
566 }
567
568 //now, loop through geometries and calculate intersection points
569 mTransformedIntersections.clear();
570 QList< QgsGeometry >::const_iterator yLineIt = yLines.constBegin();
571 for ( ; yLineIt != yLines.constEnd(); ++yLineIt )
572 {
573 QList< QgsGeometry >::const_iterator xLineIt = xLines.constBegin();
574 for ( ; xLineIt != xLines.constEnd(); ++xLineIt )
575 {
576 //look for intersections between lines
577 const QgsGeometry intersects = ( *yLineIt ).intersection( ( *xLineIt ) );
578 if ( intersects.isNull() )
579 continue;
580
581 //go through all intersections and draw grid markers/crosses
582 int i = 0;
583 QgsPointXY vertex = intersects.vertexAt( i );
584 while ( !vertex.isEmpty() )
585 {
586 mTransformedIntersections << vertex;
587 i = i + 1;
588 vertex = intersects.vertexAt( i );
589 }
590 }
591 }
592 }
593
594 mTransformDirty = false;
595}
596
597void QgsLayoutItemMapGrid::draw( QPainter *p )
598{
599 if ( !mMap || !mEvaluatedEnabled )
600 {
601 return;
602 }
603 QPaintDevice *paintDevice = p->device();
604 if ( !paintDevice )
605 {
606 return;
607 }
608
609 p->save();
610 p->setCompositionMode( mBlendMode );
611 p->setRenderHint( QPainter::Antialiasing, mMap->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
612
613 const QRectF thisPaintRect = QRectF( 0, 0, mMap->rect().width(), mMap->rect().height() );
614 p->setClipRect( thisPaintRect );
615 if ( thisPaintRect != mPrevPaintRect )
616 {
617 //rect has changed, so need to recalculate transform
618 mTransformDirty = true;
619 mPrevPaintRect = thisPaintRect;
620 }
621
622 //setup painter scaling to dots so that raster symbology is drawn to scale
623 const double dotsPerMM = paintDevice->logicalDpiX() / 25.4;
624 p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); //scale painter from mm to dots
625
626 //setup render context
628 context.setForceVectorOutput( true );
630 const QgsExpressionContext expressionContext = createExpressionContext();
631 context.setExpressionContext( expressionContext );
632
633 //is grid in a different crs than map?
634 switch ( mGridUnit )
635 {
636 case MapUnit:
638 if ( mCRS.isValid() && mCRS != mMap->crs() )
639 {
640 drawGridCrsTransform( context, dotsPerMM );
641 break;
642 }
643
645 case CM:
646 case MM:
647 drawGridNoTransform( context, dotsPerMM );
648 break;
649 }
650 p->restore();
651
652 p->setClipping( false );
653#ifdef Q_OS_MAC
654 //QPainter::setClipping(false) seems to be broken on OSX (#12747). So we hack around it by
655 //setting a larger clip rect
656 p->setClipRect( mMap->mapRectFromScene( mMap->sceneBoundingRect() ).adjusted( -10, -10, 10, 10 ) );
657#endif
658
659
660 if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame || mShowGridAnnotation )
661 updateGridLinesAnnotationsPositions();
662
663 if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
664 {
665 drawGridFrame( p );
666 }
667
668 if ( mShowGridAnnotation )
669 {
670 drawCoordinateAnnotations( context, context.expressionContext() );
671 }
672}
673
674void QgsLayoutItemMapGrid::updateGridLinesAnnotationsPositions() const
675{
676 QList< GridLine >::iterator it = mGridLines.begin();
677 for ( ; it != mGridLines.end(); ++it )
678 {
679 it->startAnnotation.border = borderForLineCoord( it->line.first(), it->coordinateType );
680 it->endAnnotation.border = borderForLineCoord( it->line.last(), it->coordinateType );
681 it->startAnnotation.position = QVector2D( it->line.first() );
682 it->endAnnotation.position = QVector2D( it->line.last() );
683 it->startAnnotation.vector = QVector2D( it->line.at( 1 ) - it->line.first() ).normalized();
684 it->endAnnotation.vector = QVector2D( it->line.at( it->line.count() - 2 ) - it->line.last() ).normalized();
685 const QVector2D normS = borderToNormal2D( it->startAnnotation.border );
686 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() );
687 const QVector2D normE = borderToNormal2D( it->endAnnotation.border );
688 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() );
689 }
690}
691
692void QgsLayoutItemMapGrid::drawGridNoTransform( QgsRenderContext &context, double dotsPerMM, bool calculateLinesOnly ) const
693{
694 //get line positions
695 mGridLines.clear();
696 yGridLines();
697 xGridLines();
698
699 if ( calculateLinesOnly )
700 return;
701
702 QList< GridLine >::const_iterator vIt = mGridLines.constBegin();
703 QList< GridLine >::const_iterator hIt = mGridLines.constBegin();
704
705 //simple approach: draw vertical lines first, then horizontal ones
706 if ( mGridStyle == QgsLayoutItemMapGrid::Solid )
707 {
708 //we need to scale line coordinates to dots, rather than mm, since the painter has already been scaled to dots
709 //this is done by multiplying each line coordinate by dotsPerMM
710 QLineF line;
711 for ( ; vIt != mGridLines.constEnd(); ++vIt )
712 {
713 if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
714 continue;
715 line = QLineF( vIt->line.first() * dotsPerMM, vIt->line.last() * dotsPerMM );
716 drawGridLine( line, context );
717 }
718
719 for ( ; hIt != mGridLines.constEnd(); ++hIt )
720 {
721 if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
722 continue;
723 line = QLineF( hIt->line.first() * dotsPerMM, hIt->line.last() * dotsPerMM );
724 drawGridLine( line, context );
725 }
726 }
727 else if ( mGridStyle != QgsLayoutItemMapGrid::FrameAnnotationsOnly ) //cross or markers
728 {
729 QLineF l1, l2;
730 QPointF intersectionPoint, crossEnd1, crossEnd2;
731 for ( ; vIt != mGridLines.constEnd(); ++vIt )
732 {
733 if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
734 continue;
735
736 l1 = QLineF( vIt->line.first(), vIt->line.last() );
737
738 //test for intersection with every horizontal line
739 hIt = mGridLines.constBegin();
740 for ( ; hIt != mGridLines.constEnd(); ++hIt )
741 {
742 if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
743 continue;
744
745 l2 = QLineF( hIt->line.first(), hIt->line.last() );
746
747 if ( l2.intersects( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
748 {
749 if ( mGridStyle == QgsLayoutItemMapGrid::Cross )
750 {
751 //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
752 crossEnd1 = ( ( intersectionPoint - l1.p1() ).manhattanLength() > 0.01 ) ?
753 QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p1(), mEvaluatedCrossLength ) : intersectionPoint;
754 crossEnd2 = ( ( intersectionPoint - l1.p2() ).manhattanLength() > 0.01 ) ?
755 QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p2(), mEvaluatedCrossLength ) : intersectionPoint;
756 //draw line using coordinates scaled to dots
757 drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
758 }
759 else if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
760 {
761 drawGridMarker( intersectionPoint * dotsPerMM, context );
762 }
763 }
764 }
765 }
766 if ( mGridStyle == QgsLayoutItemMapGrid::Markers )
767 {
768 //markers mode, so we have no need to process horizontal lines (we've already
769 //drawn markers on the intersections between horizontal and vertical lines)
770 return;
771 }
772
773 hIt = mGridLines.constBegin();
774 for ( ; hIt != mGridLines.constEnd(); ++hIt )
775 {
776 if ( hIt->coordinateType != AnnotationCoordinate::Latitude )
777 continue;
778
779 l1 = QLineF( hIt->line.first(), hIt->line.last() );
780
781 vIt = mGridLines.constBegin();
782 for ( ; vIt != mGridLines.constEnd(); ++vIt )
783 {
784 if ( vIt->coordinateType != AnnotationCoordinate::Longitude )
785 continue;
786
787 l2 = QLineF( vIt->line.first(), vIt->line.last() );
788
789 if ( l2.intersects( l1, &intersectionPoint ) == QLineF::BoundedIntersection )
790 {
791 //apply a threshold to avoid calculate point if the two points are very close together (can lead to artifacts)
792 crossEnd1 = ( ( intersectionPoint - l1.p1() ).manhattanLength() > 0.01 ) ?
793 QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p1(), mEvaluatedCrossLength ) : intersectionPoint;
794 crossEnd2 = ( ( intersectionPoint - l1.p2() ).manhattanLength() > 0.01 ) ?
795 QgsSymbolLayerUtils::pointOnLineWithDistance( intersectionPoint, l1.p2(), mEvaluatedCrossLength ) : intersectionPoint;
796 //draw line using coordinates scaled to dots
797 drawGridLine( QLineF( crossEnd1 * dotsPerMM, crossEnd2 * dotsPerMM ), context );
798 }
799 }
800 }
801 }
802}
803
804void QgsLayoutItemMapGrid::drawGridFrame( QPainter *p, GridExtension *extension ) const
805{
806 if ( p )
807 {
808 p->save();
809 p->setRenderHint( QPainter::Antialiasing, mMap->layout()->renderContext().flags() & QgsLayoutRenderContext::FlagAntialiasing );
810 }
811
812
813 switch ( mGridFrameStyle )
814 {
817 drawGridFrameZebra( p, extension );
818 break;
822 drawGridFrameTicks( p, extension );
823 break;
824
827 drawGridFrameLine( p, extension );
828 break;
829
831 break;
832 }
833
834 if ( p )
835 p->restore();
836}
837
838void QgsLayoutItemMapGrid::drawGridLine( const QLineF &line, QgsRenderContext &context ) const
839{
840 QPolygonF poly;
841 poly << line.p1() << line.p2();
842 drawGridLine( poly, context );
843}
844
845void QgsLayoutItemMapGrid::drawGridLine( const QPolygonF &line, QgsRenderContext &context ) const
846{
847 if ( !mMap || !mMap->layout() || !mGridLineSymbol )
848 {
849 return;
850 }
851
852 mGridLineSymbol->startRender( context );
853 mGridLineSymbol->renderPolyline( line, nullptr, context );
854 mGridLineSymbol->stopRender( context );
855}
856
857void QgsLayoutItemMapGrid::drawGridMarker( QPointF point, QgsRenderContext &context ) const
858{
859 if ( !mMap || !mMap->layout() || !mGridMarkerSymbol )
860 {
861 return;
862 }
863
864 mGridMarkerSymbol->startRender( context );
865 mGridMarkerSymbol->renderPoint( point, nullptr, context );
866 mGridMarkerSymbol->stopRender( context );
867}
868
869void QgsLayoutItemMapGrid::drawGridFrameZebra( QPainter *p, GridExtension *extension ) const
870{
872 {
873 drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Left, extension ? &extension->left : nullptr );
874 }
876 {
877 drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Right, extension ? &extension->right : nullptr );
878 }
880 {
881 drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Top, extension ? &extension->top : nullptr );
882 }
884 {
885 drawGridFrameZebraBorder( p, QgsLayoutItemMapGrid::Bottom, extension ? &extension->bottom : nullptr );
886 }
887}
888
889void QgsLayoutItemMapGrid::drawGridFrameZebraBorder( QPainter *p, BorderSide border, double *extension ) const
890{
891 if ( !mMap )
892 {
893 return;
894 }
895
896 if ( extension )
897 {
898 *extension = mEvaluatedGridFrameMargin + mEvaluatedGridFrameWidth + mEvaluatedGridFrameLineThickness / 2.0;
899 return;
900 }
901
902 double currentCoord = 0.0;
903 bool color1 = false;
904 double x = 0;
905 double y = 0;
906 double width = 0;
907 double height = 0;
908
909 bool drawTLBox = false;
910 bool drawTRBox = false;
911 bool drawBLBox = false;
912 bool drawBRBox = false;
913
914 QMap< double, double > pos = QMap< double, double >();
915 QList< GridLine >::const_iterator it = mGridLines.constBegin();
916 for ( ; it != mGridLines.constEnd(); ++it )
917 {
918 // for first and last point of the line
919 for ( int i = 0 ; i < 2 ; ++i )
920 {
921 const GridLineAnnotation annot = ( i == 0 ) ? it->startAnnotation : it->endAnnotation;
922
923 // we skip if the point is on another border
924 if ( annot.border != border )
925 continue;
926
927 if ( ! shouldShowDivisionForSide( it->coordinateType, annot.border ) )
928 continue;
929
930 if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
931 pos.insert( annot.position.y(), it->coordinate );
932 else
933 pos.insert( annot.position.x(), it->coordinate );
934 }
935 }
936
937
938 if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
939 {
940 pos.insert( mMap->rect().height(), mMap->rect().height() );
942 {
943 drawBLBox = border == QgsLayoutItemMapGrid::Left;
944 drawBRBox = border == QgsLayoutItemMapGrid::Right;
945 }
947 {
948 drawTLBox = border == QgsLayoutItemMapGrid::Left;
949 drawTRBox = border == QgsLayoutItemMapGrid::Right;
950 }
951 if ( !drawTLBox && border == QgsLayoutItemMapGrid::Left )
952 color1 = true;
953 }
954 else if ( border == QgsLayoutItemMapGrid::Top || border == QgsLayoutItemMapGrid::Bottom )
955 {
956 pos.insert( mMap->rect().width(), mMap->rect().width() );
957 }
958
959 //set pen to current frame pen
960 QPen framePen = QPen( mGridFramePenColor );
961 framePen.setWidthF( mEvaluatedGridFrameLineThickness );
962 framePen.setJoinStyle( Qt::MiterJoin );
963 p->setPen( framePen );
964
965 QMap< double, double >::const_iterator posIt = pos.constBegin();
966 for ( ; posIt != pos.constEnd(); ++posIt )
967 {
968 p->setBrush( QBrush( color1 ? mGridFrameFillColor1 : mGridFrameFillColor2 ) );
969 if ( border == QgsLayoutItemMapGrid::Left || border == QgsLayoutItemMapGrid::Right )
970 {
971 height = posIt.key() - currentCoord;
972 width = mEvaluatedGridFrameWidth;
973 x = ( border == QgsLayoutItemMapGrid::Left ) ? -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) : mMap->rect().width() + mEvaluatedGridFrameMargin;
974 y = currentCoord;
975 }
976 else //top or bottom
977 {
978 height = mEvaluatedGridFrameWidth;
979 width = posIt.key() - currentCoord;
980 x = currentCoord;
981 y = ( border == QgsLayoutItemMapGrid::Top ) ? -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) : mMap->rect().height() + mEvaluatedGridFrameMargin;
982 }
983 p->drawRect( QRectF( x, y, width, height ) );
984 currentCoord = posIt.key();
985 color1 = !color1;
986 }
987
988 if ( mGridFrameStyle == ZebraNautical || qgsDoubleNear( mEvaluatedGridFrameMargin, 0.0 ) )
989 {
990 //draw corners
991 width = height = ( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ) ;
992 p->setBrush( QBrush( mGridFrameFillColor1 ) );
993 if ( drawTLBox )
994 p->drawRect( QRectF( -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), width, height ) );
995 if ( drawTRBox )
996 p->drawRect( QRectF( mMap->rect().width(), -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), width, height ) );
997 if ( drawBLBox )
998 p->drawRect( QRectF( -( mEvaluatedGridFrameWidth + mEvaluatedGridFrameMargin ), mMap->rect().height(), width, height ) );
999 if ( drawBRBox )
1000 p->drawRect( QRectF( mMap->rect().width(), mMap->rect().height(), width, height ) );
1001 }
1002}
1003
1004void QgsLayoutItemMapGrid::drawGridFrameTicks( QPainter *p, GridExtension *extension ) const
1005{
1006 if ( !mMap )
1007 {
1008 return;
1009 }
1010
1011 //set pen to current frame pen
1012 if ( p )
1013 {
1014 QPen framePen = QPen( mGridFramePenColor );
1015 framePen.setWidthF( mEvaluatedGridFrameLineThickness );
1016 framePen.setCapStyle( Qt::FlatCap );
1017 p->setBrush( Qt::NoBrush );
1018 p->setPen( framePen );
1019 }
1020
1021 QList< GridLine >::iterator it = mGridLines.begin();
1022 for ( ; it != mGridLines.end(); ++it )
1023 {
1024 // for first and last point of the line
1025 for ( int i = 0 ; i < 2 ; ++i )
1026 {
1027 const GridLineAnnotation annot = ( i == 0 ) ? it->startAnnotation : it->endAnnotation;
1028
1029 if ( ! shouldShowDivisionForSide( it->coordinateType, annot.border ) )
1030 continue;
1031
1032 // If the angle is below the threshold, we don't draw the annotation
1033 if ( abs( annot.angle ) / M_PI * 180.0 > 90.0 - mRotatedTicksMinimumAngle + 0.0001 )
1034 continue;
1035
1036 // Skip outwards facing annotations that are below mRotatedTicksMarginToCorner
1037 bool facingLeft;
1038 bool facingRight;
1039 if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks )
1040 {
1041 facingLeft = ( annot.angle != 0 );
1042 facingRight = ( annot.angle != 0 );
1043 }
1044 else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1045 {
1046 facingLeft = ( annot.angle > 0 );
1047 facingRight = ( annot.angle < 0 );
1048 }
1049 else
1050 {
1051 facingLeft = ( annot.angle < 0 );
1052 facingRight = ( annot.angle > 0 );
1053 }
1054
1055 if ( annot.border == BorderSide::Top && ( ( facingLeft && annot.position.x() < mRotatedTicksMarginToCorner ) ||
1056 ( facingRight && annot.position.x() > mMap->rect().width() - mRotatedTicksMarginToCorner ) ) )
1057 continue;
1058 if ( annot.border == BorderSide::Bottom && ( ( facingLeft && annot.position.x() > mMap->rect().width() - mRotatedTicksMarginToCorner ) ||
1059 ( facingRight && annot.position.x() < mRotatedTicksMarginToCorner ) ) )
1060 continue;
1061 if ( annot.border == BorderSide::Left && ( ( facingLeft && annot.position.y() > mMap->rect().height() - mRotatedTicksMarginToCorner ) ||
1062 ( facingRight && annot.position.y() < mRotatedTicksMarginToCorner ) ) )
1063 continue;
1064 if ( annot.border == BorderSide::Right && ( ( facingLeft && annot.position.y() < mRotatedTicksMarginToCorner ) ||
1065 ( facingRight && annot.position.y() > mMap->rect().height() - mRotatedTicksMarginToCorner ) ) )
1066 continue;
1067
1068 const QVector2D normalVector = borderToNormal2D( annot.border );
1069 const QVector2D vector = ( mRotatedTicksEnabled ) ? annot.vector : normalVector;
1070
1071 double fA = mEvaluatedGridFrameMargin; // point near to frame
1072 double fB = mEvaluatedGridFrameMargin + mEvaluatedGridFrameWidth; // point far from frame
1073
1074 if ( mRotatedTicksEnabled && mRotatedTicksLengthMode == OrthogonalTicks )
1075 {
1076 fA /= QVector2D::dotProduct( vector, normalVector );
1077 fB /= QVector2D::dotProduct( vector, normalVector );
1078 }
1079
1080 // extents isn't computed accurately
1081 if ( extension )
1082 {
1083 if ( mGridFrameStyle != QgsLayoutItemMapGrid::InteriorTicks )
1084 extension->UpdateBorder( annot.border, fB );
1085 continue;
1086 }
1087
1088 QVector2D pA;
1089 QVector2D pB;
1090 if ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks )
1091 {
1092 pA = annot.position + fA * vector;
1093 pB = annot.position + fB * vector;
1094 }
1095 else if ( mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks )
1096 {
1097 pA = annot.position - fA * vector;
1098 pB = annot.position - fB * vector;
1099 }
1100 else // InteriorExteriorTicks
1101 {
1102 pA = annot.position - fB * vector;
1103 pB = annot.position + ( fB - 2.0 * mEvaluatedGridFrameMargin ) * vector;
1104 }
1105 p->drawLine( QLineF( pA.toPointF(), pB.toPointF() ) );
1106
1107 }
1108 }
1109}
1110
1111void QgsLayoutItemMapGrid::drawGridFrameLine( QPainter *p, GridExtension *extension ) const
1112{
1113 if ( !mMap )
1114 {
1115 return;
1116 }
1117
1118 if ( p )
1119 {
1120 //set pen to current frame pen
1121 QPen framePen = QPen( mGridFramePenColor );
1122 framePen.setWidthF( mEvaluatedGridFrameLineThickness );
1123 framePen.setCapStyle( Qt::SquareCap );
1124 p->setBrush( Qt::NoBrush );
1125 p->setPen( framePen );
1126 }
1127
1128 const bool drawDiagonals = mGridFrameStyle == LineBorderNautical && !qgsDoubleNear( mEvaluatedGridFrameMargin, 0.0 );
1129
1131 {
1132 if ( extension )
1133 extension->UpdateBorder( QgsLayoutItemMapGrid::Left, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1134 else
1135 p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1136 }
1137
1139 {
1140 if ( extension )
1141 extension->UpdateBorder( QgsLayoutItemMapGrid::Right, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1142 else
1143 p->drawLine( QLineF( mMap->rect().width() + mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1144 }
1145
1147 {
1148 if ( extension )
1149 extension->UpdateBorder( QgsLayoutItemMapGrid::Top, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1150 else
1151 p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, 0 - mEvaluatedGridFrameMargin ) );
1152 }
1153
1155 {
1156 if ( extension )
1157 extension->UpdateBorder( QgsLayoutItemMapGrid::Bottom, mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 );
1158 else
1159 p->drawLine( QLineF( 0 - mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin, mMap->rect().width() + mEvaluatedGridFrameMargin, mMap->rect().height() + mEvaluatedGridFrameMargin ) );
1160 }
1161
1162 if ( ! extension && drawDiagonals )
1163 {
1165 {
1166 //corner left-top
1167 const double X1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0;
1168 const double Y1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0;
1169 p->drawLine( QLineF( 0, 0, X1, Y1 ) );
1170 }
1172 {
1173 //corner right-bottom
1174 const double X1 = mMap->rect().width() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1175 const double Y1 = mMap->rect().height() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1176 p->drawLine( QLineF( mMap->rect().width(), mMap->rect().height(), X1, Y1 ) );
1177 }
1179 {
1180 //corner right-top
1181 const double X1 = mMap->rect().width() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1182 const double Y1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 ;
1183 p->drawLine( QLineF( mMap->rect().width(), 0, X1, Y1 ) );
1184 }
1186 {
1187 //corner left-bottom
1188 const double X1 = 0 - mEvaluatedGridFrameMargin + mEvaluatedGridFrameLineThickness / 2.0 ;
1189 const double Y1 = mMap->rect().height() + mEvaluatedGridFrameMargin - mEvaluatedGridFrameLineThickness / 2.0 ;
1190 p->drawLine( QLineF( 0, mMap->rect().height(), X1, Y1 ) );
1191 }
1192 }
1193}
1194
1195void QgsLayoutItemMapGrid::drawCoordinateAnnotations( QgsRenderContext &context, QgsExpressionContext &expressionContext,
1196 GridExtension *extension ) const
1197{
1198 QString currentAnnotationString;
1199 QList< GridLine >::const_iterator it = mGridLines.constBegin();
1200 for ( ; it != mGridLines.constEnd(); ++it )
1201 {
1202 currentAnnotationString = gridAnnotationString( it->coordinate, it->coordinateType, expressionContext );
1203 drawCoordinateAnnotation( context, it->startAnnotation, currentAnnotationString, it->coordinateType, extension );
1204 drawCoordinateAnnotation( context, it->endAnnotation, currentAnnotationString, it->coordinateType, extension );
1205 }
1206}
1207
1208void QgsLayoutItemMapGrid::drawCoordinateAnnotation( QgsRenderContext &context, GridLineAnnotation annot, const QString &annotationString, const AnnotationCoordinate coordinateType, GridExtension *extension ) const
1209{
1210 if ( !mMap )
1211 {
1212 return;
1213 }
1214
1215 if ( ! shouldShowAnnotationForSide( coordinateType, annot.border ) )
1216 return;
1217
1218 const QgsLayoutItemMapGrid::BorderSide frameBorder = annot.border;
1219 double textWidth = QgsTextRenderer::textWidth( context, mAnnotationFormat, QStringList() << annotationString ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1220 if ( extension )
1221 textWidth *= 1.1; // little bit of extra padding when we are calculating the bounding rect, to account for antialiasing
1222
1223 //relevant for annotations is the height of digits
1224 const double textHeight = ( extension ? ( QgsTextRenderer::textHeight( context, mAnnotationFormat, QChar(), true ) )
1225 : ( QgsTextRenderer::textHeight( context, mAnnotationFormat, '0', false ) ) ) / context.convertToPainterUnits( 1, Qgis::RenderUnit::Millimeters );
1226
1227 double xpos = annot.position.x();
1228 double ypos = annot.position.y();
1229 QPointF anchor = QPointF();
1230 int rotation = 0;
1231
1232 const AnnotationPosition anotPos = annotationPosition( frameBorder );
1233 const AnnotationDirection anotDir = annotationDirection( frameBorder );
1234
1235 // If the angle is below the threshold, we don't draw the annotation
1236 if ( abs( annot.angle ) / M_PI * 180.0 > 90.0 - mRotatedAnnotationsMinimumAngle + 0.0001 )
1237 return;
1238
1239 const QVector2D normalVector = borderToNormal2D( annot.border );
1240 const QVector2D vector = ( mRotatedAnnotationsEnabled ) ? annot.vector : normalVector;
1241
1242 // Distance to frame
1243 double f = mEvaluatedAnnotationFrameDistance;
1244
1245 // Adapt distance to frame using the frame width and line thickness into account
1246 const bool isOverTick = ( anotDir == QgsLayoutItemMapGrid::AboveTick || anotDir == QgsLayoutItemMapGrid::OnTick || anotDir == QgsLayoutItemMapGrid::UnderTick );
1247 const bool hasInteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::InteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks );
1248 const bool hasExteriorMargin = ! isOverTick && ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::InteriorExteriorTicks || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical );
1249 const bool hasBorderWidth = ( mGridFrameStyle == QgsLayoutItemMapGrid::Zebra || mGridFrameStyle == QgsLayoutItemMapGrid::ZebraNautical || mGridFrameStyle == QgsLayoutItemMapGrid::LineBorder || mGridFrameStyle == QgsLayoutItemMapGrid::LineBorderNautical );
1250 if ( ( anotPos == QgsLayoutItemMapGrid::InsideMapFrame && hasInteriorMargin ) || ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame && hasExteriorMargin ) )
1251 f += mEvaluatedGridFrameWidth;
1252 if ( hasBorderWidth )
1253 f += mEvaluatedGridFrameLineThickness / 2.0;
1254
1256 f *= -1;
1257
1258 if ( mRotatedAnnotationsEnabled && mRotatedAnnotationsLengthMode == OrthogonalTicks )
1259 {
1260 f /= QVector2D::dotProduct( vector, normalVector );
1261 }
1262
1263 const QVector2D pos = annot.position + f * vector;
1264 xpos = pos.x();
1265 ypos = pos.y();
1266
1267 const bool outside = ( anotPos == QgsLayoutItemMapGrid::OutsideMapFrame );
1268
1269 if (
1271 anotDir == QgsLayoutItemMapGrid::OnTick ||
1273 )
1274 {
1275
1276 rotation = atan2( vector.y(), vector.x() ) / M_PI * 180;
1277
1278 if ( rotation <= -90 || rotation > 90 )
1279 {
1280 rotation += 180;
1281 anchor.setX( outside ? 0 : textWidth ); // left / right
1282 }
1283 else
1284 {
1285 anchor.setX( outside ? textWidth : 0 ); // right / left
1286 }
1287
1288 if ( anotDir == QgsLayoutItemMapGrid::AboveTick )
1289 anchor.setY( 0.5 * textHeight ); // bottom
1290 else if ( anotDir == QgsLayoutItemMapGrid::UnderTick )
1291 anchor.setY( -1.5 * textHeight ); // top
1292 else // OnTick
1293 anchor.setY( -0.5 * textHeight ); // middle
1294
1295 }
1296 else if ( anotDir == QgsLayoutItemMapGrid::Horizontal )
1297 {
1298 rotation = 0;
1299 anchor.setX( 0.5 * textWidth ); // center
1300 anchor.setY( -0.5 * textHeight ); // middle
1301 if ( frameBorder == QgsLayoutItemMapGrid::Top )
1302 anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1303 else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1304 anchor.setX( outside ? 0 : textWidth ); // left / right
1305 else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1306 anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1307 else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1308 anchor.setX( outside ? textWidth : 0 ); // right / left
1309 }
1310 else if ( anotDir == QgsLayoutItemMapGrid::Vertical )
1311 {
1312 rotation = -90;
1313 anchor.setX( 0.5 * textWidth ); // center
1314 anchor.setY( -0.5 * textHeight ); // middle
1315 if ( frameBorder == QgsLayoutItemMapGrid::Top )
1316 anchor.setX( outside ? 0 : textWidth ); // left / right
1317 else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1318 anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1319 else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1320 anchor.setX( outside ? textWidth : 0 ); // right / left
1321 else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1322 anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1323 }
1324 else if ( anotDir == QgsLayoutItemMapGrid::VerticalDescending )
1325 {
1326 rotation = 90;
1327 anchor.setX( 0.5 * textWidth ); // center
1328 anchor.setY( -0.5 * textHeight ); // middle
1329 if ( frameBorder == QgsLayoutItemMapGrid::Top )
1330 anchor.setX( outside ? textWidth : 0 ); // right / left
1331 else if ( frameBorder == QgsLayoutItemMapGrid::Right )
1332 anchor.setY( outside ? 0 : -textHeight ); // bottom / top
1333 else if ( frameBorder == QgsLayoutItemMapGrid::Bottom )
1334 anchor.setX( outside ? 0 : textWidth ); // left / right
1335 else if ( frameBorder == QgsLayoutItemMapGrid::Left )
1336 anchor.setY( outside ? -textHeight : 0 ); // top / bottom
1337 }
1338 else // ( anotDir == QgsLayoutItemMapGrid::BoundaryDirection )
1339 {
1340 const QVector2D borderVector = borderToVector2D( annot.border );
1341 rotation = atan2( borderVector.y(), borderVector.x() ) / M_PI * 180;
1342 anchor.setX( 0.5 * textWidth ); // center
1344 anchor.setY( -textHeight ); // top
1345 else
1346 anchor.setY( 0 ); // bottom
1347 }
1348
1349 // extents isn't computed accurately
1350 if ( extension && anotPos == QgsLayoutItemMapGrid::OutsideMapFrame )
1351 {
1352 extension->UpdateBorder( frameBorder, -f + textWidth );
1353 // We also add a general margin, can be useful for labels near corners
1354 extension->UpdateAll( textWidth / 2.0 );
1355 }
1356
1357 if ( extension || !context.painter() )
1358 return;
1359
1360 // Skip outwards facing annotations that are below mRotatedAnnotationsMarginToCorner
1361 bool facingLeft = ( annot.angle < 0 );
1362 bool facingRight = ( annot.angle > 0 );
1364 {
1365 facingLeft = !facingLeft;
1366 facingRight = !facingRight;
1367 }
1368 if ( annot.border == BorderSide::Top && ( ( facingLeft && annot.position.x() < mRotatedAnnotationsMarginToCorner ) ||
1369 ( facingRight && annot.position.x() > mMap->rect().width() - mRotatedAnnotationsMarginToCorner ) ) )
1370 return;
1371 if ( annot.border == BorderSide::Bottom && ( ( facingLeft && annot.position.x() > mMap->rect().width() - mRotatedAnnotationsMarginToCorner ) ||
1372 ( facingRight && annot.position.x() < mRotatedAnnotationsMarginToCorner ) ) )
1373 return;
1374 if ( annot.border == BorderSide::Left && ( ( facingLeft && annot.position.y() > mMap->rect().height() - mRotatedAnnotationsMarginToCorner ) ||
1375 ( facingRight && annot.position.y() < mRotatedAnnotationsMarginToCorner ) ) )
1376 return;
1377 if ( annot.border == BorderSide::Right && ( ( facingLeft && annot.position.y() < mRotatedAnnotationsMarginToCorner ) ||
1378 ( facingRight && annot.position.y() > mMap->rect().height() - mRotatedAnnotationsMarginToCorner ) ) )
1379 return;
1380
1381 const QgsScopedQPainterState painterState( context.painter() );
1382 context.painter()->translate( QPointF( xpos, ypos ) );
1383 context.painter()->rotate( rotation );
1384 context.painter()->translate( -anchor );
1385 const QgsScopedRenderContextScaleToPixels scale( context );
1386 QgsTextRenderer::drawText( QPointF( 0, 0 ), 0, Qgis::TextHorizontalAlignment::Left, annotationString.split( '\n' ), context, mAnnotationFormat );
1387}
1388
1389QString QgsLayoutItemMapGrid::gridAnnotationString( double value, QgsLayoutItemMapGrid::AnnotationCoordinate coord, QgsExpressionContext &expressionContext ) const
1390{
1391 //check if we are using degrees (ie, geographic crs)
1392 bool geographic = false;
1393 if ( mCRS.isValid() )
1394 {
1395 geographic = mCRS.isGeographic();
1396 }
1397 else if ( mMap && mMap->layout() )
1398 {
1399 geographic = mMap->crs().isGeographic();
1400 }
1401
1402 if ( geographic && coord == QgsLayoutItemMapGrid::Longitude &&
1403 ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal || mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix ) )
1404 {
1405 // wrap around longitudes > 180 or < -180 degrees, so that, e.g., "190E" -> "170W"
1406 const double wrappedX = std::fmod( value, 360.0 );
1407 if ( wrappedX > 180.0 )
1408 {
1409 value = wrappedX - 360.0;
1410 }
1411 else if ( wrappedX < -180.0 )
1412 {
1413 value = wrappedX + 360.0;
1414 }
1415 }
1416
1417 if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::Decimal )
1418 {
1419 return QString::number( value, 'f', mGridAnnotationPrecision );
1420 }
1421 else if ( mGridAnnotationFormat == QgsLayoutItemMapGrid::DecimalWithSuffix )
1422 {
1423 QString hemisphere;
1424
1425 const double coordRounded = qgsRound( value, mGridAnnotationPrecision );
1426 if ( coord == QgsLayoutItemMapGrid::Longitude )
1427 {
1428 //don't use E/W suffixes if ambiguous (e.g., 180 degrees)
1429 if ( !geographic || ( coordRounded != 180.0 && coordRounded != 0.0 ) )
1430 {
1431 hemisphere = value < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
1432 }
1433 }
1434 else
1435 {
1436 //don't use N/S suffixes if ambiguous (e.g., 0 degrees)
1437 if ( !geographic || coordRounded != 0.0 )
1438 {
1439 hemisphere = value < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
1440 }
1441 }
1442 if ( geographic )
1443 {
1444 //insert degree symbol for geographic coordinates
1445 return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + QChar( 176 ) + hemisphere;
1446 }
1447 else
1448 {
1449 return QString::number( std::fabs( value ), 'f', mGridAnnotationPrecision ) + hemisphere;
1450 }
1451 }
1452 else if ( mGridAnnotationFormat == CustomFormat )
1453 {
1454 expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), value, true ) );
1455 expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), coord == QgsLayoutItemMapGrid::Longitude ? "x" : "y", true ) );
1456 if ( !mGridAnnotationExpression )
1457 {
1458 mGridAnnotationExpression.reset( new QgsExpression( mGridAnnotationExpressionString ) );
1459 mGridAnnotationExpression->prepare( &expressionContext );
1460 }
1461 return mGridAnnotationExpression->evaluate( &expressionContext ).toString();
1462 }
1463
1465 QgsCoordinateFormatter::FormatFlags flags = QgsCoordinateFormatter::FormatFlags();
1466 switch ( mGridAnnotationFormat )
1467 {
1468 case Decimal:
1469 case DecimalWithSuffix:
1470 case CustomFormat:
1471 break; // already handled above
1472
1473 case DegreeMinute:
1476 break;
1477
1478 case DegreeMinuteSecond:
1481 break;
1482
1485 flags = QgsCoordinateFormatter::FormatFlags();
1486 break;
1487
1488 case DegreeMinutePadded:
1491 break;
1492
1495 flags = QgsCoordinateFormatter::FormatFlags();
1496 break;
1497
1501 break;
1502 }
1503
1504 switch ( coord )
1505 {
1506 case Longitude:
1507 return QgsCoordinateFormatter::formatX( value, format, mGridAnnotationPrecision, flags );
1508
1509 case Latitude:
1510 return QgsCoordinateFormatter::formatY( value, format, mGridAnnotationPrecision, flags );
1511 }
1512
1513 return QString(); // no warnings
1514}
1515
1516int QgsLayoutItemMapGrid::xGridLines() const
1517{
1518 if ( !mMap || mEvaluatedIntervalY <= 0.0 )
1519 {
1520 return 1;
1521 }
1522
1523
1524 QPolygonF mapPolygon = mMap->transformedMapPolygon();
1525 QRectF mapBoundingRect = mapPolygon.boundingRect();
1526 double gridIntervalY = mEvaluatedIntervalY;
1527 double gridOffsetY = mEvaluatedOffsetY;
1528 double annotationScale = 1.0;
1529 switch ( mGridUnit )
1530 {
1531 case CM:
1532 case MM:
1533 {
1534 mapBoundingRect = mMap->rect();
1535 mapPolygon = QPolygonF( mMap->rect() );
1536 if ( mGridUnit == CM )
1537 {
1538 annotationScale = 0.1;
1539 gridIntervalY *= 10;
1540 gridOffsetY *= 10;
1541 }
1542 break;
1543 }
1544
1545 case MapUnit:
1547 break;
1548 }
1549
1550 //consider to round up to the next step in case the left boundary is > 0
1551 const double roundCorrection = mapBoundingRect.top() > 0 ? 1.0 : 0.0;
1552 double currentLevel = static_cast< int >( ( mapBoundingRect.top() - gridOffsetY ) / gridIntervalY + roundCorrection ) * gridIntervalY + gridOffsetY;
1553
1554 int gridLineCount = 0;
1555 if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || ( mGridUnit != MapUnit && mGridUnit != DynamicPageSizeBased ) )
1556 {
1557 //no rotation. Do it 'the easy way'
1558
1559 double yCanvasCoord;
1560 while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1561 {
1562 yCanvasCoord = mMap->rect().height() * ( 1 - ( currentLevel - mapBoundingRect.top() ) / mapBoundingRect.height() );
1563 GridLine newLine;
1564 newLine.coordinate = currentLevel * annotationScale;
1565 newLine.coordinateType = AnnotationCoordinate::Latitude;
1566 newLine.line = QPolygonF() << QPointF( 0, yCanvasCoord ) << QPointF( mMap->rect().width(), yCanvasCoord );
1567 mGridLines.append( newLine );
1568 currentLevel += gridIntervalY;
1569 gridLineCount++;
1570 }
1571 return 0;
1572 }
1573
1574 //the four border lines
1575 QVector<QLineF> borderLines;
1576 borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1577 borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1578 borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1579 borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1580
1581 QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1582
1583 while ( currentLevel <= mapBoundingRect.bottom() && gridLineCount < MAX_GRID_LINES )
1584 {
1585 intersectionList.clear();
1586 const QLineF gridLine( mapBoundingRect.left(), currentLevel, mapBoundingRect.right(), currentLevel );
1587
1588 QVector<QLineF>::const_iterator it = borderLines.constBegin();
1589 for ( ; it != borderLines.constEnd(); ++it )
1590 {
1591 QPointF intersectionPoint;
1592 if ( it->intersects( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1593 {
1594 intersectionList.push_back( intersectionPoint );
1595 if ( intersectionList.size() >= 2 )
1596 {
1597 break; //we already have two intersections, skip further tests
1598 }
1599 }
1600 }
1601
1602 if ( intersectionList.size() >= 2 )
1603 {
1604 GridLine newLine;
1605 newLine.coordinate = currentLevel;
1606 newLine.coordinateType = AnnotationCoordinate::Latitude;
1607 newLine.line = QPolygonF() << mMap->mapToItemCoords( intersectionList.at( 0 ) ) << mMap->mapToItemCoords( intersectionList.at( 1 ) );
1608 mGridLines.append( newLine );
1609 gridLineCount++;
1610 }
1611 currentLevel += gridIntervalY;
1612 }
1613
1614
1615 return 0;
1616}
1617
1618int QgsLayoutItemMapGrid::yGridLines() const
1619{
1620 if ( !mMap || mEvaluatedIntervalX <= 0.0 )
1621 {
1622 return 1;
1623 }
1624
1625 QPolygonF mapPolygon = mMap->transformedMapPolygon();
1626 QRectF mapBoundingRect = mapPolygon.boundingRect();
1627 double gridIntervalX = mEvaluatedIntervalX;
1628 double gridOffsetX = mEvaluatedOffsetX;
1629 double annotationScale = 1.0;
1630 switch ( mGridUnit )
1631 {
1632 case CM:
1633 case MM:
1634 {
1635 mapBoundingRect = mMap->rect();
1636 mapPolygon = QPolygonF( mMap->rect() );
1637 if ( mGridUnit == CM )
1638 {
1639 annotationScale = 0.1;
1640 gridIntervalX *= 10;
1641 gridOffsetX *= 10;
1642 }
1643 break;
1644 }
1645
1646 case MapUnit:
1648 break;
1649 }
1650
1651 //consider to round up to the next step in case the left boundary is > 0
1652 const double roundCorrection = mapBoundingRect.left() > 0 ? 1.0 : 0.0;
1653 double currentLevel = static_cast< int >( ( mapBoundingRect.left() - gridOffsetX ) / gridIntervalX + roundCorrection ) * gridIntervalX + gridOffsetX;
1654
1655 int gridLineCount = 0;
1656 if ( qgsDoubleNear( mMap->mapRotation(), 0.0 ) || ( mGridUnit != MapUnit && mGridUnit != DynamicPageSizeBased ) )
1657 {
1658 //no rotation. Do it 'the easy way'
1659 double xCanvasCoord;
1660 while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1661 {
1662 xCanvasCoord = mMap->rect().width() * ( currentLevel - mapBoundingRect.left() ) / mapBoundingRect.width();
1663
1664 GridLine newLine;
1665 newLine.coordinate = currentLevel * annotationScale;
1666 newLine.coordinateType = AnnotationCoordinate::Longitude;
1667 newLine.line = QPolygonF() << QPointF( xCanvasCoord, 0 ) << QPointF( xCanvasCoord, mMap->rect().height() );
1668 mGridLines.append( newLine );
1669 currentLevel += gridIntervalX;
1670 gridLineCount++;
1671 }
1672 return 0;
1673 }
1674
1675 //the four border lines
1676 QVector<QLineF> borderLines;
1677 borderLines << QLineF( mapPolygon.at( 0 ), mapPolygon.at( 1 ) );
1678 borderLines << QLineF( mapPolygon.at( 1 ), mapPolygon.at( 2 ) );
1679 borderLines << QLineF( mapPolygon.at( 2 ), mapPolygon.at( 3 ) );
1680 borderLines << QLineF( mapPolygon.at( 3 ), mapPolygon.at( 0 ) );
1681
1682 QVector<QPointF> intersectionList; //intersects between border lines and grid lines
1683
1684 while ( currentLevel <= mapBoundingRect.right() && gridLineCount < MAX_GRID_LINES )
1685 {
1686 intersectionList.clear();
1687 const QLineF gridLine( currentLevel, mapBoundingRect.bottom(), currentLevel, mapBoundingRect.top() );
1688
1689 QVector<QLineF>::const_iterator it = borderLines.constBegin();
1690 for ( ; it != borderLines.constEnd(); ++it )
1691 {
1692 QPointF intersectionPoint;
1693 if ( it->intersects( gridLine, &intersectionPoint ) == QLineF::BoundedIntersection )
1694 {
1695 intersectionList.push_back( intersectionPoint );
1696 if ( intersectionList.size() >= 2 )
1697 {
1698 break; //we already have two intersections, skip further tests
1699 }
1700 }
1701 }
1702
1703 if ( intersectionList.size() >= 2 )
1704 {
1705 GridLine newLine;
1706 newLine.coordinate = currentLevel;
1707 newLine.coordinateType = AnnotationCoordinate::Longitude;
1708 newLine.line = QPolygonF() << mMap->mapToItemCoords( intersectionList.at( 0 ) ) << mMap->mapToItemCoords( intersectionList.at( 1 ) );
1709 mGridLines.append( newLine );
1710 gridLineCount++;
1711 }
1712 currentLevel += gridIntervalX;
1713 }
1714
1715 return 0;
1716}
1717
1718int QgsLayoutItemMapGrid::xGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t ) const
1719{
1720 if ( !mMap || mEvaluatedIntervalY <= 0.0 )
1721 {
1722 return 1;
1723 }
1724
1725 const double roundCorrection = bbox.yMaximum() > 0 ? 1.0 : 0.0;
1726 double currentLevel = static_cast< int >( ( bbox.yMaximum() - mEvaluatedOffsetY ) / mEvaluatedIntervalY + roundCorrection ) * mEvaluatedIntervalY + mEvaluatedOffsetY;
1727
1728 const double minX = bbox.xMinimum();
1729 const double maxX = bbox.xMaximum();
1730 double step = ( maxX - minX ) / 20;
1731
1732 bool crosses180 = false;
1733 bool crossed180 = false;
1734 if ( mCRS.isGeographic() && ( minX > maxX ) )
1735 {
1736 //handle 180 degree longitude crossover
1737 crosses180 = true;
1738 step = ( maxX + 360.0 - minX ) / 20;
1739 }
1740
1741 if ( qgsDoubleNear( step, 0.0 ) )
1742 return 1;
1743
1744 int gridLineCount = 0;
1745 while ( currentLevel >= bbox.yMinimum() && gridLineCount < MAX_GRID_LINES )
1746 {
1747 QPolygonF gridLine;
1748 double currentX = minX;
1749 bool cont = true;
1750 while ( cont )
1751 {
1752 if ( ( !crosses180 || crossed180 ) && ( currentX > maxX ) )
1753 {
1754 cont = false;
1755 }
1756
1757 try
1758 {
1759 const QgsPointXY mapPoint = t.transform( currentX, currentLevel ); //transform back to map crs
1760 gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) ); //transform back to composer coords
1761 }
1762 catch ( QgsCsException &cse )
1763 {
1764 Q_UNUSED( cse )
1765 QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1766 }
1767
1768 currentX += step;
1769 if ( crosses180 && currentX > 180.0 )
1770 {
1771 currentX -= 360.0;
1772 crossed180 = true;
1773 }
1774 }
1775 crossed180 = false;
1776
1777 const QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1778 QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1779 for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1780 {
1781 if ( !( *lineIt ).isEmpty() )
1782 {
1783 GridLine newLine;
1784 newLine.coordinate = currentLevel;
1785 newLine.coordinateType = AnnotationCoordinate::Latitude;
1786 newLine.line = QPolygonF( *lineIt );
1787 mGridLines.append( newLine );
1788 gridLineCount++;
1789 }
1790 }
1791 currentLevel -= mEvaluatedIntervalY;
1792 }
1793
1794 return 0;
1795}
1796
1797int QgsLayoutItemMapGrid::yGridLinesCrsTransform( const QgsRectangle &bbox, const QgsCoordinateTransform &t ) const
1798{
1799 if ( !mMap || mEvaluatedIntervalX <= 0.0 )
1800 {
1801 return 1;
1802 }
1803
1804 const double roundCorrection = bbox.xMinimum() > 0 ? 1.0 : 0.0;
1805 double currentLevel = static_cast< int >( ( bbox.xMinimum() - mEvaluatedOffsetX ) / mEvaluatedIntervalX + roundCorrection ) * mEvaluatedIntervalX + mEvaluatedOffsetX;
1806
1807 const double minY = bbox.yMinimum();
1808 const double maxY = bbox.yMaximum();
1809 const double step = ( maxY - minY ) / 20;
1810
1811 if ( qgsDoubleNear( step, 0.0 ) )
1812 return 1;
1813
1814 bool crosses180 = false;
1815 bool crossed180 = false;
1816 if ( mCRS.isGeographic() && ( bbox.xMinimum() > bbox.xMaximum() ) )
1817 {
1818 //handle 180 degree longitude crossover
1819 crosses180 = true;
1820 }
1821
1822 int gridLineCount = 0;
1823 while ( ( currentLevel <= bbox.xMaximum() || ( crosses180 && !crossed180 ) ) && gridLineCount < MAX_GRID_LINES )
1824 {
1825 QPolygonF gridLine;
1826 double currentY = minY;
1827 bool cont = true;
1828 while ( cont )
1829 {
1830 if ( currentY > maxY )
1831 {
1832 cont = false;
1833 }
1834 try
1835 {
1836 //transform back to map crs
1837 const QgsPointXY mapPoint = t.transform( currentLevel, currentY );
1838 //transform back to composer coords
1839 gridLine.append( mMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) );
1840 }
1841 catch ( QgsCsException &cse )
1842 {
1843 Q_UNUSED( cse )
1844 QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
1845 }
1846
1847 currentY += step;
1848 }
1849 //clip grid line to map polygon
1850 const QList<QPolygonF> lineSegments = trimLinesToMap( gridLine, QgsRectangle( mMap->rect() ) );
1851 QList<QPolygonF>::const_iterator lineIt = lineSegments.constBegin();
1852 for ( ; lineIt != lineSegments.constEnd(); ++lineIt )
1853 {
1854 if ( !( *lineIt ).isEmpty() )
1855 {
1856 GridLine newLine;
1857 newLine.coordinate = currentLevel;
1858 newLine.coordinateType = AnnotationCoordinate::Longitude;
1859 newLine.line = QPolygonF( *lineIt );
1860 mGridLines.append( newLine );
1861 gridLineCount++;
1862 }
1863 }
1864 currentLevel += mEvaluatedIntervalX;
1865 if ( crosses180 && currentLevel > 180.0 )
1866 {
1867 currentLevel -= 360.0;
1868 crossed180 = true;
1869 }
1870 }
1871
1872 return 0;
1873}
1874
1875bool QgsLayoutItemMapGrid::shouldShowDivisionForSide( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::BorderSide side ) const
1876{
1877 switch ( side )
1878 {
1880 return testFrameSideFlag( QgsLayoutItemMapGrid::FrameLeft ) && shouldShowForDisplayMode( coordinate, mEvaluatedLeftFrameDivisions );
1882 return testFrameSideFlag( QgsLayoutItemMapGrid::FrameRight ) && shouldShowForDisplayMode( coordinate, mEvaluatedRightFrameDivisions );
1884 return testFrameSideFlag( QgsLayoutItemMapGrid::FrameTop ) && shouldShowForDisplayMode( coordinate, mEvaluatedTopFrameDivisions );
1886 return testFrameSideFlag( QgsLayoutItemMapGrid::FrameBottom ) && shouldShowForDisplayMode( coordinate, mEvaluatedBottomFrameDivisions );
1887 }
1888 return false; // no warnings
1889}
1890
1891bool QgsLayoutItemMapGrid::shouldShowAnnotationForSide( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::BorderSide side ) const
1892{
1893 switch ( side )
1894 {
1896 return shouldShowForDisplayMode( coordinate, mEvaluatedLeftGridAnnotationDisplay );
1898 return shouldShowForDisplayMode( coordinate, mEvaluatedRightGridAnnotationDisplay );
1900 return shouldShowForDisplayMode( coordinate, mEvaluatedTopGridAnnotationDisplay );
1902 return shouldShowForDisplayMode( coordinate, mEvaluatedBottomGridAnnotationDisplay );
1903 }
1904 return false; // no warnings
1905}
1906
1907bool QgsLayoutItemMapGrid::shouldShowForDisplayMode( QgsLayoutItemMapGrid::AnnotationCoordinate coordinate, QgsLayoutItemMapGrid::DisplayMode mode ) const
1908{
1909 return mode == QgsLayoutItemMapGrid::ShowAll
1912}
1913
1914
1916{
1917 if ( ddValue.compare( QLatin1String( "x_only" ), Qt::CaseInsensitive ) == 0 )
1919 else if ( ddValue.compare( QLatin1String( "y_only" ), Qt::CaseInsensitive ) == 0 )
1921 else if ( ddValue.compare( QLatin1String( "disabled" ), Qt::CaseInsensitive ) == 0 )
1923 else if ( ddValue.compare( QLatin1String( "all" ), Qt::CaseInsensitive ) == 0 )
1925 else
1926 return defValue;
1927}
1928
1929
1930void QgsLayoutItemMapGrid::refreshDataDefinedProperties()
1931{
1933
1934 // if we are changing the grid interval or offset, then we also have to mark the transform as dirty
1935 mTransformDirty = mTransformDirty
1940
1942 switch ( mGridUnit )
1943 {
1944 case MapUnit:
1945 case MM:
1946 case CM:
1947 {
1948 mEvaluatedIntervalX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalX, context, mGridIntervalX );
1949 mEvaluatedIntervalY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridIntervalY, context, mGridIntervalY );
1950 break;
1951 }
1952
1954 {
1955 if ( mMaximumIntervalWidth < mMinimumIntervalWidth )
1956 {
1957 mEvaluatedEnabled = false;
1958 }
1959 else
1960 {
1961 const double mapWidthMm = mLayout->renderContext().measurementConverter().convert( mMap->sizeWithUnits(), Qgis::LayoutUnit::Millimeters ).width();
1962 const double mapWidthMapUnits = mapWidth();
1963 const double minUnitsPerSeg = ( mMinimumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1964 const double maxUnitsPerSeg = ( mMaximumIntervalWidth * mapWidthMapUnits ) / mapWidthMm;
1965 const double interval = QgsLayoutUtils::calculatePrettySize( minUnitsPerSeg, maxUnitsPerSeg );
1966 mEvaluatedIntervalX = interval;
1967 mEvaluatedIntervalY = interval;
1968 mTransformDirty = true;
1969 }
1970 break;
1971 }
1972 }
1973 mEvaluatedOffsetX = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetX, context, mGridOffsetX );
1974 mEvaluatedOffsetY = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridOffsetY, context, mGridOffsetY );
1975 mEvaluatedGridFrameWidth = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameSize, context, mGridFrameWidth );
1976 mEvaluatedGridFrameMargin = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameMargin, context, mGridFrameMargin );
1977 mEvaluatedAnnotationFrameDistance = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridLabelDistance, context, mAnnotationFrameDistance );
1978 mEvaluatedCrossLength = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridCrossSize, context, mCrossLength );
1979 mEvaluatedGridFrameLineThickness = mDataDefinedProperties.valueAsDouble( QgsLayoutObject::MapGridFrameLineThickness, context, mGridFramePenThickness );
1980 mEvaluatedLeftGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayLeft, context ), mLeftGridAnnotationDisplay );
1981 mEvaluatedRightGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayRight, context ), mRightGridAnnotationDisplay );
1982 mEvaluatedTopGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayTop, context ), mTopGridAnnotationDisplay );
1983 mEvaluatedBottomGridAnnotationDisplay = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridAnnotationDisplayBottom, context ), mBottomGridAnnotationDisplay );
1984 mEvaluatedLeftFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsLeft, context ), mLeftFrameDivisions );
1985 mEvaluatedRightFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsRight, context ), mRightFrameDivisions );
1986 mEvaluatedTopFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsTop, context ), mTopFrameDivisions );
1987 mEvaluatedBottomFrameDivisions = gridAnnotationDisplayModeFromDD( mDataDefinedProperties.valueAsString( QgsLayoutObject::MapGridFrameDivisionsBottom, context ), mBottomFrameDivisions );
1988
1989}
1990
1991double QgsLayoutItemMapGrid::mapWidth() const
1992{
1993 if ( !mMap )
1994 {
1995 return 0.0;
1996 }
1997
1998 const QgsRectangle mapExtent = mMap->extent();
1999 const Qgis::DistanceUnit distanceUnit = mCRS.isValid() ? mCRS.mapUnits() : mMap->crs().mapUnits();
2000 if ( distanceUnit == Qgis::DistanceUnit::Unknown )
2001 {
2002 return mapExtent.width();
2003 }
2004 else
2005 {
2006 QgsDistanceArea da;
2007
2008 da.setSourceCrs( mMap->crs(), mLayout->project()->transformContext() );
2009 da.setEllipsoid( mLayout->project()->ellipsoid() );
2010
2012 double measure = da.measureLine( QgsPointXY( mapExtent.xMinimum(), mapExtent.yMinimum() ),
2013 QgsPointXY( mapExtent.xMaximum(), mapExtent.yMinimum() ) );
2014 measure /= QgsUnitTypes::fromUnitToUnitFactor( distanceUnit, units );
2015 return measure;
2016 }
2017}
2018
2019bool sortByDistance( QPair<qreal, QgsLayoutItemMapGrid::BorderSide> a, QPair<qreal, QgsLayoutItemMapGrid::BorderSide> b )
2020{
2021 return a.first < b.first;
2022}
2023
2024QgsLayoutItemMapGrid::BorderSide QgsLayoutItemMapGrid::borderForLineCoord( QPointF p, const AnnotationCoordinate coordinateType ) const
2025{
2026 if ( !mMap )
2027 {
2029 }
2030
2031 const double tolerance = std::max( mMap->frameEnabled() ? mMap->pen().widthF() : 0.0, 1.0 );
2032
2033 //check for corner coordinates
2034 if ( ( p.y() <= tolerance && p.x() <= tolerance ) // top left
2035 || ( p.y() <= tolerance && p.x() >= ( mMap->rect().width() - tolerance ) ) //top right
2036 || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() <= tolerance ) //bottom left
2037 || ( p.y() >= ( mMap->rect().height() - tolerance ) && p.x() >= ( mMap->rect().width() - tolerance ) ) //bottom right
2038 )
2039 {
2040 //coordinate is in corner - fall back to preferred side for coordinate type
2041 if ( coordinateType == QgsLayoutItemMapGrid::Latitude )
2042 {
2043 if ( p.x() <= tolerance )
2044 {
2046 }
2047 else
2048 {
2050 }
2051 }
2052 else
2053 {
2054 if ( p.y() <= tolerance )
2055 {
2057 }
2058 else
2059 {
2061 }
2062 }
2063 }
2064
2065 //otherwise, guess side based on closest map side to point
2066 QList< QPair<qreal, QgsLayoutItemMapGrid::BorderSide > > distanceToSide;
2067 distanceToSide << qMakePair( p.x(), QgsLayoutItemMapGrid::Left );
2068 distanceToSide << qMakePair( mMap->rect().width() - p.x(), QgsLayoutItemMapGrid::Right );
2069 distanceToSide << qMakePair( p.y(), QgsLayoutItemMapGrid::Top );
2070 distanceToSide << qMakePair( mMap->rect().height() - p.y(), QgsLayoutItemMapGrid::Bottom );
2071
2072 std::sort( distanceToSide.begin(), distanceToSide.end(), sortByDistance );
2073 return distanceToSide.at( 0 ).second;
2074}
2075
2077{
2078 mGridLineSymbol.reset( symbol );
2079}
2080
2082{
2083 return mGridLineSymbol.get();
2084}
2085
2087{
2088 return mGridLineSymbol.get();
2089}
2090
2092{
2093 mGridMarkerSymbol.reset( symbol );
2094}
2095
2097{
2098 return mGridMarkerSymbol.get();
2099}
2100
2102{
2103 return mGridMarkerSymbol.get();
2104}
2105
2107{
2108 mAnnotationFormat.setFont( font );
2109 if ( font.pointSizeF() > 0 )
2110 {
2111 mAnnotationFormat.setSize( font.pointSizeF() );
2112 mAnnotationFormat.setSizeUnit( Qgis::RenderUnit::Points );
2113 }
2114 else if ( font.pixelSize() > 0 )
2115 {
2116 mAnnotationFormat.setSize( font.pixelSize() );
2117 mAnnotationFormat.setSizeUnit( Qgis::RenderUnit::Pixels );
2118 }
2119}
2120
2122{
2123 return mAnnotationFormat.toQFont();
2124}
2125
2127{
2128 mAnnotationFormat.setColor( color );
2129}
2130
2132{
2133 return mAnnotationFormat.color();
2134}
2135
2137{
2138 switch ( border )
2139 {
2141 mLeftGridAnnotationDisplay = display;
2142 break;
2144 mRightGridAnnotationDisplay = display;
2145 break;
2147 mTopGridAnnotationDisplay = display;
2148 break;
2150 mBottomGridAnnotationDisplay = display;
2151 break;
2152 }
2153
2154 refreshDataDefinedProperties();
2155
2156 if ( mMap )
2157 {
2159 mMap->update();
2160 }
2161}
2162
2164{
2165 switch ( border )
2166 {
2168 return mLeftGridAnnotationDisplay;
2170 return mRightGridAnnotationDisplay;
2172 return mTopGridAnnotationDisplay;
2174 return mBottomGridAnnotationDisplay;
2175 }
2176 return mBottomGridAnnotationDisplay; // no warnings
2177}
2178
2180{
2181 double top = 0.0;
2182 double right = 0.0;
2183 double bottom = 0.0;
2184 double left = 0.0;
2185 calculateMaxExtension( top, right, bottom, left );
2186 return std::max( std::max( std::max( top, right ), bottom ), left );
2187}
2188
2189void QgsLayoutItemMapGrid::calculateMaxExtension( double &top, double &right, double &bottom, double &left ) const
2190{
2191 top = 0.0;
2192 right = 0.0;
2193 bottom = 0.0;
2194 left = 0.0;
2195
2196 if ( !mMap || !mEvaluatedEnabled )
2197 {
2198 return;
2199 }
2200
2201 //setup render context
2203 const QgsExpressionContext expressionContext = createExpressionContext();
2204 context.setExpressionContext( expressionContext );
2205
2206 GridExtension extension;
2207
2208 //collect grid lines
2209 switch ( mGridUnit )
2210 {
2211 case MapUnit:
2213 {
2214 if ( mCRS.isValid() && mCRS != mMap->crs() )
2215 {
2216 drawGridCrsTransform( context, 0, true );
2217 break;
2218 }
2219 }
2221 case CM:
2222 case MM:
2223 drawGridNoTransform( context, 0, true );
2224 break;
2225 }
2226
2227 if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame || mShowGridAnnotation )
2228 updateGridLinesAnnotationsPositions();
2229
2230 if ( mGridFrameStyle != QgsLayoutItemMapGrid::NoFrame )
2231 {
2232 drawGridFrame( nullptr, &extension );
2233 }
2234
2235 if ( mShowGridAnnotation )
2236 {
2237 drawCoordinateAnnotations( context, context.expressionContext(), &extension );
2238 }
2239
2240 top = extension.top;
2241 right = extension.right;
2242 bottom = extension.bottom;
2243 left = extension.left;
2244}
2245
2247{
2249 refreshDataDefinedProperties();
2250}
2251
2253{
2254 if ( unit == mGridUnit )
2255 {
2256 return;
2257 }
2258 mGridUnit = unit;
2259 mTransformDirty = true;
2260}
2261
2262void QgsLayoutItemMapGrid::setIntervalX( const double interval )
2263{
2264 if ( qgsDoubleNear( interval, mGridIntervalX ) )
2265 {
2266 return;
2267 }
2268 mGridIntervalX = interval;
2269 mTransformDirty = true;
2270 refreshDataDefinedProperties();
2271}
2272
2273void QgsLayoutItemMapGrid::setIntervalY( const double interval )
2274{
2275 if ( qgsDoubleNear( interval, mGridIntervalY ) )
2276 {
2277 return;
2278 }
2279 mGridIntervalY = interval;
2280 mTransformDirty = true;
2281 refreshDataDefinedProperties();
2282}
2283
2284void QgsLayoutItemMapGrid::setOffsetX( const double offset )
2285{
2286 if ( qgsDoubleNear( offset, mGridOffsetX ) )
2287 {
2288 return;
2289 }
2290 mGridOffsetX = offset;
2291 mTransformDirty = true;
2292 refreshDataDefinedProperties();
2293}
2294
2295void QgsLayoutItemMapGrid::setOffsetY( const double offset )
2296{
2297 if ( qgsDoubleNear( offset, mGridOffsetY ) )
2298 {
2299 return;
2300 }
2301 mGridOffsetY = offset;
2302 mTransformDirty = true;
2303 refreshDataDefinedProperties();
2304}
2305
2307{
2308 if ( qgsDoubleNear( minWidth, mMinimumIntervalWidth ) )
2309 {
2310 return;
2311 }
2312 mMinimumIntervalWidth = minWidth;
2313 mTransformDirty = true;
2314 refreshDataDefinedProperties();
2315}
2316
2318{
2319 if ( qgsDoubleNear( maxWidth, mMaximumIntervalWidth ) )
2320 {
2321 return;
2322 }
2323 mMaximumIntervalWidth = maxWidth;
2324 mTransformDirty = true;
2325 refreshDataDefinedProperties();
2326}
2327
2329{
2330 if ( style == mGridStyle )
2331 {
2332 return;
2333 }
2334 mGridStyle = style;
2335 mTransformDirty = true;
2336}
2337
2338void QgsLayoutItemMapGrid::setCrossLength( const double length )
2339{
2340 mCrossLength = length;
2341 refreshDataDefinedProperties();
2342}
2343
2345{
2346 switch ( border )
2347 {
2349 mLeftGridAnnotationDirection = direction;
2350 break;
2352 mRightGridAnnotationDirection = direction;
2353 break;
2355 mTopGridAnnotationDirection = direction;
2356 break;
2358 mBottomGridAnnotationDirection = direction;
2359 break;
2360 }
2361
2362 if ( mMap )
2363 {
2365 mMap->update();
2366 }
2367}
2368
2370{
2371 mGridFrameSides = flags;
2372}
2373
2375{
2376 if ( on )
2377 mGridFrameSides |= flag;
2378 else
2379 mGridFrameSides &= ~flag;
2380}
2381
2382QgsLayoutItemMapGrid::FrameSideFlags QgsLayoutItemMapGrid::frameSideFlags() const
2383{
2384 return mGridFrameSides;
2385}
2386
2388{
2390 context.appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
2391 context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_number" ), 0, true ) );
2392 context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "grid_axis" ), "x", true ) );
2393 context.setHighlightedVariables( QStringList() << QStringLiteral( "grid_number" ) << QStringLiteral( "grid_axis" ) );
2394 return context;
2395}
2396
2398{
2399 if ( mGridLineSymbol )
2400 {
2401 QgsStyleSymbolEntity entity( mGridLineSymbol.get() );
2402 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2403 return false;
2404 }
2405 if ( mGridMarkerSymbol )
2406 {
2407 QgsStyleSymbolEntity entity( mGridMarkerSymbol.get() );
2408 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "grid" ), QObject::tr( "Grid" ) ) ) )
2409 return false;
2410 }
2411
2412 return true;
2413}
2414
2416{
2417 mTransformDirty = true;
2418 refreshDataDefinedProperties();
2420 mMap->update();
2421}
2422
2424{
2425 return mGridFrameSides.testFlag( flag );
2426}
2427
2428void QgsLayoutItemMapGrid::setFrameWidth( const double width )
2429{
2430 mGridFrameWidth = width;
2431 refreshDataDefinedProperties();
2432}
2433
2434void QgsLayoutItemMapGrid::setFrameMargin( const double margin )
2435{
2436 mGridFrameMargin = margin;
2437 refreshDataDefinedProperties();
2438}
2439
2441{
2442 mGridFramePenThickness = width;
2443 refreshDataDefinedProperties();
2444}
2445
2447{
2448 mLeftGridAnnotationDirection = direction;
2449 mRightGridAnnotationDirection = direction;
2450 mTopGridAnnotationDirection = direction;
2451 mBottomGridAnnotationDirection = direction;
2452}
2453
2455{
2456 switch ( border )
2457 {
2459 mLeftGridAnnotationPosition = position;
2460 break;
2462 mRightGridAnnotationPosition = position;
2463 break;
2465 mTopGridAnnotationPosition = position;
2466 break;
2468 mBottomGridAnnotationPosition = position;
2469 break;
2470 }
2471
2472 if ( mMap )
2473 {
2475 mMap->update();
2476 }
2477}
2478
2480{
2481 switch ( border )
2482 {
2484 return mLeftGridAnnotationPosition;
2486 return mRightGridAnnotationPosition;
2488 return mTopGridAnnotationPosition;
2490 return mBottomGridAnnotationPosition;
2491 }
2492 return mLeftGridAnnotationPosition; // no warnings
2493}
2494
2496{
2497 mAnnotationFrameDistance = distance;
2498 refreshDataDefinedProperties();
2499}
2500
2502{
2503 if ( !mMap )
2504 {
2505 return mLeftGridAnnotationDirection;
2506 }
2507
2508 switch ( border )
2509 {
2511 return mLeftGridAnnotationDirection;
2513 return mRightGridAnnotationDirection;
2515 return mTopGridAnnotationDirection;
2517 return mBottomGridAnnotationDirection;
2518 }
2519 return mLeftGridAnnotationDirection; // no warnings
2520}
2521
2523{
2524 switch ( border )
2525 {
2527 mLeftFrameDivisions = divisions;
2528 break;
2530 mRightFrameDivisions = divisions;
2531 break;
2533 mTopFrameDivisions = divisions;
2534 break;
2536 mBottomFrameDivisions = divisions;
2537 break;
2538 }
2539
2540 refreshDataDefinedProperties();
2541
2542 if ( mMap )
2543 {
2544 mMap->update();
2545 }
2546}
2547
2549{
2550 switch ( border )
2551 {
2553 return mLeftFrameDivisions;
2555 return mRightFrameDivisions;
2557 return mTopFrameDivisions;
2559 return mBottomFrameDivisions;
2560 }
2561 return mLeftFrameDivisions; // no warnings
2562}
2563
2564int QgsLayoutItemMapGrid::crsGridParams( QgsRectangle &crsRect, QgsCoordinateTransform &inverseTransform ) const
2565{
2566 if ( !mMap )
2567 {
2568 return 1;
2569 }
2570
2571 try
2572 {
2573 const QgsCoordinateTransform tr( mMap->crs(), mCRS, mLayout->project() );
2574 QgsCoordinateTransform extentTransform = tr;
2575 extentTransform.setBallparkTransformsAreAppropriate( true );
2576 const QPolygonF mapPolygon = mMap->transformedMapPolygon();
2577 const QRectF mbr = mapPolygon.boundingRect();
2578 const QgsRectangle mapBoundingRect( mbr.left(), mbr.bottom(), mbr.right(), mbr.top() );
2579
2580
2581 if ( mCRS.isGeographic() )
2582 {
2583 //handle crossing the 180 degree longitude line
2584 QgsPointXY lowerLeft( mapBoundingRect.xMinimum(), mapBoundingRect.yMinimum() );
2585 QgsPointXY upperRight( mapBoundingRect.xMaximum(), mapBoundingRect.yMaximum() );
2586
2587 lowerLeft = tr.transform( lowerLeft.x(), lowerLeft.y() );
2588 upperRight = tr.transform( upperRight.x(), upperRight.y() );
2589
2590 if ( lowerLeft.x() > upperRight.x() )
2591 {
2592 //we've crossed the line
2593 crsRect = extentTransform.transformBoundingBox( mapBoundingRect, Qgis::TransformDirection::Forward, true );
2594 }
2595 else
2596 {
2597 //didn't cross the line
2598 crsRect = extentTransform.transformBoundingBox( mapBoundingRect );
2599 }
2600 }
2601 else
2602 {
2603 crsRect = extentTransform.transformBoundingBox( mapBoundingRect );
2604 }
2605
2606 inverseTransform = QgsCoordinateTransform( mCRS, mMap->crs(), mLayout->project() );
2607 }
2608 catch ( QgsCsException &cse )
2609 {
2610 Q_UNUSED( cse )
2611 QgsDebugMsg( QStringLiteral( "Caught CRS exception %1" ).arg( cse.what() ) );
2612 return 1;
2613 }
2614 return 0;
2615}
2616
2617QList<QPolygonF> QgsLayoutItemMapGrid::trimLinesToMap( const QPolygonF &line, const QgsRectangle &rect )
2618{
2619 const QgsGeometry lineGeom = QgsGeometry::fromQPolygonF( line );
2620 const QgsGeometry rectGeom = QgsGeometry::fromRect( rect );
2621
2622 const QgsGeometry intersected = lineGeom.intersection( rectGeom );
2623 const QVector<QgsGeometry> intersectedParts = intersected.asGeometryCollection();
2624
2625 QList<QPolygonF> trimmedLines;
2626 QVector<QgsGeometry>::const_iterator geomIt = intersectedParts.constBegin();
2627 for ( ; geomIt != intersectedParts.constEnd(); ++geomIt )
2628 {
2629 trimmedLines << ( *geomIt ).asQPolygonF();
2630 }
2631 return trimmedLines;
2632}
DistanceUnit
Units of distance.
Definition: qgis.h:3047
@ ApplyScalingWorkaroundForTextRendering
Whether a scaling workaround designed to stablise the rendering of small font sizes (or for painters ...
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.
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.
@ FlagDegreesUseStringSuffix
Include a direction suffix (eg 'N', 'E', 'S' or 'W'), otherwise a "-" prefix is used for west and sou...
@ FlagDegreesPadMinutesSeconds
Pad minute and second values with leading zeros, eg '05' instead of '5'.
static QString formatX(double x, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats an x coordinate value according to the specified parameters.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Q_GADGET Qgis::DistanceUnit mapUnits
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
Class for doing transforms between two map coordinate systems.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
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
Definition: qgsexception.h:48
Single scope for storing variables and functions for use within a QgsExpressionContext.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setHighlightedVariables(const QStringList &variableNames)
Sets the list of variable names within the context intended to be highlighted to the user.
Class for parsing and evaluation of expressions (formerly called "search strings").
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
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.
Q_GADGET bool isNull
Definition: qgsgeometry.h:166
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
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.
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.
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.
@ FrameTop
Top side of map.
@ 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 draw(QPainter *painter) override
Draws the item on to a destination painter.
void setIntervalY(double interval)
Sets the interval between grid lines in the y-direction.
Q_DECL_DEPRECATED QColor annotationFontColor() const
Returns the font color used for drawing grid annotations.
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,...
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.
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 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 setFrameMargin(const double margin)
Sets the grid frame margin (in layout units).
QgsLayoutItemMapGrid(const QString &name, QgsLayoutItemMap *map)
Constructor for QgsLayoutItemMapGrid.
~QgsLayoutItemMapGrid() override
void setMarkerSymbol(QgsMarkerSymbol *symbol)
Sets the marker symbol used for drawing grid points.
void setMaximumIntervalWidth(double width)
Sets the maximum width (in millimeters) for grid segments.
bool usesAdvancedEffects() const override
Returns true if the item is drawn using advanced effects, such as blend modes.
Q_DECL_DEPRECATED void setAnnotationFont(const QFont &font)
Sets the font used for drawing grid annotations.
void setLineSymbol(QgsLineSymbol *symbol)
Sets the line symbol used for drawing grid lines.
QgsCoordinateReferenceSystem crs() const
Retrieves the CRS for the grid.
TickLengthMode
Tick length mode (useful for rotated grids)
@ OrthogonalTicks
Align ticks orthogonaly.
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.
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.
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...
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).
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).
void setStyle(GridStyle style)
Sets the grid style, which controls how the grid is drawn over the map's contents.
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
const QgsLayout * layout() const
Returns the layout the object is attached to.
QPointer< QgsLayout > mLayout
@ MapGridIntervalX
Map grid interval X.
@ MapGridAnnotationDisplayBottom
Map annotation display bottom.
@ MapGridIntervalY
Map grid interval Y.
@ MapGridFrameSize
Map grid frame size.
@ MapGridFrameDivisionsBottom
Map frame division display bottom.
@ MapGridAnnotationDisplayRight
Map annotation display right.
@ MapGridFrameMargin
Map grid frame margin.
@ MapGridOffsetX
Map grid offset X.
@ MapGridLabelDistance
Map grid label distance.
@ MapGridAnnotationDisplayLeft
Map annotation display left.
@ MapGridFrameDivisionsLeft
Map frame division display left.
@ MapGridEnabled
Map grid enabled.
@ MapGridFrameLineThickness
Map grid frame line thickness.
@ MapGridFrameDivisionsRight
Map frame division display right.
@ MapGridFrameDivisionsTop
Map frame division display top.
@ MapGridCrossSize
Map grid cross size.
@ MapGridOffsetY
Map grid offset Y.
@ MapGridAnnotationDisplayTop
Map annotation display top.
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 ...
Definition: qgslayout.cpp:365
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgslinesymbol.h:30
static QgsLineSymbol * createSimple(const QVariantMap &properties)
Create a line symbol with one symbol layer: SimpleLine with specified properties.
A marker symbol type, for rendering Point and MultiPoint geometries.
static QgsMarkerSymbol * createSimple(const QVariantMap &properties)
Create a marker symbol with one symbol layer: SimpleMarker with specified properties.
A class to represent a 2D point.
Definition: qgspointxy.h:59
bool isEmpty() const SIP_HOLDGIL
Returns true if the geometry is empty.
Definition: qgspointxy.h:249
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
bool isActive(int key) const override
Returns true if the collection contains an active property with the specified key.
The class is used as a container of context for various read/write operations on other objects.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
Contains information about the context of a rendering operation.
void setForceVectorOutput(bool force)
Sets whether rendering operations should use vector operations instead of any faster raster shortcuts...
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
QgsExpressionContext & expressionContext()
Gets the expression context.
void setFlag(Qgis::RenderContextFlag flag, bool on=true)
Enable or disable a particular flag (other flags are not affected)
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.
Scoped object for saving and restoring a QPainter object's state.
Scoped object for temporary scaling of a QgsRenderContext for pixel based rendering.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:63
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:1341
static QColor decodeColor(const QString &str)
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 QString encodeColor(const QColor &color)
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.
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
#define FALLTHROUGH
Definition: qgis.h:4120
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:3448
double qgsRound(double number, int places)
Returns a double number, rounded (as close as possible) to the specified number of places.
Definition: qgis.h:3563
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:3509
QVector< QgsPointXY > QgsPolylineXY
Polyline as represented as a vector of two-dimensional points.
Definition: qgsgeometry.h:63
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 QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Single variable definition for use within a QgsExpressionContextScope.
Contains information relating to the style entity currently being visited.