QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsprojutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsprojutils.h
3  -------------------
4  begin : March 2019
5  copyright : (C) 2019 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 #include "qgsprojutils.h"
18 #include "qgis.h"
19 #include "qgscoordinatetransform.h"
20 #include "qgsexception.h"
21 #include <QString>
22 #include <QSet>
23 #include <QRegularExpression>
24 #include <QDate>
25 
26 #include <proj.h>
27 
28 #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
29 thread_local QgsProjContext QgsProjContext::sProjContext;
30 #else
31 QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
32 #endif
33 
35 {
36  mContext = proj_context_create();
37 }
38 
40 {
41  // Call removeFromCacheObjectsBelongingToCurrentThread() before
42  // destroying the context
43  QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
44  QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
45  proj_context_destroy( mContext );
46 }
47 
49 {
50 #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
51  return sProjContext.mContext;
52 #else
53  PJ_CONTEXT *pContext = nullptr;
54  if ( sProjContext.hasLocalData() )
55  {
56  pContext = sProjContext.localData()->mContext;
57  }
58  else
59  {
60  sProjContext.setLocalData( new QgsProjContext() );
61  pContext = sProjContext.localData()->mContext;
62  }
63  return pContext;
64 #endif
65 }
66 
68 {
69  proj_destroy( object );
70 }
71 
72 bool QgsProjUtils::usesAngularUnit( const QString &projDef )
73 {
74  const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
75  PJ_CONTEXT *context = QgsProjContext::get();
76  QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
77  if ( !projSingleOperation )
78  return false;
79 
80  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
81  if ( !coordinateSystem )
82  return false;
83 
84  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
85  if ( axisCount > 0 )
86  {
87  const char *outUnitAuthName = nullptr;
88  const char *outUnitAuthCode = nullptr;
89  // Read only first axis
90  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
91  nullptr,
92  nullptr,
93  nullptr,
94  nullptr,
95  nullptr,
96  &outUnitAuthName,
97  &outUnitAuthCode );
98 
99  if ( outUnitAuthName && outUnitAuthCode )
100  {
101  const char *unitCategory = nullptr;
102  if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
103  {
104  return QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
105  }
106  }
107  }
108  return false;
109 }
110 
112 {
113  //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
114  if ( !crs )
115  return false;
116 
117  PJ_CONTEXT *context = QgsProjContext::get();
118  QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
119  if ( !pjCs )
120  return false;
121 
122  const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
123  if ( axisCount > 0 )
124  {
125  const char *outDirection = nullptr;
126  // Read only first axis, see if it is degrees / north
127 
128  proj_cs_get_axis_info( context, pjCs.get(), 0,
129  nullptr,
130  nullptr,
131  &outDirection,
132  nullptr,
133  nullptr,
134  nullptr,
135  nullptr
136  );
137  return QString( outDirection ).compare( QLatin1String( "north" ), Qt::CaseInsensitive ) == 0;
138  }
139  return false;
140 }
141 
143 {
144  // ported from GDAL OGRSpatialReference::IsDynamic()
145  bool isDynamic = false;
146  PJ_CONTEXT *context = QgsProjContext::get();
147 
149 
150  proj_pj_unique_ptr datum( horiz ? proj_crs_get_datum( context, horiz.get() ) : nullptr );
151  if ( datum )
152  {
153  const PJ_TYPE type = proj_get_type( datum.get() );
154  isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
155  type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
156  if ( !isDynamic )
157  {
158  const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
159  const QString code( proj_get_id_code( datum.get(), 0 ) );
160  if ( authName == QLatin1String( "EPSG" ) && code == QLatin1String( "6326" ) )
161  {
162  isDynamic = true;
163  }
164  }
165  }
166 #if PROJ_VERSION_MAJOR > 7 || (PROJ_VERSION_MAJOR == 7 && PROJ_VERSION_MINOR >= 2)
167  else
168  {
169  proj_pj_unique_ptr ensemble( horiz ? proj_crs_get_datum_ensemble( context, horiz.get() ) : nullptr );
170  if ( ensemble )
171  {
172  proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
173  if ( member )
174  {
175  const PJ_TYPE type = proj_get_type( member.get() );
176  isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
177  type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
178  }
179  }
180  }
181 #endif
182  return isDynamic;
183 }
184 
186 {
187  if ( !crs )
188  return nullptr;
189 
190  PJ_CONTEXT *context = QgsProjContext::get();
191  switch ( proj_get_type( crs ) )
192  {
193  case PJ_TYPE_BOUND_CRS:
194  return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
195 
196  case PJ_TYPE_COMPOUND_CRS:
197  {
198  int i = 0;
199  QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
200  while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
201  {
202  i++;
203  res.reset( proj_crs_get_sub_crs( context, crs, i ) );
204  }
205  return res;
206  }
207 
208  // maybe other types to handle??
209 
210  default:
211  return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
212  }
213 
214 #ifndef _MSC_VER // unreachable
215  return nullptr;
216 #endif
217 }
218 
220 {
221  if ( !crs )
222  return nullptr;
223 
224 #if PROJ_VERSION_MAJOR>=8
225  PJ_CONTEXT *context = QgsProjContext::get();
227  if ( !singleCrs )
228  return nullptr;
229 
230  return QgsProjUtils::proj_pj_unique_ptr( proj_crs_get_datum_ensemble( context, singleCrs.get() ) );
231 #else
232  throw QgsNotSupportedException( QStringLiteral( "Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
233 #endif
234 }
235 
236 bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
237 {
238  authName.clear();
239  authCode.clear();
240 
241  if ( !crs )
242  return false;
243 
244  int *confidence = nullptr;
245  if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
246  {
247  const int count = proj_list_get_count( crsList );
248  int bestConfidence = 0;
250  for ( int i = 0; i < count; ++i )
251  {
252  if ( confidence[i] >= bestConfidence )
253  {
254  QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
255  switch ( proj_get_type( candidateCrs.get() ) )
256  {
257  case PJ_TYPE_BOUND_CRS:
258  // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
259  // consider them a candidate for a match here (depending on the identify flags, that is!)
261  break;
262  else
263  continue;
264 
265  default:
266  break;
267  }
268 
269  candidateCrs = QgsProjUtils::crsToSingleCrs( candidateCrs.get() );
270  const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
271  // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
272  if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String( "EPSG" ) ) )
273  {
274  bestConfidence = confidence[i];
275  matchedCrs = std::move( candidateCrs );
276  }
277  }
278  }
279  proj_list_destroy( crsList );
280  proj_int_list_destroy( confidence );
281  if ( matchedCrs && bestConfidence >= 70 )
282  {
283  authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
284  authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
285  }
286  }
287  return !authName.isEmpty() && !authCode.isEmpty();
288 }
289 
290 bool QgsProjUtils::coordinateOperationIsAvailable( const QString &projDef )
291 {
292  if ( projDef.isEmpty() )
293  return true;
294 
295  PJ_CONTEXT *context = QgsProjContext::get();
296  QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
297  if ( !coordinateOperation )
298  return false;
299 
300  return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
301 }
302 
303 QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
304 {
305  const thread_local QRegularExpression regex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
306 
307  QList< QgsDatumTransform::GridDetails > grids;
308  QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
309  while ( matches.hasNext() )
310  {
311  const QRegularExpressionMatch match = matches.next();
312  const QString gridName = match.captured( 1 );
314  grid.shortName = gridName;
315  const char *fullName = nullptr;
316  const char *packageName = nullptr;
317  const char *url = nullptr;
318  int directDownload = 0;
319  int openLicense = 0;
320  int available = 0;
321  proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
322  grid.fullName = QString( fullName );
323  grid.packageName = QString( packageName );
324  grid.url = QString( url );
325  grid.directDownload = directDownload;
326  grid.openLicense = openLicense;
327  grid.isAvailable = available;
328  grids.append( grid );
329  }
330  return grids;
331 }
332 
333 #if 0
334 QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
335 {
336  if ( projDef.isEmpty() )
337  return QStringList();
338 
339  PJ_CONTEXT *context = QgsProjContext::get();
340  QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
341  if ( !op )
342  return QStringList();
343 
344  QStringList res;
345  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
346  {
347  const char *shortName = nullptr;
348  int isAvailable = 0;
349  proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
350  if ( !isAvailable )
351  res << QString( shortName );
352  }
353  return res;
354 }
355 #endif
356 
358 {
359  return PROJ_VERSION_MAJOR;
360 }
361 
363 {
364  return PROJ_VERSION_MINOR;
365 }
366 
368 {
369  PJ_CONTEXT *context = QgsProjContext::get();
370  const char *version = proj_context_get_database_metadata( context, "EPSG.VERSION" );
371  return QString( version );
372 }
373 
375 {
376  PJ_CONTEXT *context = QgsProjContext::get();
377  const char *date = proj_context_get_database_metadata( context, "EPSG.DATE" );
378  return QDate::fromString( date, Qt::DateFormat::ISODate );
379 }
380 
382 {
383  PJ_CONTEXT *context = QgsProjContext::get();
384  const char *version = proj_context_get_database_metadata( context, "ESRI.VERSION" );
385  return QString( version );
386 }
387 
389 {
390  PJ_CONTEXT *context = QgsProjContext::get();
391  const char *date = proj_context_get_database_metadata( context, "ESRI.DATE" );
392  return QDate::fromString( date, Qt::DateFormat::ISODate );
393 }
394 
396 {
397  PJ_CONTEXT *context = QgsProjContext::get();
398  const char *version = proj_context_get_database_metadata( context, "IGNF.VERSION" );
399  return QString( version );
400 }
401 
403 {
404  PJ_CONTEXT *context = QgsProjContext::get();
405  const char *date = proj_context_get_database_metadata( context, "IGNF.DATE" );
406  return QDate::fromString( date, Qt::DateFormat::ISODate );
407 }
408 
410 {
411  const QString path( proj_info().searchpath );
412  QStringList paths;
413 #ifdef Q_OS_WIN
414  paths = path.split( ';' );
415 #else
416  paths = path.split( ':' );
417 #endif
418 
419  QSet<QString> existing;
420  // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
421  QStringList res;
422  res.reserve( paths.count() );
423  for ( const QString &p : std::as_const( paths ) )
424  {
425  if ( existing.contains( p ) )
426  continue;
427 
428  existing.insert( p );
429  res << p;
430  }
431  return res;
432 }
QgsProjContext::get
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
Definition: qgsprojutils.cpp:48
QgsProjUtils::axisOrderIsSwapped
static bool axisOrderIsSwapped(const PJ *crs)
Returns true if the given proj coordinate system uses requires y/x coordinate order instead of x/y.
Definition: qgsprojutils.cpp:111
QgsDatumTransform::GridDetails::directDownload
bool directDownload
true if direct download of grid is possible
Definition: qgsdatumtransform.h:143
QgsProjUtils::ignfDatabaseDate
static QDate ignfDatabaseDate()
Returns the IGNF database release date used by the proj library.
Definition: qgsprojutils.cpp:402
QgsProjUtils::projVersionMinor
static int projVersionMinor()
Returns the proj library minor version number.
Definition: qgsprojutils.cpp:362
QgsDatumTransform::GridDetails::openLicense
bool openLicense
true if grid is available under an open license
Definition: qgsdatumtransform.h:145
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:105
QgsProjUtils::crsToDatumEnsemble
static proj_pj_unique_ptr crsToDatumEnsemble(const PJ *crs)
Given a PROJ crs, attempt to retrieve the datum ensemble from it.
Definition: qgsprojutils.cpp:219
PJ_CONTEXT
struct projCtx_t PJ_CONTEXT
Definition: qgscoordinatereferencesystem.h:56
qgis.h
QgsProjUtils::proj_pj_unique_ptr
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Definition: qgsprojutils.h:141
QgsProjUtils::gridsUsed
static QList< QgsDatumTransform::GridDetails > gridsUsed(const QString &proj)
Returns a list of grids used by the given proj string.
Definition: qgsprojutils.cpp:303
QgsProjUtils::esriDatabaseDate
static QDate esriDatabaseDate()
Returns the ESRI projection engine database release date used by the proj library.
Definition: qgsprojutils.cpp:388
QgsDatumTransform::GridDetails
Contains information about a projection transformation grid file.
Definition: qgsdatumtransform.h:132
QgsProjUtils::crsToSingleCrs
static proj_pj_unique_ptr crsToSingleCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract a single crs fro...
Definition: qgsprojutils.cpp:185
QgsProjUtils::ignfDatabaseVersion
static QString ignfDatabaseVersion()
Returns the IGNF database version used by the proj library (e.g.
Definition: qgsprojutils.cpp:395
QgsDatumTransform::GridDetails::url
QString url
Url to download grid from.
Definition: qgsdatumtransform.h:141
QgsProjUtils::isDynamic
static bool isDynamic(const PJ *crs)
Returns true if the given proj coordinate system is a dynamic CRS.
Definition: qgsprojutils.cpp:142
QgsProjUtils::epsgRegistryVersion
static QString epsgRegistryVersion()
Returns the EPSG registry database version used by the proj library (e.g.
Definition: qgsprojutils.cpp:367
QgsDatumTransform::GridDetails::fullName
QString fullName
Full name of transform grid.
Definition: qgsdatumtransform.h:137
qgscoordinatetransform.h
QgsProjUtils::coordinateOperationIsAvailable
static bool coordinateOperationIsAvailable(const QString &projDef)
Returns true if a coordinate operation (specified via proj string) is available.
Definition: qgsprojutils.cpp:290
QgsDatumTransform::GridDetails::shortName
QString shortName
Short name of transform grid.
Definition: qgsdatumtransform.h:135
QgsProjUtils::FlagMatchBoundCrsToUnderlyingSourceCrs
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
Definition: qgsprojutils.h:121
QgsProjUtils::esriDatabaseVersion
static QString esriDatabaseVersion()
Returns the ESRI projection engine database version used by the proj library (e.g.
Definition: qgsprojutils.cpp:381
QgsProjUtils::projVersionMajor
static int projVersionMajor()
Returns the proj library major version number.
Definition: qgsprojutils.cpp:357
qgsprojutils.h
QgsProjUtils::usesAngularUnit
static bool usesAngularUnit(const QString &projDef)
Returns true if the given proj coordinate system uses angular units.
Definition: qgsprojutils.cpp:72
QgsDatumTransform::GridDetails::isAvailable
bool isAvailable
true if grid is currently available for use
Definition: qgsdatumtransform.h:147
QgsProjContext::QgsProjContext
QgsProjContext()
Definition: qgsprojutils.cpp:34
qgsexception.h
QgsDatumTransform::GridDetails::packageName
QString packageName
Name of package the grid is included within.
Definition: qgsdatumtransform.h:139
QgsNotSupportedException
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:117
QgsProjUtils::searchPaths
static QStringList searchPaths()
Returns the current list of Proj file search paths.
Definition: qgsprojutils.cpp:409
QgsProjContext::~QgsProjContext
~QgsProjContext()
Definition: qgsprojutils.cpp:39
QgsProjUtils::identifyCrs
static bool identifyCrs(const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags=IdentifyFlags())
Attempts to identify a crs, matching it to a known authority and code within an acceptable level of t...
Definition: qgsprojutils.cpp:236
QgsProjContext
Used to create and store a proj context object, correctly freeing the context upon destruction.
Definition: qgsprojutils.h:230
QgsProjUtils::epsgRegistryDate
static QDate epsgRegistryDate()
Returns the EPSG registry database release date used by the proj library.
Definition: qgsprojutils.cpp:374
PJ
struct PJconsts PJ
Definition: qgscoordinatereferencesystem.h:49
QgsProjUtils::ProjPJDeleter::operator()
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.
Definition: qgsprojutils.cpp:67