QGIS API Documentation  3.25.0-Master (6b426f5f8a)
qgscoordinateformatter.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinateformatter.cpp
3  --------------------------
4  begin : Decemeber 2015
5  copyright : (C) 2015 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgscoordinateformatter.h"
19 #include "qgis.h"
20 
21 #include <QObject> // for tr()
22 #include <QLocale>
23 
24 QString QgsCoordinateFormatter::formatX( double x, QgsCoordinateFormatter::Format format, int precision, FormatFlags flags )
25 {
26  switch ( format )
27  {
28  case FormatPair:
29  return formatAsPair( x, precision );
30 
32  return formatXAsDegreesMinutesSeconds( x, precision, flags );
33 
35  return formatXAsDegreesMinutes( x, precision, flags );
36 
38  return formatXAsDegrees( x, precision, flags );
39  }
40  return QString(); //avoid warnings
41 }
42 
43 QString QgsCoordinateFormatter::formatY( double y, QgsCoordinateFormatter::Format format, int precision, FormatFlags flags )
44 {
45  switch ( format )
46  {
47  case FormatPair:
48  return formatAsPair( y, precision );
49 
51  return formatYAsDegreesMinutesSeconds( y, precision, flags );
52 
54  return formatYAsDegreesMinutes( y, precision, flags );
55 
57  return formatYAsDegrees( y, precision, flags );
58  }
59  return QString(); //avoid warnings
60 }
61 
62 QString QgsCoordinateFormatter::format( const QgsPointXY &point, QgsCoordinateFormatter::Format format, int precision, FormatFlags flags, Qgis::CoordinateOrder order )
63 {
64  const QString formattedX = formatX( point.x(), format, precision, flags );
65  const QString formattedY = formatY( point.y(), format, precision, flags );
66 
67  switch ( order )
68  {
71  return QStringLiteral( "%1%2%3" ).arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
72 
74  return QStringLiteral( "%1%2%3" ).arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
75  }
77 }
78 
79 QString QgsCoordinateFormatter::asPair( double x, double y, int precision, Qgis::CoordinateOrder order )
80 {
81  const QString formattedX = formatAsPair( x, precision );
82  const QString formattedY = formatAsPair( y, precision );
83 
84  switch ( order )
85  {
88  return QStringLiteral( "%1%2%3" ).arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
89 
91  return QStringLiteral( "%1%2%3" ).arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
92  }
94 }
95 
97 {
98  return QLocale().decimalPoint() == QLatin1Char( ',' ) ? QLatin1Char( ' ' ) : QLatin1Char( ',' );
99 }
100 
101 QString QgsCoordinateFormatter::formatAsPair( double val, int precision )
102 {
103  return std::isfinite( val ) ? QLocale().toString( val, 'f', precision ) : QObject::tr( "infinite" );
104 }
105 
106 QString QgsCoordinateFormatter::formatXAsDegreesMinutesSeconds( double val, int precision, FormatFlags flags )
107 {
108  //first, limit longitude to -360 to 360 degree range
109  double wrappedX = std::fmod( val, 360.0 );
110  //next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
111  if ( wrappedX > 180.0 )
112  {
113  wrappedX = wrappedX - 360.0;
114  }
115  else if ( wrappedX < -180.0 )
116  {
117  wrappedX = wrappedX + 360.0;
118  }
119 
120  const int precisionMultiplier = std::pow( 10.0, precision );
121 
122  int degreesX = int( std::fabs( wrappedX ) );
123  const double floatMinutesX = ( std::fabs( wrappedX ) - degreesX ) * 60.0;
124  int intMinutesX = int( floatMinutesX );
125  double secondsX = ( floatMinutesX - intMinutesX ) * 60.0;
126 
127  //make sure rounding to specified precision doesn't create seconds >= 60
128  if ( std::round( secondsX * precisionMultiplier ) >= 60 * precisionMultiplier )
129  {
130  secondsX = std::max( secondsX - 60, 0.0 );
131  intMinutesX++;
132  if ( intMinutesX >= 60 )
133  {
134  intMinutesX -= 60;
135  degreesX++;
136  }
137  }
138 
139  QString hemisphere;
140  QString sign;
141  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
142  {
143  hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
144  }
145  else
146  {
147  if ( wrappedX < 0 )
148  {
149  sign = QLocale().negativeSign();
150  }
151  }
152  //check if coordinate is all zeros for the specified precision, and if so,
153  //remove the sign and hemisphere strings
154  if ( degreesX == 0 && intMinutesX == 0 && std::round( secondsX * precisionMultiplier ) == 0 )
155  {
156  sign.clear();
157  hemisphere.clear();
158  }
159 
160  //also remove directional prefix from 180 degree longitudes
161  if ( degreesX == 180 && intMinutesX == 0 && std::round( secondsX * precisionMultiplier ) == 0 )
162  {
163  hemisphere.clear();
164  }
165 
166  QString minutesX;
167  QString strSecondsX;
168 
169  //pad with leading digits if required
170  if ( flags.testFlag( FlagDegreesPadMinutesSeconds ) )
171  {
172  minutesX = QString( "%L1" ).arg( intMinutesX, 2, 10, QChar( '0' ) );
173  const int digits = 2 + ( precision == 0 ? 0 : 1 + precision ); //1 for decimal place if required
174  strSecondsX = QString( "%L1" ).arg( secondsX, digits, 'f', precision, QChar( '0' ) );
175  }
176  else
177  {
178  minutesX = QLocale().toString( intMinutesX );
179  strSecondsX = QLocale().toString( secondsX, 'f', precision );
180  }
181 
182  return sign + QLocale().toString( degreesX ) + QChar( 176 ) +
183  minutesX + QChar( 0x2032 ) +
184  strSecondsX + QChar( 0x2033 ) +
185  hemisphere;
186 }
187 
188 QString QgsCoordinateFormatter::formatYAsDegreesMinutesSeconds( double val, int precision, FormatFlags flags )
189 {
190  //first, limit latitude to -180 to 180 degree range
191  double wrappedY = std::fmod( val, 180.0 );
192  //next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
193  if ( wrappedY > 90.0 )
194  {
195  wrappedY = wrappedY - 180.0;
196  }
197  else if ( wrappedY < -90.0 )
198  {
199  wrappedY = wrappedY + 180.0;
200  }
201 
202  const int precisionMultiplier = std::pow( 10.0, precision );
203 
204  int degreesY = int( std::fabs( wrappedY ) );
205  const double floatMinutesY = ( std::fabs( wrappedY ) - degreesY ) * 60.0;
206  int intMinutesY = int( floatMinutesY );
207  double secondsY = ( floatMinutesY - intMinutesY ) * 60.0;
208 
209  //make sure rounding to specified precision doesn't create seconds >= 60
210  if ( std::round( secondsY * precisionMultiplier ) >= 60 * precisionMultiplier )
211  {
212  secondsY = std::max( secondsY - 60, 0.0 );
213  intMinutesY++;
214  if ( intMinutesY >= 60 )
215  {
216  intMinutesY -= 60;
217  degreesY++;
218  }
219  }
220 
221  QString hemisphere;
222  QString sign;
223  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
224  {
225  hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
226  }
227  else
228  {
229  if ( wrappedY < 0 )
230  {
231  sign = QLocale().negativeSign();
232  }
233  }
234  //check if coordinate is all zeros for the specified precision, and if so,
235  //remove the sign and hemisphere strings
236  if ( degreesY == 0 && intMinutesY == 0 && std::round( secondsY * precisionMultiplier ) == 0 )
237  {
238  sign = QString();
239  hemisphere.clear();
240  }
241 
242  QString strMinutesY;
243  QString strSecondsY;
244 
245  //pad with leading digits if required
246  if ( flags.testFlag( FlagDegreesPadMinutesSeconds ) )
247  {
248  strMinutesY = QString( "%L1" ).arg( intMinutesY, 2, 10, QChar( '0' ) );
249  const int digits = 2 + ( precision == 0 ? 0 : 1 + precision ); //1 for decimal place if required
250  strSecondsY = QString( "%L1" ).arg( secondsY, digits, 'f', precision, QChar( '0' ) );
251  }
252  else
253  {
254  strMinutesY = QLocale().toString( intMinutesY );
255  strSecondsY = QLocale().toString( secondsY, 'f', precision );
256  }
257 
258  return sign + QLocale().toString( degreesY ) + QChar( 176 ) +
259  strMinutesY + QChar( 0x2032 ) +
260  strSecondsY + QChar( 0x2033 ) +
261  hemisphere;
262 }
263 
264 QString QgsCoordinateFormatter::formatXAsDegreesMinutes( double val, int precision, FormatFlags flags )
265 {
266  //first, limit longitude to -360 to 360 degree range
267  double wrappedX = std::fmod( val, 360.0 );
268  //next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
269  if ( wrappedX > 180.0 )
270  {
271  wrappedX = wrappedX - 360.0;
272  }
273  else if ( wrappedX < -180.0 )
274  {
275  wrappedX = wrappedX + 360.0;
276  }
277 
278  int degreesX = int( std::fabs( wrappedX ) );
279  double floatMinutesX = ( std::fabs( wrappedX ) - degreesX ) * 60.0;
280 
281  const int precisionMultiplier = std::pow( 10.0, precision );
282 
283  //make sure rounding to specified precision doesn't create minutes >= 60
284  if ( std::round( floatMinutesX * precisionMultiplier ) >= 60 * precisionMultiplier )
285  {
286  floatMinutesX = std::max( floatMinutesX - 60, 0.0 );
287  degreesX++;
288  }
289 
290  QString hemisphere;
291  QString sign;
292  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
293  {
294  hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
295  }
296  else
297  {
298  if ( wrappedX < 0 )
299  {
300  sign = QLocale().negativeSign();
301  }
302  }
303  //check if coordinate is all zeros for the specified precision, and if so,
304  //remove the sign and hemisphere strings
305  if ( degreesX == 0 && std::round( floatMinutesX * precisionMultiplier ) == 0 )
306  {
307  sign.clear();
308  hemisphere.clear();
309  }
310 
311  //also remove directional prefix from 180 degree longitudes
312  if ( degreesX == 180 && std::round( floatMinutesX * precisionMultiplier ) == 0 )
313  {
314  hemisphere.clear();
315  }
316 
317  //pad minutes with leading digits if required
318  const int digits = 2 + ( precision == 0 ? 0 : 1 + precision ); //1 for decimal place if required
319  const QString strMinutesX = flags.testFlag( FlagDegreesPadMinutesSeconds ) ? QString( "%1" ).arg( floatMinutesX, digits, 'f', precision, QChar( '0' ) )
320  : QLocale().toString( floatMinutesX, 'f', precision );
321 
322  return sign + QLocale().toString( degreesX ) + QChar( 176 ) +
323  strMinutesX + QChar( 0x2032 ) +
324  hemisphere;
325 }
326 
327 QString QgsCoordinateFormatter::formatYAsDegreesMinutes( double val, int precision, FormatFlags flags )
328 {
329  //first, limit latitude to -180 to 180 degree range
330  double wrappedY = std::fmod( val, 180.0 );
331  //next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
332  if ( wrappedY > 90.0 )
333  {
334  wrappedY = wrappedY - 180.0;
335  }
336  else if ( wrappedY < -90.0 )
337  {
338  wrappedY = wrappedY + 180.0;
339  }
340 
341  int degreesY = int( std::fabs( wrappedY ) );
342  double floatMinutesY = ( std::fabs( wrappedY ) - degreesY ) * 60.0;
343 
344  const int precisionMultiplier = std::pow( 10.0, precision );
345 
346  //make sure rounding to specified precision doesn't create minutes >= 60
347  if ( std::round( floatMinutesY * precisionMultiplier ) >= 60 * precisionMultiplier )
348  {
349  floatMinutesY = std::max( floatMinutesY - 60, 0.0 );
350  degreesY++;
351  }
352 
353  QString hemisphere;
354  QString sign;
355  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
356  {
357  hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
358  }
359  else
360  {
361  if ( wrappedY < 0 )
362  {
363  sign = QLocale().negativeSign();
364  }
365  }
366  //check if coordinate is all zeros for the specified precision, and if so,
367  //remove the sign and hemisphere strings
368  if ( degreesY == 0 && std::round( floatMinutesY * precisionMultiplier ) == 0 )
369  {
370  sign.clear();
371  hemisphere.clear();
372  }
373 
374 
375  //pad minutes with leading digits if required
376  const int digits = 2 + ( precision == 0 ? 0 : 1 + precision ); //1 for decimal place if required
377  const QString strMinutesY = flags.testFlag( FlagDegreesPadMinutesSeconds ) ? QString( "%1" ).arg( floatMinutesY, digits, 'f', precision, QChar( '0' ) )
378  : QLocale().toString( floatMinutesY, 'f', precision );
379 
380  return sign + QLocale().toString( degreesY ) + QChar( 176 ) +
381  strMinutesY + QChar( 0x2032 ) +
382  hemisphere;
383 }
384 
385 QString QgsCoordinateFormatter::formatXAsDegrees( double val, int precision, FormatFlags flags )
386 {
387  //first, limit longitude to -360 to 360 degree range
388  double wrappedX = std::fmod( val, 360.0 );
389  //next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
390  if ( wrappedX > 180.0 )
391  {
392  wrappedX = wrappedX - 360.0;
393  }
394  else if ( wrappedX < -180.0 )
395  {
396  wrappedX = wrappedX + 360.0;
397  }
398 
399  const double absX = std::fabs( wrappedX );
400 
401  const int precisionMultiplier = std::pow( 10.0, precision );
402 
403  QString hemisphere;
404  QString sign;
405  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
406  {
407  hemisphere = wrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
408  }
409  else
410  {
411  if ( wrappedX < 0 )
412  {
413  sign = QLocale().negativeSign();
414  }
415  }
416  //check if coordinate is all zeros for the specified precision, and if so,
417  //remove the sign and hemisphere strings
418  if ( std::round( absX * precisionMultiplier ) == 0 )
419  {
420  sign.clear();
421  hemisphere.clear();
422  }
423 
424  //also remove directional prefix from 180 degree longitudes
425  if ( std::round( absX * precisionMultiplier ) == 180 * precisionMultiplier )
426  {
427  sign.clear();
428  hemisphere.clear();
429  }
430 
431  return sign + QLocale().toString( absX, 'f', precision ) + QChar( 176 ) + hemisphere;
432 }
433 
434 QString QgsCoordinateFormatter::formatYAsDegrees( double val, int precision, FormatFlags flags )
435 {
436  //first, limit latitude to -180 to 180 degree range
437  double wrappedY = std::fmod( val, 180.0 );
438  //next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
439  if ( wrappedY > 90.0 )
440  {
441  wrappedY = wrappedY - 180.0;
442  }
443  else if ( wrappedY < -90.0 )
444  {
445  wrappedY = wrappedY + 180.0;
446  }
447 
448  const double absY = std::fabs( wrappedY );
449 
450  const int precisionMultiplier = std::pow( 10.0, precision );
451 
452  QString hemisphere;
453  QString sign;
454  if ( flags.testFlag( FlagDegreesUseStringSuffix ) )
455  {
456  hemisphere = wrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
457  }
458  else
459  {
460  if ( wrappedY < 0 )
461  {
462  sign = QLocale().negativeSign();
463  }
464  }
465  //check if coordinate is all zeros for the specified precision, and if so,
466  //remove the sign and hemisphere strings
467  if ( std::round( absY * precisionMultiplier ) == 0 )
468  {
469  sign.clear();
470  hemisphere.clear();
471  }
472 
473  return sign + QLocale().toString( absY, 'f', precision ) + QChar( 176 ) + hemisphere;
474 }
CoordinateOrder
Order of coordinates.
Definition: qgis.h:962
@ XY
Easting/Northing (or Longitude/Latitude for geographic CRS)
@ Default
Respect the default axis ordering for the CRS, as defined in the CRS's parameters.
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS)
static QString asPair(double x, double y, int precision=12, Qgis::CoordinateOrder order=Qgis::CoordinateOrder::XY)
Formats coordinates as an "\a x,\a y" pair, with optional decimal precision (number of decimal places...
Format
Available formats for displaying coordinates.
@ FormatDecimalDegrees
Decimal degrees, eg 30.7555 degrees.
@ FormatPair
Formats coordinates as an "x,y" pair.
@ FormatDegreesMinutes
Degrees and decimal minutes, eg 30degrees 45.55'.
@ FormatDegreesMinutesSeconds
Degrees, minutes and seconds, eg 30 degrees 45'30".
static QString format(const QgsPointXY &point, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix, Qgis::CoordinateOrder order=Qgis::CoordinateOrder::XY)
Formats a point according to the specified parameters.
static QChar separator()
Returns the character used as X/Y separator, this is a , on locales that do not use ,...
static QString formatY(double y, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats a y coordinate value according to the specified parameters.
@ FlagDegreesUseStringSuffix
Include a direction suffix (eg 'N', 'E', 'S' or 'W'), otherwise a "-" prefix is used for west and sou...
@ FlagDegreesPadMinutesSeconds
Pad minute and second values with leading zeros, eg '05' instead of '5'.
static QString formatX(double x, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats an x coordinate value according to the specified parameters.
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
#define BUILTIN_UNREACHABLE
Definition: qgis.h:2620
int precision