QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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"
22#include "qgsproject.h"
23#include "qgis.h"
24#include "qgsexception.h"
26#include "qgsrectangle.h"
29
30#include <QLocale>
31#include <QRegularExpression>
32
34
35int 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 bool formatGeographic = project->displaySettings()->coordinateType() == Qgis::CoordinateDisplayType::MapGeographic ||
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 {
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
85int QgsCoordinateUtils::calculateCoordinatePrecisionForCrs( const QgsCoordinateReferenceSystem &crs, QgsProject *project )
86{
87 QgsProject *prj = project;
88 if ( !prj )
89 {
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
99 return calculateCoordinatePrecision( crs );
100}
101
102int QgsCoordinateUtils::calculateCoordinatePrecision( const QgsCoordinateReferenceSystem &crs )
103{
104 const Qgis::DistanceUnit unit = crs.mapUnits();
105 if ( unit == Qgis::DistanceUnit::Degrees )
106 {
107 return 8;
108 }
109 else
110 {
111 return 3;
112 }
113}
114
115QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision )
116{
117 if ( !project )
118 return QString();
119
120 QString formattedX;
121 QString formattedY;
122 formatCoordinatePartsForProject( project, point, destCrs, precision, formattedX, formattedY );
123
124 if ( formattedX.isEmpty() || formattedY.isEmpty() )
125 return QString();
126
127 const Qgis::CoordinateOrder axisOrder = project->displaySettings()->coordinateAxisOrder();
128
130 if ( !crs.isValid() && !destCrs.isValid() )
131 {
132 return QString();
133 }
134 else if ( !crs.isValid() )
135 {
136 crs = destCrs;
137 }
138
140 switch ( order )
141 {
144 return QStringLiteral( "%1%2 %3" ).arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
145
147 return QStringLiteral( "%1%2 %3" ).arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
148 }
150}
151
152void QgsCoordinateUtils::formatCoordinatePartsForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision, QString &x, QString &y )
153{
154 x.clear();
155 y.clear();
156 if ( !project )
157 return;
158
160 if ( !crs.isValid() && !destCrs.isValid() )
161 {
162 return;
163 }
164 else if ( !crs.isValid() )
165 {
166 crs = destCrs;
167 }
168
169 QgsPointXY p = point;
170 const bool isGeographic = crs.isGeographic();
171 if ( destCrs != crs )
172 {
173 const QgsCoordinateTransform ct( destCrs, crs, project );
174 try
175 {
176 p = ct.transform( point );
177 }
178 catch ( QgsCsException & )
179 {
180 return;
181 }
182 }
183
184 if ( isGeographic )
185 {
186 std::unique_ptr< QgsGeographicCoordinateNumericFormat > format( project->displaySettings()->geographicCoordinateFormat()->clone() );
187 format->setNumberDecimalPlaces( precision );
188
191 x = format->formatDouble( p.x(), context );
193 y = format->formatDouble( p.y(), context );
194 }
195 else
196 {
197 // coordinates in map units
198 x = QgsCoordinateFormatter::formatAsPair( p.x(), precision );
199 y = QgsCoordinateFormatter::formatAsPair( p.y(), precision );
200 }
201}
202
203QString QgsCoordinateUtils::formatExtentForProject( QgsProject *project, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &destCrs, int precision )
204{
205 const QgsPointXY p1( extent.xMinimum(), extent.yMinimum() );
206 const QgsPointXY p2( extent.xMaximum(), extent.yMaximum() );
207 return QStringLiteral( "%1 : %2" ).arg( QgsCoordinateUtils::formatCoordinateForProject( project, p1, destCrs, precision ),
208 QgsCoordinateUtils::formatCoordinateForProject( project, p2, destCrs, precision ) );
209}
210
211double QgsCoordinateUtils::degreeToDecimal( const QString &string, bool *ok, bool *isEasting )
212{
213 const QString negative( QStringLiteral( "swSW" ) );
214 const QString easting( QStringLiteral( "eEwW" ) );
215 double value = 0.0;
216 bool okValue = false;
217
218 if ( ok )
219 {
220 *ok = false;
221 }
222 else
223 {
224 ok = &okValue;
225 }
226
227 const QLocale locale;
228 QRegularExpression degreeWithSuffix( QStringLiteral( "^\\s*([-]?\\d{1,3}(?:[\\.\\%1]\\d+)?)\\s*([NSEWnsew])\\s*$" )
229 .arg( locale.decimalPoint() ) );
230 QRegularExpressionMatch match = degreeWithSuffix.match( string );
231 if ( match.hasMatch() )
232 {
233 const QString suffix = match.captured( 2 );
234 value = std::abs( match.captured( 1 ).toDouble( ok ) );
235 if ( *ok == false )
236 {
237 value = std::abs( locale.toDouble( match.captured( 1 ), ok ) );
238 }
239 if ( *ok )
240 {
241 value *= ( negative.contains( suffix ) ? -1 : 1 );
242 if ( isEasting )
243 {
244 *isEasting = easting.contains( suffix );
245 }
246 }
247 }
248 return value;
249}
250
251double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok, bool *isEasting )
252{
253 const QString negative( QStringLiteral( "swSW-" ) );
254 const QString easting( QStringLiteral( "eEwW" ) );
255 double value = 0.0;
256 bool okValue = false;
257
258 if ( ok )
259 {
260 *ok = false;
261 }
262 else
263 {
264 ok = &okValue;
265 }
266
267 const QLocale locale;
268 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*$" )
269 .arg( locale.decimalPoint() ), QRegularExpression::CaseInsensitiveOption );
270 const QRegularExpressionMatch match = dms.match( string.trimmed() );
271 if ( match.hasMatch() )
272 {
273 const QString dms1 = match.captured( 2 );
274 const QString dms2 = match.captured( 3 );
275 const QString dms3 = match.captured( 4 );
276
277 double v = dms3.toDouble( ok );
278 if ( *ok == false )
279 {
280 v = locale.toDouble( dms3, ok );
281 if ( *ok == false )
282 return value;
283 }
284 // Allow for Degrees/minutes format as well as DMS
285 if ( !dms2.isEmpty() )
286 {
287 v = dms2.toInt( ok ) + v / 60.0;
288 if ( *ok == false )
289 return value;
290 }
291 v = dms1.toInt( ok ) + v / 60.0;
292 if ( *ok == false )
293 return value;
294
295 const QString sign1 = match.captured( 1 );
296 const QString sign2 = match.captured( 5 );
297
298 if ( sign1.isEmpty() )
299 {
300 value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
301 if ( isEasting )
302 {
303 *isEasting = easting.contains( sign2 );
304 }
305 }
306 else if ( sign2.isEmpty() )
307 {
308 value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
309 if ( isEasting )
310 {
311 *isEasting = easting.contains( sign2 );
312 }
313 }
314 else
315 {
316 *ok = false;
317 }
318 }
319 return value;
320}
321
@ MapGeographic
Map Geographic CRS equivalent (stays unchanged if the map CRS is geographic)
DistanceUnit
Units of distance.
Definition: qgis.h:4124
@ Degrees
Degrees, for planar geographic CRS distance measurements.
CoordinateOrder
Order of coordinates.
Definition: qgis.h:1940
@ 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 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 Qgis::DistanceUnit mapUnits
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:67
@ 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:60
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
const QgsGeographicCoordinateNumericFormat * geographicCoordinateFormat() const
Returns the project's geographic coordinate format, which controls how geographic coordinates associa...
QgsCoordinateReferenceSystem coordinateCustomCrs
Qgis::CoordinateOrder coordinateAxisOrder
Qgis::CoordinateDisplayType coordinateType
QgsCoordinateReferenceSystem coordinateCrs
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:107
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:481
QgsProjectDisplaySettings * displaySettings
Definition: qgsproject.h:126
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 xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:201
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:211
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:196
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:206
#define BUILTIN_UNREACHABLE
Definition: qgis.h:5853
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:5207
const QgsCoordinateReferenceSystem & crs
int precision