Skip to content

Commit 36d3fef

Browse files
authored
Merge pull request #3623 from nyalldawson/true_north_214
[composer] Backport fixes for true north handling to 2.14
2 parents b2a2c18 + d78d62e commit 36d3fef

18 files changed

+478
-10
lines changed

python/core/composer/qgscomposerpicture.sip

+39
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ class QgsComposerPicture: QgsComposerItem
3030
Unknown
3131
};
3232

33+
//! Method for syncing rotation to a map's North direction
34+
enum NorthMode
35+
{
36+
GridNorth, /*!< Align to grid north */
37+
TrueNorth, /*!< Align to true north */
38+
};
39+
3340
QgsComposerPicture( QgsComposition *composition /TransferThis/);
3441
~QgsComposerPicture();
3542

@@ -132,6 +139,38 @@ class QgsComposerPicture: QgsComposerItem
132139
*/
133140
bool useRotationMap() const;
134141

142+
/**
143+
* Returns the mode used to align the picture to a map's North.
144+
* @see setNorthMode()
145+
* @see northOffset()
146+
* @note added in QGIS 2.18
147+
*/
148+
NorthMode northMode() const;
149+
150+
/**
151+
* Sets the mode used to align the picture to a map's North.
152+
* @see northMode()
153+
* @see setNorthOffset()
154+
* @note added in QGIS 2.18
155+
*/
156+
void setNorthMode( NorthMode mode );
157+
158+
/**
159+
* Returns the offset added to the picture's rotation from a map's North.
160+
* @see setNorthOffset()
161+
* @see northMode()
162+
* @note added in QGIS 2.18
163+
*/
164+
double northOffset() const;
165+
166+
/**
167+
* Sets the offset added to the picture's rotation from a map's North.
168+
* @see northOffset()
169+
* @see setNorthMode()
170+
* @note added in QGIS 2.18
171+
*/
172+
void setNorthOffset( double offset );
173+
135174
/** Returns the resize mode used for drawing the picture within the composer
136175
* item's frame.
137176
* @returns resize mode of picture

python/core/core.sip

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
%Include qgsannotation.sip
2222
%Include qgsapplication.sip
2323
%Include qgsattributeaction.sip
24+
%Include qgsbearingutils.sip
2425
%Include qgsbrowsermodel.sip
2526
%Include qgsclipper.sip
2627
%Include qgscolorscheme.sip

python/core/qgsbearingutils.sip

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* \class QgsBearingUtils
3+
* \ingroup core
4+
* Utilities for calculating bearings and directions.
5+
* \note Added in version 2.18
6+
*/
7+
class QgsBearingUtils
8+
{
9+
%TypeHeaderCode
10+
#include <qgsbearingutils.h>
11+
%End
12+
public:
13+
14+
/**
15+
* Returns the direction to true north from a specified point and for a specified
16+
* coordinate reference system. The returned value is in degrees clockwise from
17+
* vertical. An exception will be thrown if the bearing could not be calculated.
18+
*/
19+
static double bearingTrueNorth( const QgsCoordinateReferenceSystem& crs,
20+
const QgsPoint& point );
21+
};

python/core/qgspoint.sip

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class QgsPoint
148148
double sqrDistToSegment( double x1, double y1, double x2, double y2, QgsPoint& minDistPoint /Out/, double epsilon = DEFAULT_SEGMENT_EPSILON ) const;
149149

150150
/** Calculates azimuth between this point and other one (clockwise in degree, starting from north) */
151-
double azimuth( const QgsPoint& other );
151+
double azimuth( const QgsPoint& other ) const;
152152

