QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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  proj_context_destroy( mContext );
54 #else
55  pj_ctx_free( mContext );
56 #endif
57 }
58 
60 {
61 #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
62  return sProjContext.mContext;
63 #else
64  PJ_CONTEXT *pContext = nullptr;
65  if ( sProjContext.hasLocalData() )
66  {
67  pContext = sProjContext.localData()->mContext;
68  }
69  else
70  {
71  sProjContext.setLocalData( new QgsProjContext() );
72  pContext = sProjContext.localData()->mContext;
73  }
74  return pContext;
75 #endif
76 }
77 
78 #if PROJ_VERSION_MAJOR>=6
79 void QgsProjUtils::ProjPJDeleter::operator()( PJ *object )
80 {
81  proj_destroy( object );
82 }
83 
84 bool QgsProjUtils::usesAngularUnit( const QString &projDef )
85 {
86  const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
87  PJ_CONTEXT *context = QgsProjContext::get();
88  QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
89  if ( !projSingleOperation )
90  return false;
91 
92  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
93  if ( !coordinateSystem )
94  return false;
95 
96  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
97  if ( axisCount > 0 )
98  {
99  const char *outUnitAuthName = nullptr;
100  const char *outUnitAuthCode = nullptr;
101  // Read only first axis
102  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
103  nullptr,
104  nullptr,
105  nullptr,
106  nullptr,
107  nullptr,
108  &outUnitAuthName,
109  &outUnitAuthCode );
110 
111  if ( outUnitAuthName && outUnitAuthCode )
112  {
113  const char *unitCategory = nullptr;
114  if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
115  {
116  return QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
117  }
118  }
119  }
120  return false;
121 }
122 
123 bool QgsProjUtils::axisOrderIsSwapped( const PJ *crs )
124 {
125  //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
126  if ( !crs )
127  return false;
128 
129  PJ_CONTEXT *context = QgsProjContext::get();
130  QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
131  if ( !pjCs )
132  return false;
133 
134  const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
135  if ( axisCount > 0 )
136  {
137  const char *outDirection = nullptr;
138  // Read only first axis, see if it is degrees / north
139 
140  proj_cs_get_axis_info( context, pjCs.get(), 0,
141  nullptr,
142  nullptr,
143  &outDirection,
144  nullptr,
145  nullptr,
146  nullptr,
147  nullptr
148  );
149  return QString( outDirection ).compare( QLatin1String( "north" ), Qt::CaseInsensitive ) == 0;
150  }
151  return false;
152 }
153 
154 
155 QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::crsToSingleCrs( const PJ *crs )
156 {
157  if ( !crs )
158  return nullptr;
159 
160  PJ_CONTEXT *context = QgsProjContext::get();
161  switch ( proj_get_type( crs ) )
162  {
163  case PJ_TYPE_BOUND_CRS:
164  return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
165 
166  case PJ_TYPE_COMPOUND_CRS:
167  {
168  int i = 0;
169  QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
170  while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
171  {
172  i++;
173  res.reset( proj_crs_get_sub_crs( context, crs, i ) );
174  }
175  return res;
176  }
177 
178  // maybe other types to handle??
179 
180  default:
181  return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
182  }
183 
184  return nullptr;
185 }
186 
187 bool QgsProjUtils::coordinateOperationIsAvailable( const QString &projDef )
188 {
189  if ( projDef.isEmpty() )
190  return true;
191 
192  PJ_CONTEXT *context = QgsProjContext::get();
193  QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
194  if ( !coordinateOperation )
195  return false;
196 
197  return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
198 }
199 
200 QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
201 {
202  static QRegularExpression sRegex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
203 
204  QList< QgsDatumTransform::GridDetails > grids;
205  QRegularExpressionMatchIterator matches = sRegex.globalMatch( proj );
206  while ( matches.hasNext() )
207  {
208  const QRegularExpressionMatch match = matches.next();
209  const QString gridName = match.captured( 1 );
211  grid.shortName = gridName;
212 #if PROJ_VERSION_MAJOR>6 || (PROJ_VERSION_MAJOR==6 && PROJ_VERSION_MINOR>=2)
213  const char *fullName = nullptr;
214  const char *packageName = nullptr;
215  const char *url = nullptr;
216  int directDownload = 0;
217  int openLicense = 0;
218  int available = 0;
219  proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
220  grid.fullName = QString( fullName );
221  grid.packageName = QString( packageName );
222  grid.url = QString( url );
223  grid.directDownload = directDownload;
224  grid.openLicense = openLicense;
225  grid.isAvailable = available;
226 #endif
227  grids.append( grid );
228  }
229  return grids;
230 }
231 
232 #if 0
233 QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
234 {
235  if ( projDef.isEmpty() )
236  return QStringList();
237 
238  PJ_CONTEXT *context = QgsProjContext::get();
239  QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
240  if ( !op )
241  return QStringList();
242 
243  QStringList res;
244  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
245  {
246  const char *shortName = nullptr;
247  int isAvailable = 0;
248  proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
249  if ( !isAvailable )
250  res << QString( shortName );
251  }
252  return res;
253 }
254 #endif
255 
256 #endif
257 
259 {
260 #if PROJ_VERSION_MAJOR>=6
261  const QString path( proj_info().searchpath );
262  QStringList paths;
263 #if PROJ_VERSION_MINOR==1 && PROJ_VERSION_PATCH==0
264  // -- see https://github.com/OSGeo/proj.4/pull/1497
265  paths = path.split( ';' );
266 #else
267 #ifdef Q_OS_WIN
268  paths = path.split( ';' );
269 #else
270  paths = path.split( ':' );
271 #endif
272 #endif
273 
274  QSet<QString> existing;
275  // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
276  QStringList res;
277  res.reserve( paths.count() );
278  for ( const QString &p : qgis::as_const( paths ) )
279  {
280  if ( existing.contains( p ) )
281  continue;
282 
283  existing.insert( p );
284  res << p;
285  }
286  return res;
287 #else
288  return QStringList();
289 #endif
290 }
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:145
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:135
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