Skip to content

Commit f68b258

Browse files
committedSep 3, 2014
[composer] Improvements to grid annotation string formatting (sponsored
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
1 parent 8c091bb commit f68b258

File tree

7 files changed

+693
-45
lines changed

7 files changed

+693
-45
lines changed
 

‎python/core/qgspoint.sip

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,26 @@ class QgsPoint
5757
/** Return a string representation as degrees minutes seconds.
5858
* Its up to the calling function to ensure that this point can
5959
* be meaningfully represented in this form.
60+
* @param thePrecision number of decimal points to use for seconds
61+
* @param useSuffix set to true to include a direction suffix (eg 'N'),
62+
* set to false to use a "-" prefix for west and south coordinates
63+
* @param padded set to true to force minutes and seconds to use two decimals,
64+
* eg, '05' instead of '5'.
6065
* @note added in QGIS 1.4
6166
*/
62-
QString toDegreesMinutesSeconds( int thePrecision ) const;
67+
QString toDegreesMinutesSeconds( int thePrecision, const bool useSuffix = true, const bool padded = false ) const;
6368

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

7181

7282
/*! Return the well known text representation for the point.

‎src/app/composer/qgscomposermapwidget.cpp

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,14 @@ QgsComposerMapWidget::QgsComposerMapWidget( QgsComposerMap* composerMap ): QgsCo
6666
mGridTypeComboBox->insertItem( 3, tr( "Frame and annotations only" ) );
6767

6868
mAnnotationFormatComboBox->insertItem( 0, tr( "Decimal" ) );
69-
mAnnotationFormatComboBox->insertItem( 1, tr( "DegreeMinute" ) );
70-
mAnnotationFormatComboBox->insertItem( 2, tr( "DegreeMinuteSecond" ) );
69+
mAnnotationFormatComboBox->insertItem( 1, tr( "Decimal with suffix" ) );
70+
mAnnotationFormatComboBox->insertItem( 2, tr( "Degree, minute" ) );
71+
mAnnotationFormatComboBox->insertItem( 3, tr( "Degree, minute with suffix" ) );
72+
mAnnotationFormatComboBox->insertItem( 4, tr( "Degree, minute aligned" ) );
73+
mAnnotationFormatComboBox->insertItem( 5, tr( "Degree, minute, second" ) );
74+
mAnnotationFormatComboBox->insertItem( 6, tr( "Degree, minute, second with suffix" ) );
75+
mAnnotationFormatComboBox->insertItem( 7, tr( "Degree, minute, second aligned" ) );
76+
7177

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

12671273
//mAnnotationFormatComboBox
1268-
QgsComposerMap::GridAnnotationFormat gf = grid->gridAnnotationFormat();
1269-
mAnnotationFormatComboBox->setCurrentIndex(( int )gf );
1274+
switch ( grid->gridAnnotationFormat() )
1275+
{
1276+
case QgsComposerMap::Decimal:
1277+
mAnnotationFormatComboBox->setCurrentIndex( 0 );
1278+
break;
1279+
case QgsComposerMap::DegreeMinute:
1280+
mAnnotationFormatComboBox->setCurrentIndex( 3 );
1281+
break;
1282+
case QgsComposerMap::DegreeMinuteSecond:
1283+
mAnnotationFormatComboBox->setCurrentIndex( 6 );
1284+
break;
1285+
case QgsComposerMap::DecimalWithSuffix:
1286+
mAnnotationFormatComboBox->setCurrentIndex( 1 );
1287+
break;
1288+
case QgsComposerMap::DegreeMinuteNoSuffix:
1289+
mAnnotationFormatComboBox->setCurrentIndex( 2 );
1290+
break;
1291+
case QgsComposerMap::DegreeMinutePadded:
1292+
mAnnotationFormatComboBox->setCurrentIndex( 4 );
1293+
break;
1294+
case QgsComposerMap::DegreeMinuteSecondNoSuffix:
1295+
mAnnotationFormatComboBox->setCurrentIndex( 5 );
1296+
break;
1297+
case QgsComposerMap::DegreeMinuteSecondPadded:
1298+
mAnnotationFormatComboBox->setCurrentIndex( 7 );
1299+
break;
1300+
}
12701301
mDistanceToMapFrameSpinBox->setValue( grid->annotationFrameDistance() );
12711302
mCoordinatePrecisionSpinBox->setValue( grid->gridAnnotationPrecision() );
12721303

@@ -1851,7 +1882,35 @@ void QgsComposerMapWidget::on_mAnnotationFormatComboBox_currentIndexChanged( int
18511882
}
18521883

18531884
mComposerMap->beginCommand( tr( "Annotation format changed" ) );
1854-
grid->setGridAnnotationFormat(( QgsComposerMap::GridAnnotationFormat )index );
1885+
1886+
switch ( index )
1887+
{
1888+
case 0:
1889+
grid->setGridAnnotationFormat( QgsComposerMap::Decimal );
1890+
break;
1891+
case 3:
1892+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinute );
1893+
break;
1894+
case 6:
1895+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecond );
1896+
break;
1897+
case 1:
1898+
grid->setGridAnnotationFormat( QgsComposerMap::DecimalWithSuffix );
1899+
break;
1900+
case 2:
1901+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteNoSuffix );
1902+
break;
1903+
case 4:
1904+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinutePadded );
1905+
break;
1906+
case 5:
1907+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecondNoSuffix );
1908+
break;
1909+
case 7:
1910+
grid->setGridAnnotationFormat( QgsComposerMap::DegreeMinuteSecondPadded );
1911+
break;
1912+
}
1913+
18551914
mComposerMap->updateBoundingRect();
18561915
mComposerMap->update();
18571916
mComposerMap->endCommand();

‎src/core/composer/qgscomposermap.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ class CORE_EXPORT QgsComposerMap : public QgsComposerItem
9090
{
9191
Decimal = 0,
9292
DegreeMinute,
93-
DegreeMinuteSecond
93+
DegreeMinuteSecond,
94+
DecimalWithSuffix,
95+
DegreeMinuteNoSuffix,
96+
DegreeMinutePadded,
97+
DegreeMinuteSecondNoSuffix,
98+
DegreeMinuteSecondPadded
9499
};
95100

96101
enum GridFrameStyle

‎src/core/composer/qgscomposermapgrid.cpp

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,19 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMap::
10291029
{
10301030
return QString::number( value, 'f', mGridAnnotationPrecision );
10311031
}
1032+
else if ( mGridAnnotationFormat == QgsComposerMap::DecimalWithSuffix )
1033+
{
1034+
QString hemisphere;
1035+
if ( coord == QgsComposerMap::Longitude )
1036+
{
1037+
hemisphere = value < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
1038+
}
1039+
else
1040+
{
1041+
hemisphere = value < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
1042+
}
1043+
return QString::number( qAbs( value ), 'f', mGridAnnotationPrecision ) + hemisphere;
1044+
}
10321045

10331046
QgsPoint p;
10341047
p.setX( coord == QgsComposerMap::Longitude ? value : 0 );
@@ -1039,10 +1052,26 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMap::
10391052
{
10401053
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision );
10411054
}
1042-
else //DegreeMinuteSecond
1055+
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteNoSuffix )
1056+
{
1057+
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision, false );
1058+
}
1059+
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinutePadded )
1060+
{
1061+
annotationString = p.toDegreesMinutes( mGridAnnotationPrecision, true, true );
1062+
}
1063+
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecond )
10431064
{
10441065
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision );
10451066
}
1067+
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecondNoSuffix )
1068+
{
1069+
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision, false );
1070+
}
1071+
else if ( mGridAnnotationFormat == QgsComposerMap::DegreeMinuteSecondPadded )
1072+
{
1073+
annotationString = p.toDegreesMinutesSeconds( mGridAnnotationPrecision, true, true );
1074+
}
10461075

10471076
QStringList split = annotationString.split( "," );
10481077
if ( coord == QgsComposerMap::Longitude )

‎src/core/qgspoint.cpp

Lines changed: 166 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -134,46 +134,188 @@ QString QgsPoint::toString( int thePrecision ) const
134134
return QString( "%1,%2" ).arg( x ).arg( y );
135135
}
136136

137-
QString QgsPoint::toDegreesMinutesSeconds( int thePrecision ) const
137+
QString QgsPoint::toDegreesMinutesSeconds( int thePrecision, const bool useSuffix, const bool padded ) const
138138
{
139-
int myDegreesX = int( qAbs( m_x ) );
140-
float myFloatMinutesX = float(( qAbs( m_x ) - myDegreesX ) * 60 );
139+
//first, limit longitude to -360 to 360 degree range
140+
double myWrappedX = fmod( m_x, 360.0 );
141+
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
142+
if ( myWrappedX > 180.0 )
143+
{
144+
myWrappedX = myWrappedX - 360.0;
145+
}
146+
else if ( myWrappedX < -180.0 )
147+
{
148+
myWrappedX = myWrappedX + 360.0;
149+
}
150+
151+
int myDegreesX = int( qAbs( myWrappedX ) );
152+
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );
141153
int myIntMinutesX = int( myFloatMinutesX );
142-
float mySecondsX = float( myFloatMinutesX - myIntMinutesX ) * 60;
154+
double mySecondsX = double( myFloatMinutesX - myIntMinutesX ) * 60;
143155

144156
int myDegreesY = int( qAbs( m_y ) );
145-
float myFloatMinutesY = float(( qAbs( m_y ) - myDegreesY ) * 60 );
157+
double myFloatMinutesY = double(( qAbs( m_y ) - myDegreesY ) * 60 );
146158
int myIntMinutesY = int( myFloatMinutesY );
147-
float mySecondsY = float( myFloatMinutesY - myIntMinutesY ) * 60;
159+
double mySecondsY = double( myFloatMinutesY - myIntMinutesY ) * 60;
148160

149-
QString myXHemisphere = m_x < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
150-
QString myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
151-
QString rep = QString::number( myDegreesX ) + QChar( 176 ) +
152-
QString::number( myIntMinutesX ) + QString( "'" ) +
153-
QString::number( mySecondsX, 'f', thePrecision ) + QString( "\"" ) +
161+
//make sure rounding to specified precision doesn't create seconds >= 60
162+
if ( qRound( mySecondsX * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
163+
{
164+
mySecondsX = qMax( mySecondsX - 60, 0.0 );
165+
myIntMinutesX++;
166+
if ( myIntMinutesX >= 60 )
167+
{
168+
myIntMinutesX -= 60;
169+
myDegreesX++;
170+
}
171+
}
172+
if ( qRound( mySecondsY * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
173+
{
174+
mySecondsY = qMax( mySecondsY - 60, 0.0 );
175+
myIntMinutesY++;
176+
if ( myIntMinutesY >= 60 )
177+
{
178+
myIntMinutesY -= 60;
179+
myDegreesY++;
180+
}
181+
}
182+
183+
QString myXHemisphere;
184+
QString myYHemisphere;
185+
QString myXSign;
186+
QString myYSign;
187+
if ( useSuffix )
188+
{
189+
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
190+
myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
191+
}
192+
else
193+
{
194+
if ( myWrappedX < 0 )
195+
{
196+
myXSign = QObject::tr( "-" );
197+
}
198+
if ( m_y < 0 )
199+
{
200+
myYSign = QObject::tr( "-" );
201+
}
202+
}
203+
//check if coordinate is all zeros for the specified precision, and if so,
204+
//remove the sign and hemisphere strings
205+
if ( myDegreesX == 0 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10, thePrecision ) ) == 0 )
206+
{
207+
myXSign = QString();
208+
myXHemisphere = QString();
209+
}
210+
if ( myDegreesY == 0 && myIntMinutesY == 0 && qRound( mySecondsY * pow( 10, thePrecision ) ) == 0 )
211+
{
212+
myYSign = QString();
213+
myYHemisphere = QString();
214+
}
215+
//also remove directional prefix from 180 degree longitudes
216+
if ( myDegreesX == 180 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10, thePrecision ) ) == 0 )
217+
{
218+
myXHemisphere = QString();
219+
}
220+
//pad minutes with leading digits if required
221+
QString myMinutesX = padded ? QString( "%1" ).arg( myIntMinutesX, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesX );
222+
QString myMinutesY = padded ? QString( "%1" ).arg( myIntMinutesY, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesY );
223+
//pad seconds with leading digits if required
224+
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
225+
QString myStrSecondsX = padded ? QString( "%1" ).arg( mySecondsX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsX, 'f', thePrecision );
226+
QString myStrSecondsY = padded ? QString( "%1" ).arg( mySecondsY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsY, 'f', thePrecision );
227+
228+
QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
229+
myMinutesX + QString( "'" ) +
230+
myStrSecondsX + QString( "\"" ) +
154231
myXHemisphere + QString( "," ) +
155-
QString::number( myDegreesY ) + QChar( 176 ) +
156-
QString::number( myIntMinutesY ) + QString( "'" ) +
157-
QString::number( mySecondsY, 'f', thePrecision ) + QString( "\"" ) +
232+
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
233+
myMinutesY + QString( "'" ) +
234+
myStrSecondsY + QString( "\"" ) +
158235
myYHemisphere;
159236
return rep;
160237
}
161238

162-
QString QgsPoint::toDegreesMinutes( int thePrecision ) const
239+
QString QgsPoint::toDegreesMinutes( int thePrecision, const bool useSuffix, const bool padded ) const
163240
{
164-
int myDegreesX = int( qAbs( m_x ) );
165-
float myFloatMinutesX = float(( qAbs( m_x ) - myDegreesX ) * 60 );
241+
//first, limit longitude to -360 to 360 degree range
242+
double myWrappedX = fmod( m_x, 360.0 );
243+
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
244+
if ( myWrappedX > 180.0 )
245+
{
246+
myWrappedX = myWrappedX - 360.0;
247+
}
248+
else if ( myWrappedX < -180.0 )
249+
{
250+
myWrappedX = myWrappedX + 360.0;
251+
}
252+
253+
int myDegreesX = int( qAbs( myWrappedX ) );
254+
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );
166255

167256
int myDegreesY = int( qAbs( m_y ) );
168-
float myFloatMinutesY = float(( qAbs( m_y ) - myDegreesY ) * 60 );
257+
double myFloatMinutesY = double(( qAbs( m_y ) - myDegreesY ) * 60 );
258+
259+
//make sure rounding to specified precision doesn't create minutes >= 60
260+
if ( qRound( myFloatMinutesX * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
261+
{
262+
myFloatMinutesX = qMax( myFloatMinutesX - 60, 0.0 );
263+
myDegreesX++;
264+
}
265+
if ( qRound( myFloatMinutesY * pow( 10, thePrecision ) ) >= 60 * pow( 10, thePrecision ) )
266+
{
267+
myFloatMinutesY = qMax( myFloatMinutesY - 60, 0.0 );
268+
myDegreesY++;
269+
}
270+
271+
QString myXHemisphere;
272+
QString myYHemisphere;
273+
QString myXSign;
274+
QString myYSign;
275+
if ( useSuffix )
276+
{
277+
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
278+
myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
279+
}
280+
else
281+
{
282+
if ( myWrappedX < 0 )
283+
{
284+
myXSign = QObject::tr( "-" );
285+
}
286+
if ( m_y < 0 )
287+
{
288+
myYSign = QObject::tr( "-" );
289+
}
290+
}
291+
//check if coordinate is all zeros for the specified precision, and if so,
292+
//remove the sign and hemisphere strings
293+
if ( myDegreesX == 0 && qRound( myFloatMinutesX * pow( 10, thePrecision ) ) == 0 )
294+
{
295+
myXSign = QString();
296+
myXHemisphere = QString();
297+
}
298+
if ( myDegreesY == 0 && qRound( myFloatMinutesY * pow( 10, thePrecision ) ) == 0 )
299+
{
300+
myYSign = QString();
301+
myYHemisphere = QString();
302+
}
303+
//also remove directional prefix from 180 degree longitudes
304+
if ( myDegreesX == 180 && qRound( myFloatMinutesX * pow( 10, thePrecision ) ) == 0 )
305+
{
306+
myXHemisphere = QString();
307+
}
308+
309+
//pad minutes with leading digits if required
310+
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
311+
QString myStrMinutesX = padded ? QString( "%1" ).arg( myFloatMinutesX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesX, 'f', thePrecision );
312+
QString myStrMinutesY = padded ? QString( "%1" ).arg( myFloatMinutesY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesY, 'f', thePrecision );
169313

170-
QString myXHemisphere = m_x < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
171-
QString myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
172-
QString rep = QString::number( myDegreesX ) + QChar( 176 ) +
173-
QString::number( myFloatMinutesX, 'f', thePrecision ) + QString( "'" ) +
314+
QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
315+
myStrMinutesX + QString( "'" ) +
174316
myXHemisphere + QString( "," ) +
175-
QString::number( myDegreesY ) + QChar( 176 ) +
176-
QString::number( myFloatMinutesY, 'f', thePrecision ) + QString( "'" ) +
317+
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
318+
myStrMinutesY + QString( "'" ) +
177319
myYHemisphere;
178320
return rep;
179321
}

‎src/core/qgspoint.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,26 @@ class CORE_EXPORT QgsPoint
129129
/** Return a string representation as degrees minutes seconds.
130130
* Its up to the calling function to ensure that this point can
131131
* be meaningfully represented in this form.
132+
* @param thePrecision number of decimal points to use for seconds
133+
* @param useSuffix set to true to include a direction suffix (eg 'N'),
134+
* set to false to use a "-" prefix for west and south coordinates
135+
* @param padded set to true to force minutes and seconds to use two decimals,
136+
* eg, '05' instead of '5'.
132137
* @note added in QGIS 1.4
133138
*/
134-
QString toDegreesMinutesSeconds( int thePrecision ) const;
139+
QString toDegreesMinutesSeconds( int thePrecision, const bool useSuffix = true, const bool padded = false ) const;
135140

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

143153

144154
/*! Return the well known text representation for the point.

‎tests/src/core/testqgspoint.cpp

Lines changed: 403 additions & 10 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.