QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
Loading...
Searching...
No Matches
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:4062
@ Degrees
Degrees, for planar geographic CRS distance measurements.
CoordinateOrder
Order of coordinates.
Definition qgis.h:1919
@ 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.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
@ 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
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.
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.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
double xMaximum() const
Returns the x maximum value (right side of rectangle).
double yMaximum() const
Returns the y maximum value (top side of rectangle).
#define BUILTIN_UNREACHABLE
Definition qgis.h:5790
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5144
const QgsCoordinateReferenceSystem & crs
int precision