QGIS API Documentation 3.99.0-Master (357b655ed83)
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
20#include "qgis.h"
26#include "qgsexception.h"
27#include "qgsproject.h"
29#include "qgsrectangle.h"
30
31#include <QLocale>
32#include <QRegularExpression>
33#include <QString>
34
35#include "moc_qgscoordinateutils.cpp"
36
37using namespace Qt::StringLiterals;
38
40
41int QgsCoordinateUtils::calculateCoordinatePrecision( double mapUnitsPerPixel, const QgsCoordinateReferenceSystem &mapCrs, QgsProject *project )
42{
43 if ( !project )
44 project = QgsProject::instance(); // skip-keyword-check
45 // Get the display precision from the project settings
46 const bool automatic = project->readBoolEntry( u"PositionPrecision"_s, u"/Automatic"_s );
47 int dp = 0;
48
49 if ( automatic )
50 {
51 const bool formatGeographic = project->displaySettings()->coordinateType() == Qgis::CoordinateDisplayType::MapGeographic ||
52 ( project->displaySettings()->coordinateType() == Qgis::CoordinateDisplayType::CustomCrs &&
54
55 // we can only calculate an automatic precision if one of these is true:
56 // - both map CRS and format are geographic
57 // - both map CRS and format are not geographic
58 // - map CRS is geographic but format is not geographic (i.e. map units)
59 if ( mapCrs.isGeographic() || !formatGeographic )
60 {
61 // Work out a suitable number of decimal places for the coordinates with the aim of always
62 // having enough decimal places to show the difference in position between adjacent pixels.
63 // Also avoid taking the log of 0.
64 if ( !qgsDoubleNear( mapUnitsPerPixel, 0.0 ) )
65 dp = static_cast<int>( std::ceil( -1.0 * std::log10( mapUnitsPerPixel ) ) );
66 }
67 else
68 {
70 {
73 dp = 2;
74 break;
76 dp = 4;
77 break;
78 }
79 }
80 }
81 else
82 dp = project->readNumEntry( u"PositionPrecision"_s, u"/DecimalPlaces"_s );
83
84 // Keep dp sensible
85 if ( dp < 0 )
86 dp = 0;
87
88 return dp;
89}
90
91int QgsCoordinateUtils::calculateCoordinatePrecisionForCrs( const QgsCoordinateReferenceSystem &crs, QgsProject *project )
92{
93 QgsProject *prj = project;
94 if ( !prj )
95 {
96 prj = QgsProject::instance(); // skip-keyword-check
97 }
98
99 const bool automatic = prj->readBoolEntry( u"PositionPrecision"_s, u"/Automatic"_s );
100 if ( !automatic )
101 {
102 return prj->readNumEntry( u"PositionPrecision"_s, u"/DecimalPlaces"_s, 6 );
103 }
104
105 return calculateCoordinatePrecision( crs );
106}
107
108int QgsCoordinateUtils::calculateCoordinatePrecision( const QgsCoordinateReferenceSystem &crs )
109{
110 const Qgis::DistanceUnit unit = crs.mapUnits();
111 if ( unit == Qgis::DistanceUnit::Degrees )
112 {
113 return 8;
114 }
115 else
116 {
117 return 3;
118 }
119}
120
121QString QgsCoordinateUtils::formatCoordinateForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision )
122{
123 if ( !project )
124 return QString();
125
126 QString formattedX;
127 QString formattedY;
128 formatCoordinatePartsForProject( project, point, destCrs, precision, formattedX, formattedY );
129
130 if ( formattedX.isEmpty() || formattedY.isEmpty() )
131 return QString();
132
133 const Qgis::CoordinateOrder axisOrder = project->displaySettings()->coordinateAxisOrder();
134
136 if ( !crs.isValid() && !destCrs.isValid() )
137 {
138 return u"%1%2 %3"_s.arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
139 }
140 else if ( !crs.isValid() )
141 {
142 crs = destCrs;
143 }
144
146 switch ( order )
147 {
150 return u"%1%2 %3"_s.arg( formattedX, QgsCoordinateFormatter::separator(), formattedY );
151
153 return u"%1%2 %3"_s.arg( formattedY, QgsCoordinateFormatter::separator(), formattedX );
154 }
156}
157
158void QgsCoordinateUtils::formatCoordinatePartsForProject( QgsProject *project, const QgsPointXY &point, const QgsCoordinateReferenceSystem &destCrs, int precision, QString &x, QString &y )
159{
160 x.clear();
161 y.clear();
162 if ( !project )
163 return;
164
166 if ( !crs.isValid() && !destCrs.isValid() )
167 {
168 x = QgsCoordinateFormatter::formatAsPair( point.x(), precision );
169 y = QgsCoordinateFormatter::formatAsPair( point.y(), precision );
170 return;
171 }
172 else if ( !crs.isValid() )
173 {
174 crs = destCrs;
175 }
176
177 QgsPointXY p = point;
178 const bool isGeographic = crs.isGeographic();
179 if ( destCrs != crs )
180 {
181 const QgsCoordinateTransform ct( destCrs, crs, project );
182 try
183 {
184 p = ct.transform( point );
185 }
186 catch ( QgsCsException & )
187 {
188 return;
189 }
190 }
191
192 if ( isGeographic )
193 {
194 std::unique_ptr< QgsGeographicCoordinateNumericFormat > format( project->displaySettings()->geographicCoordinateFormat()->clone() );
195 format->setNumberDecimalPlaces( precision );
196
199 x = format->formatDouble( p.x(), context );
201 y = format->formatDouble( p.y(), context );
202 }
203 else
204 {
205 // coordinates in map units
206 x = QgsCoordinateFormatter::formatAsPair( p.x(), precision );
207 y = QgsCoordinateFormatter::formatAsPair( p.y(), precision );
208 }
209}
210
211QString QgsCoordinateUtils::formatExtentForProject( QgsProject *project, const QgsRectangle &extent, const QgsCoordinateReferenceSystem &destCrs, int precision )
212{
213 const QgsPointXY p1( extent.xMinimum(), extent.yMinimum() );
214 const QgsPointXY p2( extent.xMaximum(), extent.yMaximum() );
215 return u"%1 : %2"_s.arg( QgsCoordinateUtils::formatCoordinateForProject( project, p1, destCrs, precision ),
216 QgsCoordinateUtils::formatCoordinateForProject( project, p2, destCrs, precision ) );
217}
218
219double QgsCoordinateUtils::degreeToDecimal( const QString &string, bool *ok, bool *isEasting )
220{
221 const QString negative( u"swSW"_s );
222 const QString easting( u"eEwW"_s );
223 double value = 0.0;
224 bool okValue = false;
225
226 if ( ok )
227 {
228 *ok = false;
229 }
230 else
231 {
232 ok = &okValue;
233 }
234
235 const QLocale locale;
236 QRegularExpression degreeWithSuffix( u"^\\s*([-]?\\d{1,3}(?:[\\.\\%1]\\d+)?)\\s*([NSEWnsew])\\s*$"_s
237 .arg( locale.decimalPoint() ) );
238 QRegularExpressionMatch match = degreeWithSuffix.match( string );
239 if ( match.hasMatch() )
240 {
241 const QString suffix = match.captured( 2 );
242 value = std::abs( match.captured( 1 ).toDouble( ok ) );
243 if ( *ok == false )
244 {
245 value = std::abs( locale.toDouble( match.captured( 1 ), ok ) );
246 }
247 if ( *ok )
248 {
249 value *= ( negative.contains( suffix ) ? -1 : 1 );
250 if ( isEasting )
251 {
252 *isEasting = easting.contains( suffix );
253 }
254 }
255 }
256 return value;
257}
258
259double QgsCoordinateUtils::dmsToDecimal( const QString &string, bool *ok, bool *isEasting )
260{
261 const QString negative( u"swSW-"_s );
262 const QString easting( u"eEwW"_s );
263 double value = 0.0;
264 bool okValue = false;
265
266 if ( ok )
267 {
268 *ok = false;
269 }
270 else
271 {
272 ok = &okValue;
273 }
274
275 const QLocale locale;
276 const QRegularExpression dms( u"^\\s*(?:([-+nsew])\\s*)?(\\d{1,3})(?:[^0-9.]+([0-5]?\\d))?[^0-9.]+([0-5]?\\d(?:[\\.\\%1]\\d+)?)[^0-9.,]*?([-+nsew])?\\s*$"_s
277 .arg( locale.decimalPoint() ), QRegularExpression::CaseInsensitiveOption );
278 const QRegularExpressionMatch match = dms.match( string.trimmed() );
279 if ( match.hasMatch() )
280 {
281 const QString dms1 = match.captured( 2 );
282 const QString dms2 = match.captured( 3 );
283 const QString dms3 = match.captured( 4 );
284
285 double v = dms3.toDouble( ok );
286 if ( *ok == false )
287 {
288 v = locale.toDouble( dms3, ok );
289 if ( *ok == false )
290 return value;
291 }
292 // Allow for Degrees/minutes format as well as DMS
293 if ( !dms2.isEmpty() )
294 {
295 v = dms2.toInt( ok ) + v / 60.0;
296 if ( *ok == false )
297 return value;
298 }
299 v = dms1.toInt( ok ) + v / 60.0;
300 if ( *ok == false )
301 return value;
302
303 const QString sign1 = match.captured( 1 );
304 const QString sign2 = match.captured( 5 );
305
306 if ( sign1.isEmpty() )
307 {
308 value = !sign2.isEmpty() && negative.contains( sign2 ) ? -v : v;
309 if ( isEasting )
310 {
311 *isEasting = easting.contains( sign2 );
312 }
313 }
314 else if ( sign2.isEmpty() )
315 {
316 value = !sign1.isEmpty() && negative.contains( sign1 ) ? -v : v;
317 if ( isEasting )
318 {
319 *isEasting = easting.contains( sign1 );
320 }
321 }
322 else
323 {
324 *ok = false;
325 }
326 }
327 return value;
328}
329
@ MapGeographic
Map Geographic CRS equivalent (stays unchanged if the map CRS is geographic).
Definition qgis.h:4548
@ CustomCrs
Custom CRS.
Definition qgis.h:4549
DistanceUnit
Units of distance.
Definition qgis.h:5120
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5127
CoordinateOrder
Order of coordinates.
Definition qgis.h:2460
@ XY
Easting/Northing (or Longitude/Latitude for geographic CRS).
Definition qgis.h:2462
@ Default
Respect the default axis ordering for the CRS, as defined in the CRS's parameters.
Definition qgis.h:2461
@ YX
Northing/Easting (or Latitude/Longitude for geographic CRS).
Definition qgis.h:2463
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.
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Handles coordinate transforms between two coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
@ DegreesMinutes
Degrees and decimal minutes, eg 30 degrees 45.55'.
@ 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.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
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:113
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:133
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:7524
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6935