QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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!)
214  if ( flags & FlagMatchBoundCrsToUnderlyingSourceCrs )
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 }
Contains information about a projection transformation grid file.
const QgsCoordinateReferenceSystem & crs
QString packageName
Name of package the grid is included within.
Used to create and store a proj context object, correctly freeing the context upon destruction...
Definition: qgsprojutils.h:161
QString fullName
Full name of transform grid.
bool openLicense
true if grid is available under an open license
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
QString url
Url to download grid from.
static QStringList searchPaths()
Returns the current list of Proj file search paths.
void PJ_CONTEXT
Definition: qgsprojutils.h:151
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
bool directDownload
true if direct download of grid is possible