Skip to content

Commit

Permalink
[composer] Improvements to grid annotation string formatting (sponsored
Browse files Browse the repository at this point in the history
by NIWA, New Zealand):
- Don't show directional suffix for 0 or 180 latitudes or 180 longitudes
- Add padded coordinate modes
- Fix precision errors causing minutes/seconds > 60
- Wraparound longitudes to restrict them to the -180 to 180 degree range
  • Loading branch information
nyalldawson committed Sep 3, 2014
1 parent 8c091bb commit f68b258
Show file tree
Hide file tree
Showing 7 changed files with 693 additions and 45 deletions.
14 changes: 12 additions & 2 deletions python/core/qgspoint.sip
Expand Up @@ -57,16 +57,26 @@ class QgsPoint
/** Return a string representation as degrees minutes seconds.
* Its up to the calling function to ensure that this point can
* be meaningfully represented in this form.
* @param thePrecision number of decimal points to use for seconds
* @param useSuffix set to true to include a direction suffix (eg 'N'),
* set to false to use a "-" prefix for west and south coordinates
* @param padded set to true to force minutes and seconds to use two decimals,
* eg, '05' instead of '5'.
* @note added in QGIS 1.4
*/
QString toDegreesMinutesSeconds( int thePrecision ) const;
QString toDegreesMinutesSeconds( int thePrecision, const bool useSuffix = true, const bool padded = false ) const;

/** Return a string representation as degrees minutes.
* Its up to the calling function to ensure that this point can
* be meaningfully represented in this form.
* @param thePrecision number of decimal points to use for minutes
* @param useSuffix set to true to include a direction suffix (eg 'N'),
* set to false to use a "-" prefix for west and south coordinates
* @param padded set to true to force minutes to use two decimals,
* eg, '05' instead of '5'.
* @note added in QGIS 1.9
*/
QString toDegreesMinutes( int thePrecision ) const;
QString toDegreesMinutes( int thePrecision, const bool useSuffix = true, const bool padded = false ) const;


