QGIS API Documentation 3.41.0-Master (af5edcb665c)
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"
19#include "moc_qgscoordinateutils.cpp"
23#include "qgsproject.h"
24#include "qgis.h"
25#include "qgsexception.h"
27#include "qgsrectangle.h"
30
31#include <QLocale>
32#include <QRegularExpression>
33
35
36int QgsCoordinateUtils::calculateCoordinatePrecision( double mapUnitsPerPixel, const QgsCoordinateReferenceSystem &mapCrs, QgsProject *project )
37{
38 if ( !project )
39 project = QgsProject::instance(); // skip-keyword-check
40 // Get the display precision from the project settings
41 const bool automatic = project->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
42 int dp = 0;
43
44 if ( automatic )
45 {
46 const bool formatGeographic = project->displaySettings()->coordinateType() == Qgis::CoordinateDisplayType::MapGeographic ||
49
50 // we can only calculate an automatic precision if one of these is true:
51 // - both map CRS and format are geographic
52 // - both map CRS and format are not geographic
53 // - map CRS is geographic but format is not geographic (i.e. map units)
54 if ( mapCrs.isGeographic() || !formatGeographic )
55 {
56 // Work out a suitable number of decimal places for the coordinates with the aim of always
57 // having enough decimal places to show the difference in position between adjacent pixels.
58 // Also avoid taking the log of 0.
59 if ( !qgsDoubleNear( mapUnitsPerPixel, 0.0 ) )
60 dp = static_cast<int>( std::ceil( -1.0 * std::log10( mapUnitsPerPixel ) ) );
61 }
62 else
63 {
65 {
68 dp = 2;
69 break;
71 dp = 4;
72 break;
73 }
74 }
75 }
76 else
77 dp = project->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ) );
78
79 // Keep dp sensible
80 if ( dp < 0 )
81 dp = 0;
82
83 return dp;
84}
85
86int QgsCoordinateUtils::calculateCoordinatePrecisionForCrs( const QgsCoordinateReferenceSystem &crs, QgsProject *project )
87{
88 QgsProject *prj = project;
89 if ( !prj )
90 {
91 prj = QgsProject::instance(); // skip-keyword-check
92 }
93
94 const bool automatic = prj->readBoolEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/Automatic" ) );
95 if ( !automatic )
96 {
97 return prj->readNumEntry( QStringLiteral( "PositionPrecision" ), QStringLiteral( "/DecimalPlaces" ), 6 );
98 }
99
100 return calculateCoordinatePrecision( crs );
101}
102
103int QgsCoordinateUtils::calculateCoordinatePrecision( const QgsCoordinateReferenceSystem &crs )
104{
105 const Qgis::DistanceUnit unit = crs.mapUnits();
106 if ( unit == Qgis::DistanceUnit::Degrees )
107 {
108 return 8;
109 }
110 else
111 {
112 return 3;
113 }
114}
115
116QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision )
117{
118 if ( !project )
119 return QString();
120
121 QString formattedX;
122 QString formattedY;
123 formatCoordinatePartsForProject( project, point, destCrs, precision, formattedX, formattedY );
124
125 if ( formattedX.isEmpty() || formattedY.isEmpty() )
126 return QString();
127
128 const Qgis::CoordinateOrder axisOrder = project->displaySettings()->coordinateAxisOrder();
129
131 if ( !crs.isValid() && !destCrs.isValid() )
132 {
133 return QString();
134 }
135 else if ( !crs.isValid() )
136 {
137 crs = destCrs;
138 }
139
141 switch ( order )
142 {
145 return QStringLiteral( "%1%2 %3" ).arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
146
148 return QStringLiteral( "%1%2 %3" ).arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
149 }
151}
152
153void QgsCoordinateUtils::formatCoordinatePartsForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision, QString &x, QString &y )
154{
155 x.clear();
156 y.clear();
157 if ( !project )
158 return;
159
161 if ( !crs.isValid() && !destCrs.isValid() )
162 {
163 return;
164 }
165 else if ( !crs.isValid() )
166 {
167 crs = destCrs;
168 }
169
170 QgsPointXY p = point;
171 const bool isGeographic = crs.isGeographic();
172 if ( destCrs != crs )
173 {
174 const QgsCoordinateTransform ct( destCrs, crs, project );
175 try
176 {
177 p = ct.transform( point );
178 }
179 catch ( QgsCsException & )
180 {
181 return;
182 }
183 }
184
185 if ( isGeographic )
186 {
187 std::unique_ptr< QgsGeographicCoordinateNumericFormat > format( project->displaySettings()->geographicCoordinateFormat()->clone() );
188 format->setNumberDecimalPlaces( precision );
189
192 x = format->formatDouble( p.x(), context );
194 y = format->formatDouble( p.y(), context );
195 }
196 else
197 {
198 // coordinates in map units
199 x = QgsCoordinateFormatter::formatAsPair( p.x(), precision );
200 y = QgsCoordinateFormatter::formatAsPair( p.y(), precision );
201 }
202}
203
204QString QgsCoordinateUtils::formatExtentForProject( QgsProject *project, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &destCrs, int precision )
205{
206 const QgsPointXY p1( extent.xMinimum(), extent.yMinimum() );
207 const QgsPointXY p2( extent.xMaximum(), extent.yMaximum() );
208 return QStringLiteral( "%1 : %2" ).arg( QgsCoordinateUtils::formatCoordinateForProject( project, p1, destCrs, precision ),
209 QgsCoordinateUtils::formatCoordinateForProject( project, p2, destCrs, precision ) );
210}
211
212double QgsCoordinateUtils::degreeToDecimal( 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 QLocale locale;
229 QRegularExpression degreeWithSuffix( QStringLiteral( "^\\s*([-]?\\d{1,3}(?:[\\.\\%1]\\d+)?)\\s*([NSEWnsew])\\s*$" )
230 .arg( locale.decimalPoint() ) );
231 QRegularExpressionMatch match = degreeWithSuffix.match( string );
232 if ( match.hasMatch() )
233 {
234 const QString suffix = match.captured( 2 );
235 value = std::abs( match.captured( 1 ).toDouble( ok ) );
236 if ( *ok == false )
237 {
238 value = std::abs( locale.toDouble( match.captured( 1 ), ok ) );
239 }
240 if ( *ok )
241 {
242 value *= ( negative.contains( suffix ) ? -1 : 1 );
243 if ( isEasting )
244 {
245 *isEasting = easting.contains( suffix );
246 }
247 }
248 }
249 return value;
250}
251
252double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok, bool *isEasting )
253{
254 const QString negative( QStringLiteral( "swSW-" ) );
255 const QString easting( QStringLiteral( "eEwW" ) );
256 double value = 0.0;
257 bool okValue = false;
258
259 if ( ok )
260 {
261 *ok = false;
262 }
263 else
264 {
265 ok = &okValue;
266 }
267
268 const QLocale locale;
269 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*$" )
270 .arg( locale.decimalPoint() ), QRegularExpression::CaseInsensitiveOption );
271 const QRegularExpressionMatch match = dms.match( string.trimmed() );
272 if ( match.hasMatch() )
273 {
274 const QString dms1 = match.captured( 2 );
275 const QString dms2 = match.captured( 3 );
276 const QString dms3 = match.captured( 4 );
277
278 double v = dms3.toDouble( ok );
279 if ( *ok == false )
280 {
281 v = locale.toDouble( dms3, ok );
282 if ( *ok == false )
283 return value;
284 }
285 // Allow for Degrees/minutes format as well as DMS
286 if ( !dms2.isEmpty() )
287 {
288 v = dms2.toInt( ok ) + v / 60.0;
289 if ( *ok == false )
290 return value;
291 }
292 v = dms1.toInt( ok ) + v / 60.0;
293 if ( *ok == false )
294 return value;
295
296 const QString sign1 = match.captured( 1 );
297 const QString sign2 = match.captured( 5 );
298
299 if ( sign1.isEmpty() )
300 {
301 value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
302 if ( isEasting )
303 {
304 *isEasting = easting.contains( sign2 );
305 }
306 }
307 else if ( sign2.isEmpty() )
308 {
309 value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
310 if ( isEasting )
311 {
312 *isEasting = easting.contains( sign1 );
313 }
314 }
315 else
316 {
317 *ok = false;
318 }
319 }
320 return value;
321}
322
@ MapGeographic
Map Geographic CRS equivalent (stays unchanged if the map CRS is geographic)
DistanceUnit
Units of distance.
Definition qgis.h:4740
@ Degrees
Degrees, for planar geographic CRS distance measurements.
CoordinateOrder
Order of coordinates.
Definition qgis.h:2297
@ 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
double yMinimum
double xMaximum
double yMaximum
#define BUILTIN_UNREACHABLE
Definition qgis.h:6720
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:6066
const QgsCoordinateReferenceSystem & crs
int precision