QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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 
21 #include <QString>
22 #include <QSet>
23 #include <QRegularExpression>
24 
25 #if PROJ_VERSION_MAJOR>=6
26 #include <proj.h>
27 #else
28 #include <proj_api.h>
29 #endif
30 
31 
32 #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
33 thread_local QgsProjContext QgsProjContext::sProjContext;
34 #else
35 QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
36 #endif
37 
39 {
40 #if PROJ_VERSION_MAJOR>=6
41  mContext = proj_context_create();
42 #else
43  mContext = pj_ctx_alloc();
44 #endif
45 }
46 
48 {
49 #if PROJ_VERSION_MAJOR>=6
50  // Call removeFromCacheObjectsBelongingToCurrentThread() before
51  // destroying the context
52  QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
53  QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
54  proj_context_destroy( mContext );
55 #else
56  pj_ctx_free( mContext );
57 #endif
58 }
59 
61 {
62 #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
63  return sProjContext.mContext;
64 #else
65  PJ_CONTEXT *pContext = nullptr;
66  if ( sProjContext.hasLocalData() )
67  {
68  pContext = sProjContext.localData()->mContext;
69  }
70  else
71  {
72  sProjContext.setLocalData( new QgsProjContext() );
73  pContext = sProjContext.localData()->mContext;
74  }
75  return pContext;
76 #endif
77 }
78 
79 #if PROJ_VERSION_MAJOR>=6
80 void QgsProjUtils::ProjPJDeleter::operator()( PJ *object )
81 {
82  proj_destroy( object );
83 }
84 
85 bool QgsProjUtils::usesAngularUnit( const QString &projDef )
86 {
87  const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
88  PJ_CONTEXT *context = QgsProjContext::get();
89  QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
90  if ( !projSingleOperation )
91  return false;
92 
93  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
94  if ( !coordinateSystem )
95  return false;
96 
97  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
98  if ( axisCount > 0 )
99  {
100  const char *outUnitAuthName = nullptr;
101  const char *outUnitAuthCode = nullptr;
102  // Read only first axis
103  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
104  nullptr,
105  nullptr,
106  nullptr,
107  nullptr,
108  nullptr,
109  &outUnitAuthName,
110  &outUnitAuthCode );
111 
112  if ( outUnitAuthName && outUnitAuthCode )
113  {
114  const char *unitCategory = nullptr;
115  if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
116  {
117  return QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
118  }
119  }
120  }
121  return false;
122 }
123 
124 bool QgsProjUtils::axisOrderIsSwapped( const PJ *crs )
125 {
126  //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
127  if ( !crs )
128  return false;
129 
130  PJ_CONTEXT *context = QgsProjContext::get();
131  QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
132  if ( !pjCs )
133  return false;
134 
135  const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
136  if ( axisCount > 0 )
137  {
138  const char *outDirection = nullptr;
139  // Read only first axis, see if it is degrees / north
140 
141  proj_cs_get_axis_info( context, pjCs.get(), 0,
142  nullptr,
143  nullptr,
144  &outDirection,
145  nullptr,
146  nullptr,
147  nullptr,
148  nullptr
149  );
150  return QString( outDirection ).compare( QLatin1String( "north" ), Qt::CaseInsensitive ) == 0;
151  }
152  return false;
153 }
154 
155 
156 QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::crsToSingleCrs( const PJ *crs )
157 {
158  if ( !crs )
159  return nullptr;
160 
161  PJ_CONTEXT *context = QgsProjContext::get();
162  switch ( proj_get_type( crs ) )
163  {
164  case PJ_TYPE_BOUND_CRS:
165  return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
166 
167  case PJ_TYPE_COMPOUND_CRS:
168  {
169  int i = 0;
170  QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
171  while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
172  {
173  i++;
174  res.reset( proj_crs_get_sub_crs( context, crs, i ) );
175  }
176  return res;
177  }
178 
179  // maybe other types to handle??
180 
181  default:
182  return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
183  }
184 
185 #ifndef _MSC_VER // unreachable
186  return nullptr;
187 #endif
188 }
189 
190 bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
191 {
192  authName.clear();
193  authCode.clear();
194 
195  if ( !crs )
196  return false;
197 
198  int *confidence = nullptr;
199  if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
200  {
201  const int count = proj_list_get_count( crsList );
202  int bestConfidence = 0;
203  QgsProjUtils::proj_pj_unique_ptr matchedCrs;
204  for ( int i = 0; i < count; ++i )
205  {
206  if ( confidence[i] >= bestConfidence )
207  {
208  QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
209  switch ( proj_get_type( candidateCrs.get() ) )
210  {
211  case PJ_TYPE_BOUND_CRS:
212  // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
213  // consider them a candidate for a match here (depending on the identify flags, that is!)
215  break;
216  else
217  continue;
218 
219  default:
220  break;
221  }
222 
223  candidateCrs = QgsProjUtils::crsToSingleCrs( candidateCrs.get() );
224  const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
225  // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
226  if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String( "EPSG" ) ) )
227  {
228  bestConfidence = confidence[i];
229  matchedCrs = std::move( candidateCrs );
230  }
231  }
232  }
233  proj_list_destroy( crsList );
234  proj_int_list_destroy( confidence );
235  if ( matchedCrs && bestConfidence >= 70 )
236  {
237  authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
238  authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
239  }
240  }
241  return !authName.isEmpty() && !authCode.isEmpty();
242 }
243 
244 bool QgsProjUtils::coordinateOperationIsAvailable( const QString &projDef )
245 {
246  if ( projDef.isEmpty() )
247  return true;
248 
249  PJ_CONTEXT *context = QgsProjContext::get();
250  QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
251  if ( !coordinateOperation )
252  return false;
253 
254  return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
255 }
256 
257 QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
258 {
259  static QRegularExpression sRegex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
260 
261  QList< QgsDatumTransform::GridDetails > grids;
262  QRegularExpressionMatchIterator matches = sRegex.globalMatch( proj );
263  while ( matches.hasNext() )
264  {
265  const QRegularExpressionMatch match = matches.next();
266  const QString gridName = match.captured( 1 );
268  grid.shortName = gridName;
269  const char *fullName = nullptr;
270  const char *packageName = nullptr;
271  const char *url = nullptr;
272  int directDownload = 0;
273  int openLicense = 0;
274  int available = 0;
275  proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
276  grid.fullName = QString( fullName );
277  grid.packageName = QString( packageName );
278  grid.url = QString( url );
279  grid.directDownload = directDownload;
280  grid.openLicense = openLicense;
281  grid.isAvailable = available;
282  grids.append( grid );
283  }
284  return grids;
285 }
286 
287 #if 0
288 QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
289 {
290  if ( projDef.isEmpty() )
291  return QStringList();
292 
293  PJ_CONTEXT *context = QgsProjContext::get();
294  QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
295  if ( !op )
296  return QStringList();
297 
298  QStringList res;
299  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
300  {
301  const char *shortName = nullptr;
302  int isAvailable = 0;
303  proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
304  if ( !isAvailable )
305  res << QString( shortName );
306  }
307  return res;
308 }
309 #endif
310 
311 #endif
312 
314 {
315 #if PROJ_VERSION_MAJOR>=6
316  const QString path( proj_info().searchpath );
317  QStringList paths;
318 #ifdef Q_OS_WIN
319  paths = path.split( ';' );
320 #else
321  paths = path.split( ':' );
322 #endif
323 
324  QSet<QString> existing;
325  // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
326  QStringList res;
327  res.reserve( paths.count() );
328  for ( const QString &p : qgis::as_const( paths ) )
329  {
330  if ( existing.contains( p ) )
331  continue;
332 
333  existing.insert( p );
334  res << p;
335  }
336  return res;
337 #else
338  return QStringList();
339 #endif
340 }
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:60
QgsDatumTransform::GridDetails::directDownload
bool directDownload
true if direct download of grid is possible
Definition: qgsdatumtransform.h:144
QgsDatumTransform::GridDetails::openLicense
bool openLicense
true if grid is available under an open license
Definition: qgsdatumtransform.h:146
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:105
qgis.h
QgsDatumTransform::GridDetails
Contains information about a projection transformation grid file.
Definition: qgsdatumtransform.h:133
QgsDatumTransform::GridDetails::url
QString url
Url to download grid from.
Definition: qgsdatumtransform.h:142
QgsDatumTransform::GridDetails::fullName
QString fullName
Full name of transform grid.
Definition: qgsdatumtransform.h:138
qgscoordinatetransform.h
QgsDatumTransform::GridDetails::shortName
QString shortName
Short name of transform grid.
Definition: qgsdatumtransform.h:136
QgsProjUtils::FlagMatchBoundCrsToUnderlyingSourceCrs
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
Definition: qgsprojutils.h:70
qgsprojutils.h
QgsDatumTransform::GridDetails::isAvailable
bool isAvailable
true if grid is currently available for use
Definition: qgsdatumtransform.h:148
PJ_CONTEXT
void PJ_CONTEXT
Definition: qgsprojutils.h:151
QgsProjContext::QgsProjContext
QgsProjContext()
Definition: qgsprojutils.cpp:38
QgsDatumTransform::GridDetails::packageName
QString packageName
Name of package the grid is included within.
Definition: qgsdatumtransform.h:140
QgsProjUtils::searchPaths
static QStringList searchPaths()
Returns the current list of Proj file search paths.
Definition: qgsprojutils.cpp:313
QgsProjContext::~QgsProjContext
~QgsProjContext()
Definition: qgsprojutils.cpp:47
QgsProjContext
Definition: qgsprojutils.h:161