Skip to content

Commit

Permalink
[composer] Allow syncing pictures to true north
Browse files Browse the repository at this point in the history
Previously pictures could only be synced to grid north, which
can be totally wrong for many CRSes (especially in polar areas)

Users now are given a choice of grid or true north, and can also
enter an optional offset to apply if eg magnetic north is instead
desired.

When synced to true north the bearing is calculated using the
centre point of the linked map item.

Fix #192, #4711

This fix was sponsored by the Norwegian Polar Institute's
Quantarctica project (http://quantarctica.npolar.no) and
coordinated by Faunalia.

(cherry-picked from 89cc645)
  • Loading branch information
nyalldawson committed Oct 18, 2016
1 parent 71bfa1d commit 13afc10
Show file tree
Hide file tree
Showing 15 changed files with 475 additions and 7 deletions.
39 changes: 39 additions & 0 deletions python/core/composer/qgscomposerpicture.sip
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ class QgsComposerPicture: QgsComposerItem
Unknown
};

//! Method for syncing rotation to a map's North direction
enum NorthMode
{
GridNorth, /*!< Align to grid north */
TrueNorth, /*!< Align to true north */
};

QgsComposerPicture( QgsComposition *composition /TransferThis/);
~QgsComposerPicture();

Expand Down Expand Up @@ -132,6 +139,38 @@ class QgsComposerPicture: QgsComposerItem
*/
bool useRotationMap() const;

/**
* Returns the mode used to align the picture to a map's North.
* @see setNorthMode()
* @see northOffset()
* @note added in QGIS 2.18
*/
NorthMode northMode() const;

/**
* Sets the mode used to align the picture to a map's North.
* @see northMode()
* @see setNorthOffset()
* @note added in QGIS 2.18
*/
void setNorthMode( NorthMode mode );

/**
* Returns the offset added to the picture's rotation from a map's North.
* @see setNorthOffset()
* @see northMode()
* @note added in QGIS 2.18
*/
double northOffset() const;

/**
* Sets the offset added to the picture's rotation from a map's North.
* @see northOffset()
* @see setNorthMode()
* @note added in QGIS 2.18
*/
void setNorthOffset( double offset );

/** Returns the resize mode used for drawing the picture within the composer
* item's frame.
* @returns resize mode of picture
Expand Down
1 change: 1 addition & 0 deletions python/core/core.sip
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
%Include qgsannotation.sip
%Include qgsapplication.sip
%Include qgsattributeaction.sip
%Include qgsbearingutils.sip
%Include qgsbrowsermodel.sip
%Include qgsclipper.sip
%Include qgscolorscheme.sip
Expand Down
21 changes: 21 additions & 0 deletions python/core/qgsbearingutils.sip
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* \class QgsBearingUtils
* \ingroup core
* Utilities for calculating bearings and directions.
* \note Added in version 2.18
*/
class QgsBearingUtils
{
%TypeHeaderCode
#include <qgsbearingutils.h>
%End
public:

/**
* Returns the direction to true north from a specified point and for a specified
* coordinate reference system. The returned value is in degrees clockwise from
* vertical. An exception will be thrown if the bearing could not be calculated.
*/
static double bearingTrueNorth( const QgsCoordinateReferenceSystem& crs,
const QgsPoint& point );
};
37 changes: 37 additions & 0 deletions src/app/composer/qgscomposerpicturewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ QgsComposerPictureWidget::QgsComposerPictureWidget( QgsComposerPicture* picture
mOutlineColorButton->setColorDialogTitle( tr( "Select outline color" ) );
mOutlineColorButton->setContext( "composer" );

mNorthTypeComboBox->blockSignals( true );
mNorthTypeComboBox->addItem( tr( "Grid north" ), QgsComposerPicture::GridNorth );
mNorthTypeComboBox->addItem( tr( "True north" ), QgsComposerPicture::TrueNorth );
mNorthTypeComboBox->blockSignals( false );
mPictureRotationOffsetSpinBox->setClearValue( 0.0 );
mPictureRotationSpinBox->setClearValue( 0.0 );

//add widget for general composer item properties
QgsComposerItemWidget* itemPropertiesWidget = new QgsComposerItemWidget( this, picture );
mainLayout->addWidget( itemPropertiesWidget );
Expand Down Expand Up @@ -273,6 +280,8 @@ void QgsComposerPictureWidget::on_mRotationFromComposerMapCheckBox_stateChanged(
mPicture->setRotationMap( -1 );
mPictureRotationSpinBox->setEnabled( true );
mComposerMapComboBox->setEnabled( false );
mNorthTypeComboBox->setEnabled( false );
mPictureRotationOffsetSpinBox->setEnabled( false );
mPicture->setPictureRotation( mPictureRotationSpinBox->value() );
}
else
Expand All @@ -286,6 +295,8 @@ void QgsComposerPictureWidget::on_mRotationFromComposerMapCheckBox_stateChanged(

mPicture->setRotationMap( composerId );
mPictureRotationSpinBox->setEnabled( false );
mNorthTypeComboBox->setEnabled( true );
mPictureRotationOffsetSpinBox->setEnabled( true );
mComposerMapComboBox->setEnabled( true );
}
mPicture->endCommand();
Expand Down Expand Up @@ -388,6 +399,8 @@ void QgsComposerPictureWidget::setGuiElementValues()
mPictureLineEdit->blockSignals( true );
mComposerMapComboBox->blockSignals( true );
mRotationFromComposerMapCheckBox->blockSignals( true );
mNorthTypeComboBox->blockSignals( true );
mPictureRotationOffsetSpinBox->blockSignals( true );
mResizeModeComboBox->blockSignals( true );
mAnchorPointComboBox->blockSignals( true );
mFillColorButton->blockSignals( true );
Expand All @@ -410,13 +423,19 @@ void QgsComposerPictureWidget::setGuiElementValues()
{
mComposerMapComboBox->setCurrentIndex( itemId );
}
mNorthTypeComboBox->setEnabled( true );
mPictureRotationOffsetSpinBox->setEnabled( true );
}
else
{
mRotationFromComposerMapCheckBox->setCheckState( Qt::Unchecked );
mPictureRotationSpinBox->setEnabled( true );
mComposerMapComboBox->setEnabled( false );
mNorthTypeComboBox->setEnabled( false );
mPictureRotationOffsetSpinBox->setEnabled( false );
}
mNorthTypeComboBox->setCurrentIndex( mNorthTypeComboBox->findData( mPicture->northMode() ) );
mPictureRotationOffsetSpinBox->setValue( mPicture->northOffset() );

