Skip to content

Commit 13d0556

Browse files
authored
[FEATURE][needs-docs] Custom SVG path and size for the north arrow decoration (#6715)
1 parent 8ebd47b commit 13d0556

5 files changed

+350
-227
lines changed

src/app/qgsdecorationnortharrow.cpp

+121-108
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ void QgsDecorationNorthArrow::projectRead()
7070
QgsDecorationItem::projectRead();
7171
mColor = QgsSymbolLayerUtils::decodeColor( QgsProject::instance()->readEntry( mNameConfig, QStringLiteral( "/Color" ), QStringLiteral( "#000000" ) ) );
7272
mOutlineColor = QgsSymbolLayerUtils::decodeColor( QgsProject::instance()->readEntry( mNameConfig, QStringLiteral( "/OutlineColor" ), QStringLiteral( "#FFFFFF" ) ) );
73+
mSize = QgsProject::instance()->readDoubleEntry( mNameConfig, QStringLiteral( "/Size" ), 16.0 );
74+
mSvgPath = QgsProject::instance()->readEntry( mNameConfig, QStringLiteral( "/SvgPath" ), QString() );
7375
mRotationInt = QgsProject::instance()->readNumEntry( mNameConfig, QStringLiteral( "/Rotation" ), 0 );
7476
mAutomatic = QgsProject::instance()->readBoolEntry( mNameConfig, QStringLiteral( "/Automatic" ), true );
7577
mMarginHorizontal = QgsProject::instance()->readNumEntry( mNameConfig, QStringLiteral( "/MarginH" ), 0 );
@@ -81,7 +83,8 @@ void QgsDecorationNorthArrow::saveToProject()
8183
QgsDecorationItem::saveToProject();
8284
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Color" ), QgsSymbolLayerUtils::encodeColor( mColor ) );
8385
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/OutlineColor" ), QgsSymbolLayerUtils::encodeColor( mOutlineColor ) );
84-
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Rotation" ), mRotationInt );
86+
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Size" ), mSize );
87+
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/SvgPath" ), mSvgPath );
8588
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/Automatic" ), mAutomatic );
8689
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/MarginH" ), mMarginHorizontal );
8790
QgsProject::instance()->writeEntry( mNameConfig, QStringLiteral( "/MarginV" ), mMarginVertical );
@@ -94,128 +97,138 @@ void QgsDecorationNorthArrow::run()
9497
dlg.exec();
9598
}
9699

