QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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"
20 #include "qgscoordinatetransform.h"
21 #include "qgsproject.h"
22 #include "qgis.h"
23 #include "qgsexception.h"
24 #include "qgscoordinateformatter.h"
25 #include "qgsrectangle.h"
26 #include <QRegularExpression>
27 
29 
30 int QgsCoordinateUtils::calculateCoordinatePrecision( double mapUnitsPerPixel, const QgsCoordinateReferenceSystem &mapCrs, QgsProject *project )
31 {
32  if ( !project )
33  project = QgsProject::instance();
34  // Get the display precision from the project settings
35  const bool automatic = project->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
36  int dp = 0;
37 
38  if ( automatic )
39  {
40  const QString format = project->readEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DegreeFormat" ), QStringLiteral( "MU" ) );
41  const bool formatGeographic = ( format == QLatin1String( "DM" ) || format == QLatin1String( "DMS" ) || format == QLatin1String( "D" ) );
42 
43  // we can only calculate an automatic precision if one of these is true:
44  // - both map CRS and format are geographic
45  // - both map CRS and format are not geographic
46  // - map CRS is geographic but format is not geographic (i.e. map units)
47  if ( mapCrs.isGeographic() || !formatGeographic )
48  {
49  // Work out a suitable number of decimal places for the coordinates with the aim of always
50  // having enough decimal places to show the difference in position between adjacent pixels.
51  // Also avoid taking the log of 0.
52  if ( !qgsDoubleNear( mapUnitsPerPixel, 0.0 ) )
53  dp = static_cast<int>( std::ceil( -1.0 * std::log10( mapUnitsPerPixel ) ) );
54  }
55  else
56  {
57  if ( format == QLatin1String( "D" ) )
58  dp = 4;
59  else
60  dp = 2;
61  }
62  }
63  else
64  dp = project->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ) );
65 
66  // Keep dp sensible
67  if ( dp < 0 )
68  dp = 0;
69 
70  return dp;
71 }
72 
73 int QgsCoordinateUtils::calculateCoordinatePrecisionForCrs( const QgsCoordinateReferenceSystem &crs, QgsProject *project )
74 {
75  QgsProject *prj = project;
76  if ( !prj )
77  {
78  prj = QgsProject::instance();
79  }
80 
81  const bool automatic = prj->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
82  if ( !automatic )
83  {
84  return prj->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 6 );
85  }
86 
88  if ( unit == QgsUnitTypes::DistanceDegrees )
89  {
90  return 8;
91  }
92  else
93  {
94  return 3;
95  }
96 }
97 
98 QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision )
99 {
100  if ( !project )
101  return QString();
102 
103  const QString format = project->readEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DegreeFormat" ), QStringLiteral( "MU" ) );
104 
105  QgsPointXY geo = point;
106  if ( format == QLatin1String( "DM" ) || format == QLatin1String( "DMS" ) || format == QLatin1String( "D" ) )
107  {
108  // degrees
109  if ( destCrs.isValid() && !destCrs.isGeographic() )
110  {
111  // need to transform to geographic coordinates
112  const QgsCoordinateTransform ct( destCrs, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), project );
113  try
114  {
115  geo = ct.transform( point );
116  }
117  catch ( QgsCsException & )
118  {
119  return QString();
120  }
121  }
122 
123  if ( format == QLatin1String( "DM" ) )
125  else if ( format == QLatin1String( "DMS" ) )
127  else
128  return QgsCoordinateFormatter::asPair( geo.x(), geo.y(), precision );
129  }
130  else
131  {
132  // coordinates in map units
133  return QgsCoordinateFormatter::asPair( point.x(), point.y(), precision );
134  }
135 }
136 
137 QString QgsCoordinateUtils::formatExtentForProject( QgsProject *project, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &destCrs, int precision )
138 {
139  const QgsPointXY p1( extent.xMinimum(), extent.yMinimum() );
140  const QgsPointXY p2( extent.xMaximum(), extent.yMaximum() );
141  return QStringLiteral( "%1 : %2" ).arg( QgsCoordinateUtils::formatCoordinateForProject( project, p1, destCrs, precision ),
142  QgsCoordinateUtils::formatCoordinateForProject( project, p2, destCrs, precision ) );
143 }
144 
145 double QgsCoordinateUtils::degreeToDecimal( const QString &string, bool *ok, bool *isEasting )
146 {
147  const QString negative( QStringLiteral( "swSW" ) );
148  const QString easting( QStringLiteral( "eEwW" ) );
149  double value = 0.0;
150  bool okValue = false;
151 
152  if ( ok )
153  {
154  *ok = false;
155  }
156  else
157  {
158  ok = &okValue;
159  }
160 
161  QRegularExpression degreeWithSuffix( QStringLiteral( "^\\s*([0-9\\-\\.]*)\\s*([NSEWnsew])\\s*$" ) );
162  QRegularExpressionMatch match = degreeWithSuffix.match( string );
163  if ( match.hasMatch() )
164  {
165  const QString suffix = match.captured( 2 );
166  value = std::abs( match.captured( 1 ).toDouble( ok ) );
167  if ( ok )
168  {
169  value *= ( negative.contains( suffix ) ? -1 : 1 );
170  if ( isEasting )
171  {
172  *isEasting = easting.contains( suffix );
173  }
174  }
175  }
176  return value;
177 }
178 
179 double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok, bool *isEasting )
180 {
181  const QString negative( QStringLiteral( "swSW-" ) );
182  const QString easting( QStringLiteral( "eEwW" ) );
183  double value = 0.0;
184  bool okValue = false;
185 
186  if ( ok )
187  {
188  *ok = false;
189  }
190  else
191  {
192  ok = &okValue;
193  }
194 
195  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 );
196  const QRegularExpressionMatch match = dms.match( string.trimmed() );
197  if ( match.hasMatch() )
198  {
199  const QString dms1 = match.captured( 2 );
200  const QString dms2 = match.captured( 3 );
201  const QString dms3 = match.captured( 4 );
202 
203  double v = dms3.toDouble( ok );
204  if ( *ok == false )
205  return value;
206  // Allow for Degrees/minutes format as well as DMS
207  if ( !dms2.isEmpty() )
208  {
209  v = dms2.toInt( ok ) + v / 60.0;
210  if ( *ok == false )
211  return value;
212  }
213  v = dms1.toInt( ok ) + v / 60.0;
214  if ( *ok == false )
215  return value;
216 
217  const QString sign1 = match.captured( 1 );
218  const QString sign2 = match.captured( 5 );
219 
220  if ( sign1.isEmpty() )
221  {
222  value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
223  if ( isEasting )
224  {
225  *isEasting = easting.contains( sign2 );
226  }
227  }
228  else if ( sign2.isEmpty() )
229  {
230  value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
231  if ( isEasting )
232  {
233  *isEasting = easting.contains( sign2 );
234  }
235  }
236  else
237  {
238  *ok = false;
239  }
240  }
241  return value;
242 }
243 
@ FormatDegreesMinutes
Degrees and decimal minutes, eg 30degrees 45.55'.
@ FormatDegreesMinutesSeconds
Degrees, minutes and seconds, eg 30 degrees 45'30".
static QString asPair(double x, double y, int precision=12)
Formats coordinates as an "\a x,\a y" pair, with optional decimal precision (number of decimal places...
@ 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 format(const QgsPointXY &point, Format format, int precision=12, FormatFlags flags=FlagDegreesUseStringSuffix)
Formats a point according to the specified parameters.
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
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
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:101
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:470
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.
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
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1578
const QgsCoordinateReferenceSystem & crs
int precision