QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgscoordinateutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinateutils.cpp
3  ----------------------
4  begin : February 2016
5  copyright : (C) 2016 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 "qgscoordinateutils.h"
21 #include "qgscoordinatetransform.h"
22 #include "qgsproject.h"
23 #include "qgis.h"
24 #include "qgsexception.h"
25 #include "qgscoordinateformatter.h"
26 #include "qgsrectangle.h"
29 
30 #include <QLocale>
31 #include <QRegularExpression>
32 
34 
35 int QgsCoordinateUtils::calculateCoordinatePrecision( double mapUnitsPerPixel, const QgsCoordinateReferenceSystem &mapCrs, QgsProject *project )
36 {
37  if ( !project )
38  project = QgsProject::instance();
39  // Get the display precision from the project settings
40  const bool automatic = project->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
41  int dp = 0;
42 
43  if ( automatic )
44  {
45  const QString format = project->readEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DegreeFormat" ), QStringLiteral( "MU" ) );
46  // only MU or D is used now, but older projects may have DM/DMS
47  const bool formatGeographic = format == QLatin1String( "D" ) || format == QLatin1String( "DM" ) || format == QLatin1String( "DMS" );
48 
49  // we can only calculate an automatic precision if one of these is true:
50  // - both map CRS and format are geographic
51  // - both map CRS and format are not geographic
52  // - map CRS is geographic but format is not geographic (i.e. map units)
53  if ( mapCrs.isGeographic() || !formatGeographic )
54  {
55  // Work out a suitable number of decimal places for the coordinates with the aim of always
56  // having enough decimal places to show the difference in position between adjacent pixels.
57  // Also avoid taking the log of 0.
58  if ( !qgsDoubleNear( mapUnitsPerPixel, 0.0 ) )
59  dp = static_cast<int>( std::ceil( -1.0 * std::log10( mapUnitsPerPixel ) ) );
60  }
61  else
62  {
63  switch ( project->displaySettings()->geographicCoordinateFormat()->angleFormat() )
64  {
67  dp = 2;
68  break;
70  dp = 4;
71  break;
72  }
73  }
74  }
75  else
76  dp = project->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ) );
77 
78  // Keep dp sensible
79  if ( dp < 0 )
80  dp = 0;
81 
82  return dp;
83 }
84 
85 int QgsCoordinateUtils::calculateCoordinatePrecisionForCrs( const QgsCoordinateReferenceSystem &crs, QgsProject *project )
86 {
87  QgsProject *prj = project;
88  if ( !prj )
89  {
90  prj = QgsProject::instance();
91  }
92 
93  const bool automatic = prj->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
94  if ( !automatic )
95  {
96  return prj->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 6 );
97  }
98 
100  if ( unit == QgsUnitTypes::DistanceDegrees )
101  {
102  return 8;
103  }
104  else
105  {
106  return 3;
107  }
108 }
109 
110 QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision )
111 {
112  if ( !project )
113  return QString();
114 
115  const QString format = project->readEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DegreeFormat" ), QStringLiteral( "MU" ) );
116  const Qgis::CoordinateOrder axisOrder = qgsEnumKeyToValue( project->readEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/CoordinateOrder" ) ), Qgis::CoordinateOrder::Default );
117 
118  // only MU or D is used now, but older projects may have DM/DMS
119  const bool formatGeographic = format == QLatin1String( "D" ) || format == QLatin1String( "DM" ) || format == QLatin1String( "DMS" );
120 
121  QgsPointXY geo = point;
122  if ( formatGeographic )
123  {
124  // degrees
125  QgsCoordinateReferenceSystem geographicCrs = destCrs;
126  if ( destCrs.isValid() && !destCrs.isGeographic() )
127  {
128  // default to EPSG:4326 if the project CRS isn't already geographic
129  geographicCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
130  // need to transform to geographic coordinates
131  const QgsCoordinateTransform ct( destCrs, geographicCrs, project );
132  try
133  {
134  geo = ct.transform( point );
135  }
136  catch ( QgsCsException & )
137  {
138  return QString();
139  }
140  }
141 
143 
144  std::unique_ptr< QgsGeographicCoordinateNumericFormat > format( project->displaySettings()->geographicCoordinateFormat()->clone() );
145  format->setNumberDecimalPlaces( precision );
146 
147  QgsNumericFormatContext context;
149  const QString formattedX = format->formatDouble( geo.x(), context );
151  const QString formattedY = format->formatDouble( geo.y(), context );
152 
153  switch ( order )
154  {
157  return QStringLiteral( "%1%2%3" ).arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
158 
160  return QStringLiteral( "%1%2%3" ).arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
161  }
163  }
164  else
165  {
166  // coordinates in map units
168  return QgsCoordinateFormatter::asPair( point.x(), point.y(), precision, order );
169  }
170 }
171 
172 QString QgsCoordinateUtils::formatExtentForProject( QgsProject *project, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &destCrs, int precision )
173 {
174  const QgsPointXY p1( extent.xMinimum(), extent.yMinimum() );
175  const QgsPointXY p2( extent.xMaximum(), extent.yMaximum() );
176  return QStringLiteral( "%1 : %2" ).arg( QgsCoordinateUtils::formatCoordinateForProject( project, p1, destCrs, precision ),
177  QgsCoordinateUtils::formatCoordinateForProject( project, p2, destCrs, precision ) );
178 }
179 
180 double QgsCoordinateUtils::degreeToDecimal( const QString &string, bool *ok, bool *isEasting )
181 {
182  const QString negative( QStringLiteral( "swSW" ) );
183  const QString easting( QStringLiteral( "eEwW" ) );
184  double value = 0.0;
185  bool okValue = false;
186 
187  if ( ok )
188  {
189  *ok = false;
190  }
191  else
192  {
193  ok = &okValue;
194  }
195 
196  const QLocale locale;
197  QRegularExpression degreeWithSuffix( QStringLiteral( "^\\s*([-]?\\d{1,3}(?:[\\.\\%1]\\d+)?)\\s*([NSEWnsew])\\s*$" )
198  .arg( locale.decimalPoint() ) );
199  QRegularExpressionMatch match = degreeWithSuffix.match( string );
200  if ( match.hasMatch() )
201  {
202  const QString suffix = match.captured( 2 );
203  value = std::abs( match.captured( 1 ).toDouble( ok ) );
204  if ( *ok == false )
205  {
206  value = std::abs( locale.toDouble( match.captured( 1 ), ok ) );
207  }
208  if ( *ok )
209  {
210  value *= ( negative.contains( suffix ) ? -1 : 1 );
211  if ( isEasting )
212  {
213  *isEasting = easting.contains( suffix );
214  }
215  }
216  }
217  return value;
218 }
219 
220 double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok, bool *isEasting )
221 {
222  const QString negative( QStringLiteral( "swSW-" ) );
223  const QString easting( QStringLiteral( "eEwW" ) );
224  double value = 0.0;
225  bool okValue = false;
226 
227  if ( ok )
228  {
229  *ok = false;
230  }
231  else
232  {
233  ok = &okValue;
234  }
235 
236  const QLocale locale;
237  const QRegularExpression dms( QStringLiteral( "^\\s*(?:([-+nsew])\\s*)?(\\d{1,3})(?:[^0-9.]+([0-5]?\\d))?[^0-9.]+([0-5]?\\d(?:[\\.\\%1]\\d+)?)[^0-9.,]*?([-+nsew])?\\s*$" )
238  .arg( locale.decimalPoint() ), QRegularExpression::CaseInsensitiveOption );
239  const QRegularExpressionMatch match = dms.match( string.trimmed() );
240  if ( match.hasMatch() )
241  {
242  const QString dms1 = match.captured( 2 );
243  const QString dms2 = match.captured( 3 );
244  const QString dms3 = match.captured( 4 );
245 
246  double v = dms3.toDouble( ok );
247  if ( *ok == false )
248  {
249  v = locale.toDouble( dms3, ok );
250  if ( *ok == false )
251  return value;
252  }
253  // Allow for Degrees/minutes format as well as DMS
254  if ( !dms2.isEmpty() )
255  {
256  v = dms2.toInt( ok ) + v / 60.0;
257  if ( *ok == false )
258  return value;
259  }
260  v = dms1.toInt( ok ) + v / 60.0;
261  if ( *ok == false )
262  return value;
263 
264  const QString sign1 = match.captured( 1 );
265  const QString sign2 = match.captured( 5 );
266 
267  if ( sign1.isEmpty() )
268  {
269  value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
270  if ( isEasting )
271  {
272  *isEasting = easting.contains( sign2 );
273  }
274  }
275  else if ( sign2.isEmpty() )
276  {
277  value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
278  if ( isEasting )
279  {
280  *isEasting = easting.contains( sign2 );
281  }
282  }
283  else
284  {
285  *ok = false;
286  }
287  }
288  return value;
289 }
290 
QgsPointXY::y
double y
Definition: qgspointxy.h:63
qgsrectangle.h
qgscoordinateutils.h
QgsNumericFormatContext::Interpretation::Latitude
@ Latitude
Latitude values.
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:105
QgsRectangle::yMinimum
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
qgis.h
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:480
QgsProject::displaySettings
const QgsProjectDisplaySettings * displaySettings() const
Returns the project's display settings, which settings and properties relating to how a QgsProject sh...
Definition: qgsproject.cpp:3531
QgsProject::readEntry
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
Definition: qgsproject.cpp:2946
QgsUnitTypes::DistanceUnit
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:67
QgsProject::readBoolEntry
bool readBoolEntry(const QString &scope, const QString &key, bool def=false, bool *ok=nullptr) const
Reads a boolean from the specified scope and key.
Definition: qgsproject.cpp:3021
QgsGeographicCoordinateNumericFormat::AngleFormat::DecimalDegrees
@ DecimalDegrees
Decimal degrees, eg 30.7555 degrees.
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QgsProject
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:103
QgsProjectDisplaySettings::geographicCoordinateFormat
const QgsGeographicCoordinateNumericFormat * geographicCoordinateFormat() const
Returns the project's geographic coordinate format, which controls how geographic coordinates associa...
Definition: qgsprojectdisplaysettings.cpp:64
QgsRectangle::xMaximum
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
QgsCoordinateReferenceSystemUtils::defaultCoordinateOrderForCrs
static Qgis::CoordinateOrder defaultCoordinateOrderForCrs(const QgsCoordinateReferenceSystem &crs)
Returns the default coordinate order to use for the specified crs.
Definition: qgscoordinatereferencesystemutils.cpp:20
Qgis::CoordinateOrder::Default
@ Default
Respect the default axis ordering for the CRS, as defined in the CRS's parameters.
QgsCoordinateFormatter::separator
static QChar separator()
Returns the character used as X/Y separator, this is a , on locales that do not use ,...
Definition: qgscoordinateformatter.cpp:96
precision
int precision
Definition: qgswfsgetfeature.cpp:103
QgsCoordinateReferenceSystem::isGeographic
bool isGeographic
Definition: qgscoordinatereferencesystem.h:216
QgsCsException
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QgsUnitTypes::DistanceDegrees
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
QgsNumericFormatContext::Interpretation::Longitude
@ Longitude
Longitude values.
QgsGeographicCoordinateNumericFormat::angleFormat
AngleFormat angleFormat() const
Returns the angle format, which controls how bearing the angles are formatted described in the return...
Definition: qgscoordinatenumericformat.cpp:112
qgsDoubleNear
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2265
Qgis::CoordinateOrder::XY
@ XY
Easting/Northing (or Longitude/Latitude for geographic CRS)
QgsCoordinateReferenceSystem::isValid
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Definition: qgscoordinatereferencesystem.cpp:977
qgscoordinatetransform.h
BUILTIN_UNREACHABLE
#define BUILTIN_UNREACHABLE
Definition: qgis.h:2907
qgscoordinatereferencesystemutils.h
QgsRectangle::xMinimum
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:58
QgsCoordinateReferenceSystem::mapUnits
QgsUnitTypes::DistanceUnit mapUnits
Definition: qgscoordinatereferencesystem.h:215
QgsRectangle::yMaximum
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
qgscoordinatenumericformat.h
QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutesSeconds
@ DegreesMinutesSeconds
Degrees, minutes and seconds, eg 30 degrees 45'30.
Qgis::CoordinateOrder::YX
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS)
QgsPointXY::x
double x
Definition: qgspointxy.h:62
qgsEnumKeyToValue
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition: qgis.h:2459
QgsNumericFormatContext::setInterpretation
void setInterpretation(Interpretation interpretation)
Sets the interpretation of the numbers being converted.
Definition: qgsnumericformat.h:220
QgsCoordinateFormatter::asPair
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...
Definition: qgscoordinateformatter.cpp:79
qgsexception.h
QgsGeographicCoordinateNumericFormat::AngleFormat::DegreesMinutes
@ DegreesMinutes
Degrees and decimal minutes, eg 30 degrees 45.55'.
qgscoordinateformatter.h
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:57
qgsprojectdisplaysettings.h
Qgis::CoordinateOrder
CoordinateOrder
Order of coordinates.
Definition: qgis.h:1101
qgscoordinatereferencesystem.h
QgsGeographicCoordinateNumericFormat::clone
QgsGeographicCoordinateNumericFormat * clone() const override
Clones the format, returning a new object.
Definition: qgscoordinatenumericformat.cpp:86
qgsproject.h
QgsNumericFormatContext
A context for numeric formats.
Definition: qgsnumericformat.h:34
QgsProject::readNumEntry
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
Definition: qgsproject.cpp:2972