100+
QString QgsDecorationNorthArrow::svgPath()
101+
{
102+
if ( !mSvgPath.isEmpty() )
103+
{
104+
QString resolvedPath = QgsSymbolLayerUtils::svgSymbolNameToPath( mSvgPath, QgsProject::instance()->pathResolver() );
105+
bool validSvg = QFileInfo::exists( resolvedPath );
106+
if ( validSvg )
107+
{
108+
return resolvedPath;
109+
}
110+
}
111+
112+
return QStringLiteral( ":/images/north_arrows/default.svg" );
113+
}
114+
97115
void QgsDecorationNorthArrow::render( const QgsMapSettings &mapSettings, QgsRenderContext &context )
98116
{
117+
if ( !enabled() )
118+
return;
99119

100-
//Large IF statement controlled by enable checkbox
101-
if ( enabled() )
102-
{
103-
QSize size( 64, 64 );
104-
QSvgRenderer svg;
120+
double maxLength = mSize * mapSettings.outputDpi() / 25.4;
121+
QSvgRenderer svg;
105122

106-
const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( QStringLiteral( ":/images/north_arrows/default.svg" ), size.width(), mColor, mOutlineColor, 1.0, 1.0 );
107-
svg.load( svgContent );
123+
const QByteArray &svgContent = QgsApplication::svgCache()->svgContent( svgPath(), maxLength, mColor, mOutlineColor, 1.0, 1.0 );
124+
svg.load( svgContent );
108125

109-
if ( svg.isValid() )
126+
if ( svg.isValid() )
127+
{
128+
QSize size( maxLength, maxLength );
129+
QRectF viewBox = svg.viewBoxF();
130+
if ( viewBox.height() > viewBox.width() )
110131
{
111-
double centerXDouble = size.width() / 2.0;
112-
double centerYDouble = size.width() / 2.0;
113-
114-
//save the current canvas rotation
115-
context.painter()->save();
116-
//
117-
//work out how to shift the image so that it rotates
118-
// properly about its center
119-
//(x cos a + y sin a - x, -x sin a + y cos a - y)
120-
//
121-
122-
// could move this call to somewhere else so that it is only
123-
// called when the projection or map extent changes
124-
if ( mAutomatic )
125-
{
126-
try
127-
{
128-
mRotationInt = QgsBearingUtils:: bearingTrueNorth( mapSettings.destinationCrs(), mapSettings.transformContext(), context.extent().center() );
129-
}
130-
catch ( QgsException & )
131-
{
132-
mRotationInt = 0.0;
133-
//QgsDebugMsg( "Can not get direction to true north. Probably project CRS is not defined." );
134-
}
135-
mRotationInt += mapSettings.rotation();
136-
}
132+
size.setWidth( maxLength * viewBox.width() / viewBox.height() );
133+
}
134+
else
135+
{
136+
size.setHeight( maxLength * viewBox.height() / viewBox.width() );
137+
}
137138

138-
double myRadiansDouble = mRotationInt * M_PI / 180.0;
139-
int xShift = static_cast<int>( (
140-
( centerXDouble * std::cos( myRadiansDouble ) ) +
141-
( centerYDouble * std::sin( myRadiansDouble ) )
142-
) - centerXDouble );
143-
int yShift = static_cast<int>( (
144-
( -centerXDouble * std::sin( myRadiansDouble ) ) +
145-
( centerYDouble * std::cos( myRadiansDouble ) )
146-
) - centerYDouble );
147-
148-
// need width/height of paint device
149-
int myHeight = context.painter()->device()->height();
150-
int myWidth = context.painter()->device()->width();
151-
152-
//QgsDebugMsg("Rendering north arrow at " + mPlacementLabels.at(mPlacementIndex));
153-
154-
// Set margin according to selected units
155-
int myXOffset = 0;
156-
int myYOffset = 0;
157-
switch ( mMarginUnit )
139+
double centerXDouble = size.width() / 2.0;
140+
double centerYDouble = size.height() / 2.0;
141+
142+
//save the current canvas rotation
143+
context.painter()->save();
144+
//
145+
//work out how to shift the image so that it rotates
146+
// properly about its center
147+
//(x cos a + y sin a - x, -x sin a + y cos a - y)
148+
//
149+
// could move this call to somewhere else so that it is only
150+
// called when the projection or map extent changes
151+
if ( mAutomatic )
152+
{
153+
try
158154
{
159-
case QgsUnitTypes::RenderMillimeters:
160-
{
161-
int myPixelsInchX = context.painter()->device()->logicalDpiX();
162-
int myPixelsInchY = context.painter()->device()->logicalDpiY();
163-
myXOffset = myPixelsInchX * INCHES_TO_MM * mMarginHorizontal;
164-
myYOffset = myPixelsInchY * INCHES_TO_MM * mMarginVertical;
165-
break;
166-
}
167-
168-
case QgsUnitTypes::RenderPixels:
169-
myXOffset = mMarginHorizontal - 5; // Minus 5 to shift tight into corner
170-
myYOffset = mMarginVertical - 5;
171-
break;
172-
173-
case QgsUnitTypes::RenderPercentage:
174-
myXOffset = ( ( myWidth - size.width() ) / 100. ) * mMarginHorizontal;
175-
myYOffset = ( ( myHeight - size.width() ) / 100. ) * mMarginVertical;
176-
break;
177-
178-
default: // Use default of top left
179-
break;
155+
mRotationInt = QgsBearingUtils:: bearingTrueNorth( mapSettings.destinationCrs(), mapSettings.transformContext(), context.extent().center() );
180156
}
181-
//Determine placement of label from form combo box
182-
switch ( mPlacement )
157+
catch ( QgsException & )
183158
{
184-
case BottomLeft:
185-
context.painter()->translate( myXOffset, myHeight - myYOffset - size.width() );
186-
break;
187-
case TopLeft:
188-
context.painter()->translate( myXOffset, myYOffset );
189-
break;
190-
case TopRight:
191-
context.painter()->translate( myWidth - myXOffset - size.width(), myYOffset );
192-
break;
193-
case BottomRight:
194-
context.painter()->translate( myWidth - myXOffset - size.width(),
195-
myHeight - myYOffset - size.width() );
196-
break;
197-
default:
198-
{
199-
//QgsDebugMsg("Unable to determine where to put north arrow so defaulting to top left");
200-
}
159+
mRotationInt = 0.0;
201160
}
161+
mRotationInt += mapSettings.rotation();
162+
}
202163

203-
//rotate the canvas by the north arrow rotation amount
204-
context.painter()->rotate( mRotationInt );
205-
//Now we can actually do the drawing, and draw a smooth north arrow even when rotated
206-
207-
context.painter()->translate( xShift, yShift );
208-
svg.render( context.painter(), QRectF( 0, 0, size.width(), size.height() ) );
164+
double radiansDouble = mRotationInt * M_PI / 180.0;
165+
int xShift = static_cast<int>( (
166+
( centerXDouble * std::cos( radiansDouble ) ) +
167+
( centerYDouble * std::sin( radiansDouble ) )
168+
) - centerXDouble );
169+
int yShift = static_cast<int>( (
170+
( -centerXDouble * std::sin( radiansDouble ) ) +
171+
( centerYDouble * std::cos( radiansDouble ) )
172+
) - centerYDouble );
173+
// need width/height of paint device
174+
int deviceHeight = context.painter()->device()->height();
175+
int deviceWidth = context.painter()->device()->width();
176+
177+
// Set margin according to selected units
178+
int xOffset = 0;
179+
int yOffset = 0;
180+
switch ( mMarginUnit )
181+
{
182+
case QgsUnitTypes::RenderMillimeters:
183+
{
184+
int pixelsInchX = context.painter()->device()->logicalDpiX();
185+
int pixelsInchY = context.painter()->device()->logicalDpiY();
186+
xOffset = pixelsInchX * INCHES_TO_MM * mMarginHorizontal;
187+
yOffset = pixelsInchY * INCHES_TO_MM * mMarginVertical;
188+
break;
189+
}
209190

210-
//unrotate the canvas again
211-
context.painter()->restore();
191+
case QgsUnitTypes::RenderPixels:
192+
xOffset = mMarginHorizontal - 5; // Minus 5 to shift tight into corner
193+
yOffset = mMarginVertical - 5;
194+
break;
195+
196+
case QgsUnitTypes::RenderPercentage:
197+
xOffset = ( ( deviceWidth - size.width() ) / 100. ) * mMarginHorizontal;
198+
yOffset = ( ( deviceHeight - size.width() ) / 100. ) * mMarginVertical;
199+
break;
200+
case QgsUnitTypes::RenderMapUnits:
201+
case QgsUnitTypes::RenderPoints:
202+
case QgsUnitTypes::RenderInches:
203+
case QgsUnitTypes::RenderUnknownUnit:
204+
case QgsUnitTypes::RenderMetersInMapUnits:
205+
break;
212206
}
213-
else
207+
//Determine placement of label from form combo box
208+
switch ( mPlacement )
214209
{
215-
QFont myQFont( QStringLiteral( "time" ), 12, QFont::Bold );
216-
context.painter()->setFont( myQFont );
217-
context.painter()->setPen( Qt::black );
218-
context.painter()->drawText( 10, 20, tr( "North arrow pixmap not found" ) );
210+
case BottomLeft:
211+
context.painter()->translate( xOffset, deviceHeight - yOffset - maxLength + ( maxLength - size.height() ) / 2 );
212+
break;
213+
case TopLeft:
214+
context.painter()->translate( xOffset, yOffset );
215+
break;
216+
case TopRight:
217+
context.painter()->translate( deviceWidth - xOffset - maxLength + ( maxLength - size.width() ) / 2, yOffset );
218+
break;
219+
case BottomRight:
220+
context.painter()->translate( deviceWidth - xOffset - maxLength + ( maxLength - size.width() ) / 2,
221+
deviceHeight - yOffset - maxLength + ( maxLength - size.height() ) / 2 );
222+
break;
219223
}
224+
225+
//rotate the canvas by the north arrow rotation amount
226+
context.painter()->rotate( mRotationInt );
227+
//Now we can actually do the drawing, and draw a smooth north arrow even when rotated
228+
context.painter()->translate( xShift, yShift );
229+
svg.render( context.painter(), QRectF( 0, 0, size.width(), size.height() ) );
230+
231+
//unrotate the canvas again
232+
context.painter()->restore();
220233
}
221234
}

src/app/qgsdecorationnortharrow.h

+10-3
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ class APP_EXPORT QgsDecorationNorthArrow: public QgsDecorationItem
3737
QgsDecorationNorthArrow( QObject *parent = nullptr );
3838

3939
public slots:
40-
//! set values on the gui when a project is read or the gui first loaded
40+
//! Set values on the gui when a project is read or the gui first loaded
4141
void projectRead() override;
42-
//! save values to the project
42+
//! Save values to the project
4343
void saveToProject() override;
4444

4545
//! Show the dialog box
4646
void run() override;
47-
//! draw some arbitrary text to the screen
47+
//! Draw some arbitrary text to the screen
4848
void render( const QgsMapSettings &mapSettings, QgsRenderContext &context ) override;
4949

50+
//! Return the north arrow SVG path
51+
QString svgPath();
52+
5053
private:
5154

5255
// static const double DEG2RAD;
@@ -56,6 +59,10 @@ class APP_EXPORT QgsDecorationNorthArrow: public QgsDecorationItem
5659
QColor mColor;
5760
//! The north arrow outline color
5861
QColor mOutlineColor;
62+
//! The north arrow size in millimeter
63+
double mSize = 16.0;
64+
//! Custom north arrow svg path
65+
QString mSvgPath;
5966

6067
// The amount of rotation for the north arrow
6168
int mRotationInt = 0;

0 commit comments

Comments
 (0)