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