/*! Return the well known text representation for the point.
Expand Down
69 changes: 64 additions & 5 deletions src/app/composer/qgscomposermapwidget.cpp
Expand Up @@ -66,8 +66,14 @@ QgsComposerMapWidget::QgsComposerMapWidget( QgsComposerMap* composerMap ): QgsCo
mGridTypeComboBox->insertItem( 3, tr( "Frame and annotations only" ) );

mAnnotationFormatComboBox->insertItem( 0, tr( "Decimal" ) );
mAnnotationFormatComboBox->insertItem( 1, tr( "DegreeMinute" ) );
mAnnotationFormatComboBox->insertItem( 2, tr( "DegreeMinuteSecond" ) );
mAnnotationFormatComboBox->insertItem( 1, tr( "Decimal with suffix" ) );
mAnnotationFormatComboBox->insertItem( 2, tr( "Degree, minute" ) );
mAnnotationFormatComboBox->insertItem( 3, tr( "Degree, minute with suffix" ) );
mAnnotationFormatComboBox->insertItem( 4, tr( "Degree, minute aligned" ) );
mAnnotationFormatComboBox->insertItem( 5, tr( "Degree, minute, second" ) );
mAnnotationFormatComboBox->insertItem( 6, tr( "Degree, minute, second with suffix" ) );
mAnnotationFormatComboBox->insertItem( 7, tr( "Degree, minute, second aligned" ) );


mAnnotationFontColorButton->setColorDialogTitle( tr( "Select font color" ) );
mAnnotationFontColorButton->setColorDialogOptions( QColorDialog::ShowAlphaChannel );
Expand Down Expand Up @@ -1265,8 +1271,33 @@ void QgsComposerMapWidget::setGridItems( const QgsComposerMapGrid* grid )
mAnnotationFontColorButton->setColor( grid->gridAnnotationFontColor() );

//mAnnotationFormatComboBox
QgsComposerMap::GridAnnotationFormat gf = grid->gridAnnotationFormat();
mAnnotationFormatComboBox->setCurrentIndex(( int )gf );
switch ( grid->gridAnnotationFormat() )
{
case QgsComposerMap::Decimal:
mAnnotationFormatComboBox->setCurrentIndex( 0 );
break;
case QgsComposerMap::DegreeMinute:
mAnnotationFormatComboBox->setCurrentIndex( 3 );
break;
case QgsComposerMap::DegreeMinuteSecond:
mAnnotationFormatComboBox->setCurrentIndex( 6 );
break;
case QgsComposerMap::DecimalWithSuffix:
mAnnotationFormatComboBox->setCurrentIndex( 1 );
break;
case QgsComposerMap::DegreeMinuteNoSuffix:
mAnnotationFormatComboBox->setCurrentIndex( 2 );
break;
case QgsComposerMap::DegreeMinutePadded:
mAnnotationFormatComboBox->setCurrentIndex( 4 );
break;
case QgsComposerMap::DegreeMinuteSecondNoSuffix:
mAnnotationFormatComboBox->setCurrentIndex( 5 );
break;
case QgsComposerMap::DegreeMinuteSecondPadded:
mAnnotationFormatComboBox->setCurrentIndex( 7 );
break;
}
mDistanceToMapFrameSpinBox->setValue( grid->annotationFrameDistance() );
mCoordinatePrecisionSpinBox->setValue( grid->gridAnnotationPrecision() );

Expand Down Expand Up @@ -1851,7 +1882,35 @@ void QgsComposerMapWidget::on_mAnnotationFormatComboBox_currentIndexChanged( int
}

mComposerMap->beginCommand( tr( "Annotation format changed" ) );
grid->setGridAnnotationFormat(( QgsComposerMap::GridAnnotationFormat )index );

switch ( index )
{
case 0:
grid->setGridAnnotationFormat( QgsComposerMap::Decimal );
break;
case 3:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinute );
break;
case 6:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecond );
break;
case 1:
grid->setGridAnnotationFormat( QgsComposerMap::DecimalWithSuffix );
break;
case 2:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteNoSuffix );
break;
case 4:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinutePadded );
break;
case 5:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecondNoSuffix );
break;
case 7:
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecondPadded );
break;
}

mComposerMap->updateBoundingRect();
mComposerMap->update();
mComposerMap->endCommand();
Expand Down
7 changes: 6 additions & 1 deletion src/core/composer/qgscomposermap.h
Expand Up @@ -90,7 +90,12 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
{
Decimal = 0,
DegreeMinute,
DegreeMinuteSecond
DegreeMinuteSecond,
DecimalWithSuffix,
DegreeMinuteNoSuffix,
DegreeMinutePadded,
DegreeMinuteSecondNoSuffix,
DegreeMinuteSecondPadded
};

enum GridFrameStyle
Expand Down
31 changes: 30 additions & 1 deletion src/core/composer/qgscomposermapgrid.cpp
Expand Up @@ -1029,6 +1029,19 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMap::
{
return QString::number( value, 'f', mGridAnnotationPrecision );
}
else if ( mGridAnnotationFormat == QgsComposerMap::DecimalWithSuffix )
{
QString hemisphere;
if ( coord == QgsComposerMap::Longitude )
{
hemisphere = value < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
}
else
{
hemisphere = value < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
return QString::number( qAbs( value ), 'f', mGridAnnotationPrecision ) + hemisphere;
}

QgsPoint p;
p.setX( coord == QgsComposerMap::Longitude ? value : 0 );
Expand All @@ -1039,10 +1052,26 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMap::
{
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision );
}
else //DegreeMinuteSecond
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteNoSuffix )
{
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision, false );
}
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinutePadded )
{
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision, true, true );
}
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecond )
{
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision );
}
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecondNoSuffix )
{
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision, false );
}
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecondPadded )
{
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision, true, true );
}

QStringList split = annotationString.split( "," );
if ( coord == QgsComposerMap::Longitude )
Expand Down
190 changes: 166 additions & 24 deletions src/core/qgspoint.cpp
Expand Up @@ -134,46 +134,188 @@ QString QgsPoint::toString( int thePrecision ) const
return QString( "%1,%2" ).arg( x ).arg( y );
}

QString QgsPoint::toDegreesMinutesSeconds( int thePrecision ) const
QString QgsPoint::toDegreesMinutesSeconds( int thePrecision, const bool useSuffix, const bool padded ) const
{
int myDegreesX = int( qAbs( m_x ) );
float myFloatMinutesX = float(( qAbs( m_x ) - myDegreesX ) * 60 );
//first, limit longitude to -360 to 360 degree range
double myWrappedX = fmod( m_x, 360.0 );
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
if ( myWrappedX > 180.0 )
{
myWrappedX = myWrappedX - 360.0;
}
else if ( myWrappedX < -180.0 )
{
myWrappedX = myWrappedX + 360.0;
}

int myDegreesX = int( qAbs( myWrappedX ) );
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );
int myIntMinutesX = int( myFloatMinutesX );
float mySecondsX = float( myFloatMinutesX - myIntMinutesX ) * 60;
double mySecondsX = double( myFloatMinutesX - myIntMinutesX ) * 60;

int myDegreesY = int( qAbs( m_y ) );
float myFloatMinutesY = float(( qAbs( m_y ) - myDegreesY ) * 60 );
double myFloatMinutesY = double(( qAbs( m_y ) - myDegreesY ) * 60 );
int myIntMinutesY = int( myFloatMinutesY );
float mySecondsY = float( myFloatMinutesY - myIntMinutesY ) * 60;
double mySecondsY = double( myFloatMinutesY - myIntMinutesY ) * 60;

QString myXHemisphere = m_x < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
QString myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
QString rep = QString::number( myDegreesX ) + QChar( 176 ) +
QString::number( myIntMinutesX ) + QString( "'" ) +
QString::number( mySecondsX, 'f', thePrecision ) + QString( "\"" ) +
//make sure rounding to specified precision doesn't create seconds >= 60
if ( qRound( mySecondsX * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
{
mySecondsX = qMax( mySecondsX - 60, 0.0 );
myIntMinutesX++;
if ( myIntMinutesX >= 60 )
{
myIntMinutesX -= 60;
myDegreesX++;
}
}
if ( qRound( mySecondsY * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
{
mySecondsY = qMax( mySecondsY - 60, 0.0 );
myIntMinutesY++;
if ( myIntMinutesY >= 60 )
{
myIntMinutesY -= 60;
myDegreesY++;
}
}

QString myXHemisphere;
QString myYHemisphere;
QString myXSign;
QString myYSign;
if ( useSuffix )
{
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
else
{
if ( myWrappedX < 0 )
{
myXSign = QObject::tr( "-" );
}
if ( m_y < 0 )
{
myYSign = QObject::tr( "-" );
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( myDegreesX == 0 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10, thePrecision ) ) == 0 )
{
myXSign = QString();
myXHemisphere = QString();
}
if ( myDegreesY == 0 && myIntMinutesY == 0 && qRound( mySecondsY * pow( 10, thePrecision ) ) == 0 )
{
myYSign = QString();
myYHemisphere = QString();
}
//also remove directional prefix from 180 degree longitudes
if ( myDegreesX == 180 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10, thePrecision ) ) == 0 )
{
myXHemisphere = QString();
}
//pad minutes with leading digits if required
QString myMinutesX = padded ? QString( "%1" ).arg( myIntMinutesX, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesX );
QString myMinutesY = padded ? QString( "%1" ).arg( myIntMinutesY, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesY );
//pad seconds with leading digits if required
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
QString myStrSecondsX = padded ? QString( "%1" ).arg( mySecondsX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsX, 'f', thePrecision );
QString myStrSecondsY = padded ? QString( "%1" ).arg( mySecondsY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsY, 'f', thePrecision );

QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
myMinutesX + QString( "'" ) +
myStrSecondsX + QString( "\"" ) +
myXHemisphere + QString( "," ) +
QString::number( myDegreesY ) + QChar( 176 ) +
QString::number( myIntMinutesY ) + QString( "'" ) +
QString::number( mySecondsY, 'f', thePrecision ) + QString( "\"" ) +
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
myMinutesY + QString( "'" ) +
myStrSecondsY + QString( "\"" ) +
myYHemisphere;
return rep;
}

QString QgsPoint::toDegreesMinutes( int thePrecision ) const
QString QgsPoint::toDegreesMinutes( int thePrecision, const bool useSuffix, const bool padded ) const
{
int myDegreesX = int( qAbs( m_x ) );
float myFloatMinutesX = float(( qAbs( m_x ) - myDegreesX ) * 60 );
//first, limit longitude to -360 to 360 degree range
double myWrappedX = fmod( m_x, 360.0 );
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
if ( myWrappedX > 180.0 )
{
myWrappedX = myWrappedX - 360.0;
}
else if ( myWrappedX < -180.0 )
{
myWrappedX = myWrappedX + 360.0;
}

int myDegreesX = int( qAbs( myWrappedX ) );
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );

int myDegreesY = int( qAbs( m_y ) );
float myFloatMinutesY = float(( qAbs( m_y ) - myDegreesY ) * 60 );
double myFloatMinutesY = double(( qAbs( m_y ) - myDegreesY ) * 60 );

//make sure rounding to specified precision doesn't create minutes >= 60
if ( qRound( myFloatMinutesX * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
{
myFloatMinutesX = qMax( myFloatMinutesX - 60, 0.0 );
myDegreesX++;
}
if ( qRound( myFloatMinutesY * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
{
myFloatMinutesY = qMax( myFloatMinutesY - 60, 0.0 );
myDegreesY++;
}

QString myXHemisphere;
QString myYHemisphere;
QString myXSign;
QString myYSign;
if ( useSuffix )
{
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
}
else
{
if ( myWrappedX < 0 )
{
myXSign = QObject::tr( "-" );
}
if ( m_y < 0 )
{
myYSign = QObject::tr( "-" );
}
}
//check if coordinate is all zeros for the specified precision, and if so,
//remove the sign and hemisphere strings
if ( myDegreesX == 0 && qRound( myFloatMinutesX * pow( 10, thePrecision ) ) == 0 )
{
myXSign = QString();
myXHemisphere = QString();
}
if ( myDegreesY == 0 && qRound( myFloatMinutesY * pow( 10, thePrecision ) ) == 0 )
{
myYSign = QString();
myYHemisphere = QString();
}
//also remove directional prefix from 180 degree longitudes
if ( myDegreesX == 180 && qRound( myFloatMinutesX * pow( 10, thePrecision ) ) == 0 )
{
myXHemisphere = QString();
}

//pad minutes with leading digits if required
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
QString myStrMinutesX = padded ? QString( "%1" ).arg( myFloatMinutesX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesX, 'f', thePrecision );
QString myStrMinutesY = padded ? QString( "%1" ).arg( myFloatMinutesY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesY, 'f', thePrecision );

QString myXHemisphere = m_x < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
QString myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
QString rep = QString::number( myDegreesX ) + QChar( 176 ) +
QString::number( myFloatMinutesX, 'f', thePrecision ) + QString( "'" ) +
QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
myStrMinutesX + QString( "'" ) +
myXHemisphere + QString( "," ) +
QString::number( myDegreesY ) + QChar( 176 ) +
QString::number( myFloatMinutesY, 'f', thePrecision ) + QString( "'" ) +
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
myStrMinutesY + QString( "'" ) +
myYHemisphere;
return rep;
}
Expand Down

0 comments on commit f68b258

Please sign in to comment.