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