QGIS API Documentation 3.36.0-Maidenhead (09951dc0acf)
Loading...
Searching...
No Matches
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"
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)
29thread_local QgsProjContext QgsProjContext::sProjContext;
30#else
31QThreadStorage< 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
72bool QgsProjUtils::usesAngularUnit( const QString &projDef )
73{
74 const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
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
148 // prefer horizontal crs if possible
150 if ( !crs )
151 candidate = unboundCrs( crs );
152
153 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
154 if ( datum )
155 {
156 const PJ_TYPE type = proj_get_type( datum.get() );
157 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
158 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
159 if ( !isDynamic )
160 {
161 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
162 const QString code( proj_get_id_code( datum.get(), 0 ) );
163 if ( authName == QLatin1String( "EPSG" ) && code == QLatin1String( "6326" ) )
164 {
165 isDynamic = true;
166 }
167 }
168 }
169 else
170 {
171 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
172 if ( ensemble )
173 {
174 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
175 if ( member )
176 {
177 const PJ_TYPE type = proj_get_type( member.get() );
178 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
179 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
180 }
181 }
182 }
183 return isDynamic;
184}
185
187{
188 if ( !crs )
189 return nullptr;
190
191 PJ_CONTEXT *context = QgsProjContext::get();
192 switch ( proj_get_type( crs ) )
193 {
194 case PJ_TYPE_COMPOUND_CRS:
195 {
196 int i = 0;
197 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
198 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
199 {
200 i++;
201 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
202 }
203 return res;
204 }
205
206 case PJ_TYPE_VERTICAL_CRS:
207 return nullptr;
208
209 // maybe other types to handle??
210
211 default:
212 return unboundCrs( crs );
213 }
214
215#ifndef _MSC_VER // unreachable
216 return nullptr;
217#endif
218}
219
221{
222 if ( !crs )
223 return nullptr;
224
225 PJ_CONTEXT *context = QgsProjContext::get();
226 switch ( proj_get_type( crs ) )
227 {
228 case PJ_TYPE_BOUND_CRS:
229 return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
230
231 // maybe other types to handle??
232
233 default:
234 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
235 }
236
237#ifndef _MSC_VER // unreachable
238 return nullptr;
239#endif
240}
241
243{
244 if ( !crs )
245 return nullptr;
246
247#if PROJ_VERSION_MAJOR>=8
248 PJ_CONTEXT *context = QgsProjContext::get();
250 if ( !candidate ) // purely vertical CRS
251 candidate = unboundCrs( crs );
252
253 if ( !candidate )
254 return nullptr;
255
256 return QgsProjUtils::proj_pj_unique_ptr( proj_crs_get_datum_ensemble( context, candidate.get() ) );
257#else
258 throw QgsNotSupportedException( QObject::tr( "Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
259#endif
260}
261
262bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
263{
264 authName.clear();
265 authCode.clear();
266
267 if ( !crs )
268 return false;
269
270 int *confidence = nullptr;
271 if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
272 {
273 const int count = proj_list_get_count( crsList );
274 int bestConfidence = 0;
276 for ( int i = 0; i < count; ++i )
277 {
278 if ( confidence[i] >= bestConfidence )
279 {
280 QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
281 switch ( proj_get_type( candidateCrs.get() ) )
282 {
283 case PJ_TYPE_BOUND_CRS:
284 // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
285 // consider them a candidate for a match here (depending on the identify flags, that is!)
287 break;
288 else
289 continue;
290
291 default:
292 break;
293 }
294
295 candidateCrs = QgsProjUtils::unboundCrs( candidateCrs.get() );
296 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
297 // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
298 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String( "EPSG" ) ) )
299 {
300 bestConfidence = confidence[i];
301 matchedCrs = std::move( candidateCrs );
302 }
303 }
304 }
305 proj_list_destroy( crsList );
306 proj_int_list_destroy( confidence );
307 if ( matchedCrs && bestConfidence >= 70 )
308 {
309 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
310 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
311 }
312 }
313 return !authName.isEmpty() && !authCode.isEmpty();
314}
315
317{
318 if ( projDef.isEmpty() )
319 return true;
320
321 PJ_CONTEXT *context = QgsProjContext::get();
322 QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
323 if ( !coordinateOperation )
324 return false;
325
326 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
327}
328
329QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
330{
331 const thread_local QRegularExpression regex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
332
333 QList< QgsDatumTransform::GridDetails > grids;
334 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
335 while ( matches.hasNext() )
336 {
337 const QRegularExpressionMatch match = matches.next();
338 const QString gridName = match.captured( 1 );
340 grid.shortName = gridName;
341 const char *fullName = nullptr;
342 const char *packageName = nullptr;
343 const char *url = nullptr;
344 int directDownload = 0;
345 int openLicense = 0;
346 int available = 0;
347 proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
348 grid.fullName = QString( fullName );
349 grid.packageName = QString( packageName );
350 grid.url = QString( url );
351 grid.directDownload = directDownload;
352 grid.openLicense = openLicense;
353 grid.isAvailable = available;
354 grids.append( grid );
355 }
356 return grids;
357}
358
359#if 0
360QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
361{
362 if ( projDef.isEmpty() )
363 return QStringList();
364
365 PJ_CONTEXT *context = QgsProjContext::get();
366 QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
367 if ( !op )
368 return QStringList();
369
370 QStringList res;
371 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
372 {
373 const char *shortName = nullptr;
374 int isAvailable = 0;
375 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
376 if ( !isAvailable )
377 res << QString( shortName );
378 }
379 return res;
380}
381#endif
382
384{
385 return PROJ_VERSION_MAJOR;
386}
387
389{
390 return PROJ_VERSION_MINOR;
391}
392
394{
395 PJ_CONTEXT *context = QgsProjContext::get();
396 const char *version = proj_context_get_database_metadata( context, "EPSG.VERSION" );
397 return QString( version );
398}
399
401{
402 PJ_CONTEXT *context = QgsProjContext::get();
403 const char *date = proj_context_get_database_metadata( context, "EPSG.DATE" );
404 return QDate::fromString( date, Qt::DateFormat::ISODate );
405}
406
408{
409 PJ_CONTEXT *context = QgsProjContext::get();
410 const char *version = proj_context_get_database_metadata( context, "ESRI.VERSION" );
411 return QString( version );
412}
413
415{
416 PJ_CONTEXT *context = QgsProjContext::get();
417 const char *date = proj_context_get_database_metadata( context, "ESRI.DATE" );
418 return QDate::fromString( date, Qt::DateFormat::ISODate );
419}
420
422{
423 PJ_CONTEXT *context = QgsProjContext::get();
424 const char *version = proj_context_get_database_metadata( context, "IGNF.VERSION" );
425 return QString( version );
426}
427
429{
430 PJ_CONTEXT *context = QgsProjContext::get();
431 const char *date = proj_context_get_database_metadata( context, "IGNF.DATE" );
432 return QDate::fromString( date, Qt::DateFormat::ISODate );
433}
434
436{
437 const QString path( proj_info().searchpath );
438 QStringList paths;
439#ifdef Q_OS_WIN
440 paths = path.split( ';' );
441#else
442 paths = path.split( ':' );
443#endif
444
445 QSet<QString> existing;
446 // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
447 QStringList res;
448 res.reserve( paths.count() );
449 for ( const QString &p : std::as_const( paths ) )
450 {
451 if ( existing.contains( p ) )
452 continue;
453
454 existing.insert( p );
455 res << p;
456 }
457 return res;
458}
Custom exception class which is raised when an operation is not supported.
Used to create and store a proj context object, correctly freeing the context upon destruction.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static proj_pj_unique_ptr crsToHorizontalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract the horizontal c...
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
static QList< QgsDatumTransform::GridDetails > gridsUsed(const QString &proj)
Returns a list of grids used by the given proj string.
static bool isDynamic(const PJ *crs)
Returns true if the given proj coordinate system is a dynamic CRS.
static QDate epsgRegistryDate()
Returns the EPSG registry database release date used by the proj library.
static proj_pj_unique_ptr unboundCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), ensure that it is not a ...
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...
static QStringList searchPaths()
Returns the current list of Proj file search paths.
static QString ignfDatabaseVersion()
Returns the IGNF database version used by the proj library (e.g.
static proj_pj_unique_ptr crsToDatumEnsemble(const PJ *crs)
Given a PROJ crs, attempt to retrieve the datum ensemble from it.
static bool coordinateOperationIsAvailable(const QString &projDef)
Returns true if a coordinate operation (specified via proj string) is available.
static QString epsgRegistryVersion()
Returns the EPSG registry database version used by the proj library (e.g.
static QDate esriDatabaseDate()
Returns the ESRI projection engine database release date used by the proj library.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
static bool usesAngularUnit(const QString &projDef)
Returns true if the given proj coordinate system uses angular units.
static bool axisOrderIsSwapped(const PJ *crs)
Returns true if the given proj coordinate system uses requires y/x coordinate order instead of x/y.
static QString esriDatabaseVersion()
Returns the ESRI projection engine database version used by the proj library (e.g.
static int projVersionMajor()
Returns the proj library major version number.
static QDate ignfDatabaseDate()
Returns the IGNF database release date used by the proj library.
static int projVersionMinor()
Returns the proj library minor version number.
struct projCtx_t PJ_CONTEXT
struct PJconsts PJ
struct projCtx_t PJ_CONTEXT
const QgsCoordinateReferenceSystem & crs
Contains information about a projection transformation grid file.
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
QString fullName
Full name of transform grid.
bool directDownload
true if direct download of grid is possible
QString packageName
Name of package the grid is included within.
QString url
Url to download grid from.
bool openLicense
true if grid is available under an open license
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.