mResizeModeComboBox->setCurrentIndex(( int )mPicture->resizeMode() );
//disable picture rotation for non-zoom modes
Expand Down Expand Up @@ -444,6 +463,8 @@ void QgsComposerPictureWidget::setGuiElementValues()
mPictureRotationSpinBox->blockSignals( false );
mPictureLineEdit->blockSignals( false );
mComposerMapComboBox->blockSignals( false );
mNorthTypeComboBox->blockSignals( false );
mPictureRotationOffsetSpinBox->blockSignals( false );
mResizeModeComboBox->blockSignals( false );
mAnchorPointComboBox->blockSignals( false );
mFillColorButton->blockSignals( false );
Expand Down Expand Up @@ -726,6 +747,22 @@ void QgsComposerPictureWidget::showEvent( QShowEvent * event )
refreshMapComboBox();
}

void QgsComposerPictureWidget::on_mPictureRotationOffsetSpinBox_valueChanged( double d )
{
mPicture->beginCommand( tr( "Picture North offset changed" ), QgsComposerMergeCommand::ComposerPictureNorthOffset );
mPicture->setNorthOffset( d );
mPicture->endCommand();
mPicture->update();
}

void QgsComposerPictureWidget::on_mNorthTypeComboBox_currentIndexChanged( int index )
{
mPicture->beginCommand( tr( "Picture North mode changed" ) );
mPicture->setNorthMode( static_cast< QgsComposerPicture::NorthMode >( mNorthTypeComboBox->itemData( index ).toInt() ) );
mPicture->endCommand();
mPicture->update();
}

