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