153153
/** Compares this point with another point with a fuzzy tolerance
154154
* @param other point to compare with

src/app/composer/qgscomposerpicturewidget.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ QgsComposerPictureWidget::QgsComposerPictureWidget( QgsComposerPicture* picture
4444
mOutlineColorButton->setColorDialogTitle( tr( "Select outline color" ) );
4545
mOutlineColorButton->setContext( "composer" );
4646

47+
mNorthTypeComboBox->blockSignals( true );
48+
mNorthTypeComboBox->addItem( tr( "Grid north" ), QgsComposerPicture::GridNorth );
49+
mNorthTypeComboBox->addItem( tr( "True north" ), QgsComposerPicture::TrueNorth );
50+
mNorthTypeComboBox->blockSignals( false );
51+
mPictureRotationOffsetSpinBox->setClearValue( 0.0 );
52+
mPictureRotationSpinBox->setClearValue( 0.0 );
53+
4754
//add widget for general composer item properties
4855
QgsComposerItemWidget* itemPropertiesWidget = new QgsComposerItemWidget( this, picture );
4956
mainLayout->addWidget( itemPropertiesWidget );
@@ -273,6 +280,8 @@ void QgsComposerPictureWidget::on_mRotationFromComposerMapCheckBox_stateChanged(
273280
mPicture->setRotationMap( -1 );
274281
mPictureRotationSpinBox->setEnabled( true );
275282
mComposerMapComboBox->setEnabled( false );
283+
mNorthTypeComboBox->setEnabled( false );
284+
mPictureRotationOffsetSpinBox->setEnabled( false );
276285
mPicture->setPictureRotation( mPictureRotationSpinBox->value() );
277286
}
278287
else
@@ -286,6 +295,8 @@ void QgsComposerPictureWidget::on_mRotationFromComposerMapCheckBox_stateChanged(
286295

287296
mPicture->setRotationMap( composerId );
288297
mPictureRotationSpinBox->setEnabled( false );
298+
mNorthTypeComboBox->setEnabled( true );
299+
mPictureRotationOffsetSpinBox->setEnabled( true );
289300
mComposerMapComboBox->setEnabled( true );
290301
}
291302
mPicture->endCommand();
@@ -388,6 +399,8 @@ void QgsComposerPictureWidget::setGuiElementValues()
388399
mPictureLineEdit->blockSignals( true );
389400
mComposerMapComboBox->blockSignals( true );
390401
mRotationFromComposerMapCheckBox->blockSignals( true );
402+
mNorthTypeComboBox->blockSignals( true );
403+
mPictureRotationOffsetSpinBox->blockSignals( true );
391404
mResizeModeComboBox->blockSignals( true );
392405
mAnchorPointComboBox->blockSignals( true );
393406
mFillColorButton->blockSignals( true );
@@ -410,13 +423,19 @@ void QgsComposerPictureWidget::setGuiElementValues()
410423
{
411424
mComposerMapComboBox->setCurrentIndex( itemId );
412425
}
426+
mNorthTypeComboBox->setEnabled( true );
427+
mPictureRotationOffsetSpinBox->setEnabled( true );
413428
}
414429
else
415430
{
416431
mRotationFromComposerMapCheckBox->setCheckState( Qt::Unchecked );
417432
mPictureRotationSpinBox->setEnabled( true );
418433
mComposerMapComboBox->setEnabled( false );
434+
mNorthTypeComboBox->setEnabled( false );
435+
mPictureRotationOffsetSpinBox->setEnabled( false );
419436
}
437+
mNorthTypeComboBox->setCurrentIndex( mNorthTypeComboBox->findData( mPicture->northMode() ) );
438+
mPictureRotationOffsetSpinBox->setValue( mPicture->northOffset() );
420439

421440
mResizeModeComboBox->setCurrentIndex(( int )mPicture->resizeMode() );
422441
//disable picture rotation for non-zoom modes
@@ -444,6 +463,8 @@ void QgsComposerPictureWidget::setGuiElementValues()
444463
mPictureRotationSpinBox->blockSignals( false );
445464
mPictureLineEdit->blockSignals( false );
446465
mComposerMapComboBox->blockSignals( false );
466+
mNorthTypeComboBox->blockSignals( false );
467+
mPictureRotationOffsetSpinBox->blockSignals( false );
447468
mResizeModeComboBox->blockSignals( false );
448469
mAnchorPointComboBox->blockSignals( false );
449470
mFillColorButton->blockSignals( false );
@@ -726,6 +747,22 @@ void QgsComposerPictureWidget::showEvent( QShowEvent * event )
726747
refreshMapComboBox();
727748
}
728749

750+
void QgsComposerPictureWidget::on_mPictureRotationOffsetSpinBox_valueChanged( double d )
751+
{
752+
mPicture->beginCommand( tr( "Picture North offset changed" ), QgsComposerMergeCommand::ComposerPictureNorthOffset );
753+
mPicture->setNorthOffset( d );
754+
mPicture->endCommand();
755+
mPicture->update();
756+
}
757+
758+
void QgsComposerPictureWidget::on_mNorthTypeComboBox_currentIndexChanged( int index )
759+
{
760+
mPicture->beginCommand( tr( "Picture North mode changed" ) );
761+
mPicture->setNorthMode( static_cast< QgsComposerPicture::NorthMode >( mNorthTypeComboBox->itemData( index ).toInt() ) );
762+
mPicture->endCommand();
763+
mPicture->update();
764+
}
765+
729766
void QgsComposerPictureWidget::resizeEvent( QResizeEvent * event )
730767
{
731768
Q_UNUSED( event );

src/app/composer/qgscomposerpicturewidget.h

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ class QgsComposerPictureWidget: public QgsComposerItemBaseWidget, private Ui::Qg
7373
void on_mFillColorButton_colorChanged( const QColor& color );
7474
void on_mOutlineColorButton_colorChanged( const QColor& color );
7575
void on_mOutlineWidthSpinBox_valueChanged( double d );
76+
void on_mPictureRotationOffsetSpinBox_valueChanged( double d );
77+
void on_mNorthTypeComboBox_currentIndexChanged( int index );
7678

7779
private:
7880
QgsComposerPicture* mPicture;

src/core/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ SET(QGIS_CORE_SRCS
7676
qgis.cpp
7777
qgsapplication.cpp
7878
qgsattributeaction.cpp
79+
qgsbearingutils.cpp
7980
qgsbrowsermodel.cpp
8081
qgscachedfeatureiterator.cpp
8182
qgscacheindex.cpp
@@ -583,6 +584,7 @@ SET(QGIS_CORE_HDRS
583584
qgis.h
584585
qgsannotation.h
585586
qgsattributeaction.h
587+
qgsbearingutils.h
586588
qgscachedfeatureiterator.h
587589
qgscacheindex.h
588590
qgscacheindexfeatureid.h

src/core/composer/qgscomposeritemcommand.h

+1
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ class CORE_EXPORT QgsComposerMergeCommand: public QgsComposerItemCommand
105105
LegendRasterBorderWidth,
106106
//composer picture
107107
ComposerPictureRotation,
108+
ComposerPictureNorthOffset,
108109
// composer scalebar
109110
ScaleBarLineWidth,
110111
ScaleBarHeight,

src/core/composer/qgscomposerpicture.cpp

+73-6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
#include "qgsnetworkcontentfetcher.h"
2929
#include "qgssymbollayerv2utils.h"
3030
#include "qgssvgcache.h"
31+
#include "qgslogger.h"
32+
#include "qgsbearingutils.h"
33+
#include "qgsmapsettings.h"
34+
3135
#include <QDomDocument>
3236
#include <QDomElement>
3337
#include <QFileInfo>
@@ -44,6 +48,8 @@ QgsComposerPicture::QgsComposerPicture( QgsComposition *composition )
4448
, mMode( Unknown )
4549
, mPictureRotation( 0 )
4650
, mRotationMap( nullptr )
51+
, mNorthMode( GridNorth )
52+
, mNorthOffset( 0.0 )
4753
, mResizeMode( QgsComposerPicture::Zoom )
4854
, mPictureAnchor( UpperLeft )
4955
, mSvgFillColor( QColor( 255, 255, 255 ) )
@@ -61,6 +67,8 @@ QgsComposerPicture::QgsComposerPicture()
6167
, mMode( Unknown )
6268
, mPictureRotation( 0 )
6369
, mRotationMap( nullptr )
70+
, mNorthMode( GridNorth )
71+
, mNorthOffset( 0.0 )
6472
, mResizeMode( QgsComposerPicture::Zoom )
6573
, mPictureAnchor( UpperLeft )
6674
, mSvgFillColor( QColor( 255, 255, 255 ) )
@@ -419,6 +427,43 @@ void QgsComposerPicture::remotePictureLoaded()
419427
mLoaded = true;
420428
}
421429

430+
void QgsComposerPicture::updateMapRotation()
431+
{
432+
if ( !mRotationMap )
433+
return;
434+
435+
// take map rotation
436+
double rotation = mRotationMap->mapRotation();
437+
438+
// handle true north
439+
switch ( mNorthMode )
440+
{
441+
case GridNorth:
442+
break; // nothing to do
443+
444+
case TrueNorth:
445+
{
446+
QgsPoint center = mRotationMap->currentMapExtent()->center();
447+
QgsCoordinateReferenceSystem crs = mComposition->mapSettings().destinationCrs();
448+
449+
try
450+
{
451+
double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
452+
rotation += bearing;
453+
}
454+
catch ( QgsException& e )
455+
{
456+
Q_UNUSED( e );
457+
QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
458+
}
459+
break;
460+
}
461+
}
462+
463+
rotation += mNorthOffset;
464+
setPictureRotation( rotation );
465+
}
466+
422467
void QgsComposerPicture::loadPicture( const QString &path )
423468
{
424469
if ( path.startsWith( "http" ) )
@@ -650,7 +695,8 @@ void QgsComposerPicture::setRotationMap( int composerMapId )
650695

651696
if ( composerMapId == -1 ) //disable rotation from map
652697
{
653-
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
698+
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
699+
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
654700
mRotationMap = nullptr;
655701
}
656702

@@ -661,12 +707,14 @@ void QgsComposerPicture::setRotationMap( int composerMapId )
661707
}
662708
if ( mRotationMap )
663709
{
664-
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
710+
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
711+
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
665712
}
666713
mPictureRotation = map->mapRotation();
667-
QObject::connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
714+
connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
715+
connect( map, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
668716
mRotationMap = map;
669-
update();
717+
updateMapRotation();
670718
emit pictureRotationChanged( mPictureRotation );
671719
}
672720

@@ -761,6 +809,8 @@ bool QgsComposerPicture::writeXML( QDomElement& elem, QDomDocument & doc ) const
761809
{
762810
composerPictureElem.setAttribute( "mapId", mRotationMap->id() );
763811
}
812+
composerPictureElem.setAttribute( "northMode", mNorthMode );
813+
composerPictureElem.setAttribute( "northOffset", mNorthOffset );
764814

765815
_writeXML( composerPictureElem, doc );
766816
elem.appendChild( composerPictureElem );
@@ -827,6 +877,9 @@ bool QgsComposerPicture::readXML( const QDomElement& itemElem, const QDomDocumen
827877
}
828878

829879
//rotation map
880+
mNorthMode = static_cast< NorthMode >( itemElem.attribute( "northMode", "0" ).toInt() );
881+
mNorthOffset = itemElem.attribute( "northOffset", "0" ).toDouble();
882+
830883
int rotationMapId = itemElem.attribute( "mapId", "-1" ).toInt();
831884
if ( rotationMapId == -1 )
832885
{
@@ -837,10 +890,12 @@ bool QgsComposerPicture::readXML( const QDomElement& itemElem, const QDomDocumen
837890

838891
if ( mRotationMap )
839892
{
840-
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setRotation( double ) ) );
893+
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
894+
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
841895
}
842896
mRotationMap = mComposition->getComposerMapById( rotationMapId );
843-
QObject::connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setRotation( double ) ) );
897+
connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
898+
connect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
844899
}
845900

846901
refreshPicture();
@@ -861,6 +916,18 @@ int QgsComposerPicture::rotationMap() const
861916
}
862917
}
863918

919+
void QgsComposerPicture::setNorthMode( QgsComposerPicture::NorthMode mode )
920+
{
921+
mNorthMode = mode;
922+
updateMapRotation();
923+
}
924+
925+
void QgsComposerPicture::setNorthOffset( double offset )
926+
{
927+
mNorthOffset = offset;
928+
updateMapRotation();
929+
}
930+
864931
void QgsComposerPicture::setPictureAnchor( QgsComposerItem::ItemPositionMode anchor )
865932
{
866933
mPictureAnchor = anchor;

0 commit comments

Comments
 (0)