void QgsComposerPictureWidget::resizeEvent( QResizeEvent * event )
{
Q_UNUSED( event );
Expand Down
2 changes: 2 additions & 0 deletions src/app/composer/qgscomposerpicturewidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ class QgsComposerPictureWidget: public QgsComposerItemBaseWidget, private Ui::Qg
void on_mFillColorButton_colorChanged( const QColor& color );
void on_mOutlineColorButton_colorChanged( const QColor& color );
void on_mOutlineWidthSpinBox_valueChanged( double d );
void on_mPictureRotationOffsetSpinBox_valueChanged( double d );
void on_mNorthTypeComboBox_currentIndexChanged( int index );

private:
QgsComposerPicture* mPicture;
Expand Down
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ SET(QGIS_CORE_SRCS
qgis.cpp
qgsapplication.cpp
qgsattributeaction.cpp
qgsbearingutils.cpp
qgsbrowsermodel.cpp
qgscachedfeatureiterator.cpp
qgscacheindex.cpp
Expand Down Expand Up @@ -583,6 +584,7 @@ SET(QGIS_CORE_HDRS
qgis.h
qgsannotation.h
qgsattributeaction.h
qgsbearingutils.h
qgscachedfeatureiterator.h
qgscacheindex.h
qgscacheindexfeatureid.h
Expand Down
1 change: 1 addition & 0 deletions src/core/composer/qgscomposeritemcommand.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class CORE_EXPORT QgsComposerMergeCommand: public QgsComposerItemCommand
LegendRasterBorderWidth,
//composer picture
ComposerPictureRotation,
ComposerPictureNorthOffset,
// composer scalebar
ScaleBarLineWidth,
ScaleBarHeight,
Expand Down
79 changes: 73 additions & 6 deletions src/core/composer/qgscomposerpicture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
#include "qgsnetworkcontentfetcher.h"
#include "qgssymbollayerv2utils.h"
#include "qgssvgcache.h"
#include "qgslogger.h"
#include "qgsbearingutils.h"
#include "qgsmapsettings.h"

#include <QDomDocument>
#include <QDomElement>
#include <QFileInfo>
Expand All @@ -44,6 +48,8 @@ QgsComposerPicture::QgsComposerPicture( QgsComposition *composition )
, mMode( Unknown )
, mPictureRotation( 0 )
, mRotationMap( nullptr )
, mNorthMode( GridNorth )
, mNorthOffset( 0.0 )
, mResizeMode( QgsComposerPicture::Zoom )
, mPictureAnchor( UpperLeft )
, mSvgFillColor( QColor( 255, 255, 255 ) )
Expand All @@ -61,6 +67,8 @@ QgsComposerPicture::QgsComposerPicture()
, mMode( Unknown )
, mPictureRotation( 0 )
, mRotationMap( nullptr )
, mNorthMode( GridNorth )
, mNorthOffset( 0.0 )
, mResizeMode( QgsComposerPicture::Zoom )
, mPictureAnchor( UpperLeft )
, mSvgFillColor( QColor( 255, 255, 255 ) )
Expand Down Expand Up @@ -419,6 +427,43 @@ void QgsComposerPicture::remotePictureLoaded()
mLoaded = true;
}

void QgsComposerPicture::updateMapRotation()
{
if ( !mRotationMap )
return;

// take map rotation
double rotation = mRotationMap->mapRotation();

// handle true north
switch ( mNorthMode )
{
case GridNorth:
break; // nothing to do

case TrueNorth:
{
QgsPoint center = mRotationMap->currentMapExtent()->center();
QgsCoordinateReferenceSystem crs = mComposition->mapSettings().destinationCrs();

try
{
double bearing = QgsBearingUtils::bearingTrueNorth( crs, center );
rotation += bearing;
}
catch ( QgsException& e )
{
Q_UNUSED( e );
QgsDebugMsg( QString( "Caught exception %1" ).arg( e.what() ) );
}
break;
}
}

rotation += mNorthOffset;
setPictureRotation( rotation );
}

void QgsComposerPicture::loadPicture( const QString &path )
{
if ( path.startsWith( "http" ) )
Expand Down Expand Up @@ -650,7 +695,8 @@ void QgsComposerPicture::setRotationMap( int composerMapId )

if ( composerMapId == -1 ) //disable rotation from map
{
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
mRotationMap = nullptr;
}

Expand All @@ -661,12 +707,14 @@ void QgsComposerPicture::setRotationMap( int composerMapId )
}
if ( mRotationMap )
{
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
}
mPictureRotation = map->mapRotation();
QObject::connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setPictureRotation( double ) ) );
connect( map, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
connect( map, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
mRotationMap = map;
update();
updateMapRotation();
emit pictureRotationChanged( mPictureRotation );
}

Expand Down Expand Up @@ -761,6 +809,8 @@ bool QgsComposerPicture::writeXML( QDomElement& elem, QDomDocument & doc ) const
{
composerPictureElem.setAttribute( "mapId", mRotationMap->id() );
}
composerPictureElem.setAttribute( "northMode", mNorthMode );
composerPictureElem.setAttribute( "northOffset", mNorthOffset );

_writeXML( composerPictureElem, doc );
elem.appendChild( composerPictureElem );
Expand Down Expand Up @@ -827,6 +877,9 @@ bool QgsComposerPicture::readXML( const QDomElement& itemElem, const QDomDocumen
}

//rotation map
mNorthMode = static_cast< NorthMode >( itemElem.attribute( "northMode", "0" ).toInt() );
mNorthOffset = itemElem.attribute( "northOffset", "0" ).toDouble();

int rotationMapId = itemElem.attribute( "mapId", "-1" ).toInt();
if ( rotationMapId == -1 )
{
Expand All @@ -837,10 +890,12 @@ bool QgsComposerPicture::readXML( const QDomElement& itemElem, const QDomDocumen

if ( mRotationMap )
{
QObject::disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setRotation( double ) ) );
disconnect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
disconnect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
}
mRotationMap = mComposition->getComposerMapById( rotationMapId );
QObject::connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( setRotation( double ) ) );
connect( mRotationMap, SIGNAL( mapRotationChanged( double ) ), this, SLOT( updateMapRotation() ) );
connect( mRotationMap, SIGNAL( extentChanged() ), this, SLOT( updateMapRotation() ) );
}

refreshPicture();
Expand All @@ -861,6 +916,18 @@ int QgsComposerPicture::rotationMap() const
}
}

void QgsComposerPicture::setNorthMode( QgsComposerPicture::NorthMode mode )
{
mNorthMode = mode;
updateMapRotation();
}

void QgsComposerPicture::setNorthOffset( double offset )
{
mNorthOffset = offset;
updateMapRotation();
}

void QgsComposerPicture::setPictureAnchor( QgsComposerItem::ItemPositionMode anchor )
{
mPictureAnchor = anchor;
Expand Down
Loading

0 comments on commit 13afc10

Please sign in to comment.