QGIS API Documentation 3.99.0-Master (8e76e220402)
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
19#include <proj.h>
20#include <proj_experimental.h>
21
22#include "qgis.h"
24#include "qgsexception.h"
25#include "qgslogger.h"
26
27#include <QDate>
28#include <QRegularExpression>
29#include <QSet>
30#include <QString>
31
32using namespace Qt::StringLiterals;
33
34#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
35thread_local QgsProjContext QgsProjContext::sProjContext;
36#else
37QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
38#endif
39
41{
42 mContext = proj_context_create();
43}
44
46{
47 // Call removeFromCacheObjectsBelongingToCurrentThread() before
48 // destroying the context
49 QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
50 QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
51 proj_context_destroy( mContext );
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
74{
75 proj_destroy( object );
76}
77
78bool QgsProjUtils::usesAngularUnit( const QString &projDef )
79{
80 const QString crsDef = u"%1 +type=crs"_s.arg( projDef );
82 QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
83 if ( !projSingleOperation )
84 return false;
85
86 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
87 if ( !coordinateSystem )
88 return false;
89
90 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
91 if ( axisCount > 0 )
92 {
93 const char *outUnitAuthName = nullptr;
94 const char *outUnitAuthCode = nullptr;
95 // Read only first axis
96 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
97 nullptr,
98 nullptr,
99 nullptr,
100 nullptr,
101 nullptr,
102 &outUnitAuthName,
103 &outUnitAuthCode );
104
105 if ( outUnitAuthName && outUnitAuthCode )
106 {
107 const char *unitCategory = nullptr;
108 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
109 {
110 return QString( unitCategory ).compare( "angular"_L1, Qt::CaseInsensitive ) == 0;
111 }
112 }
113 }
114 return false;
115}
116
118{
119 //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
120 //and GDAL OGRSpatialReference::isNorthEastAxisOrder https://github.com/OSGeo/gdal/blob/release/3.10/ogr/ogrspatialreference.cpp#L419
121 if ( !crs )
122 return false;
123
124 PJ_CONTEXT *context = QgsProjContext::get();
125
127 if ( !horizCrs )
128 return false;
129
130 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, horizCrs.get() ) );
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 *outDirection0 = nullptr;
138 const char *outDirection1 = nullptr;
139 const char *outName0 = nullptr;
140 const char *outName1 = nullptr;
141
142 proj_cs_get_axis_info( context, pjCs.get(), 0,
143 &outName0,
144 nullptr,
145 &outDirection0,
146 nullptr,
147 nullptr,
148 nullptr,
149 nullptr
150 );
151
152 proj_cs_get_axis_info( context, pjCs.get(), 1,
153 &outName1,
154 nullptr,
155 &outDirection1,
156 nullptr,
157 nullptr,
158 nullptr,
159 nullptr
160 );
161
162 if ( QString( outDirection0 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 &&
163 QString( outDirection1 ).compare( "east"_L1, Qt::CaseInsensitive ) == 0 )
164 {
165 return true;
166 }
167
168 // Handle polar projections with NE-order
169 if ( ( QString( outDirection0 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 &&
170 QString( outDirection1 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 ) ||
171 ( QString( outDirection0 ).compare( "south"_L1, Qt::CaseInsensitive ) == 0 &&
172 QString( outDirection1 ).compare( "south"_L1, Qt::CaseInsensitive ) == 0 ) )
173 {
174 return QString( outName0 ).startsWith( "northing"_L1, Qt::CaseInsensitive ) &&
175 QString( outName1 ).startsWith( "easting"_L1, Qt::CaseInsensitive ) ;
176 }
177 }
178 return false;
179}
180
181bool QgsProjUtils::isDynamic( const PJ *crs )
182{
183 // ported from GDAL OGRSpatialReference::IsDynamic()
184 bool isDynamic = false;
185 PJ_CONTEXT *context = QgsProjContext::get();
186
187 // prefer horizontal crs if possible
188 proj_pj_unique_ptr candidate = crsToHorizontalCrs( crs );
189 if ( !crs )
190 candidate = unboundCrs( crs );
191
192 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
193 if ( datum )
194 {
195 const PJ_TYPE type = proj_get_type( datum.get() );
196 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
197 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
198 if ( !isDynamic )
199 {
200 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
201 const QString code( proj_get_id_code( datum.get(), 0 ) );
202 if ( authName == "EPSG"_L1 && code == "6326"_L1 )
203 {
204 isDynamic = true;
205 }
206 }
207 }
208 else
209 {
210 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
211 if ( ensemble )
212 {
213 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
214 if ( member )
215 {
216 const PJ_TYPE type = proj_get_type( member.get() );
217 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
218 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
219 }
220 }
221 }
222 return isDynamic;
223}
224
226{
227 if ( !crs )
228 return nullptr;
229
230 PJ_CONTEXT *context = QgsProjContext::get();
231 switch ( proj_get_type( crs ) )
232 {
233 case PJ_TYPE_COMPOUND_CRS:
234 {
235 int i = 0;
236 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
237 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
238 {
239 i++;
240 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
241 }
242 return res;
243 }
244
245 case PJ_TYPE_VERTICAL_CRS:
246 return nullptr;
247
248 // maybe other types to handle??
249
250 default:
251 return unboundCrs( crs );
252 }
253
254#ifndef _MSC_VER // unreachable
255 return nullptr;
256#endif
257}
258
260{
261 if ( !crs )
262 return nullptr;
263
264 PJ_CONTEXT *context = QgsProjContext::get();
265 switch ( proj_get_type( crs ) )
266 {
267 case PJ_TYPE_COMPOUND_CRS:
268 {
269 int i = 0;
270 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
271 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
272 {
273 i++;
274 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
275 }
276 return res;
277 }
278
279 case PJ_TYPE_VERTICAL_CRS:
280 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
281
282 // maybe other types to handle??
283
284 default:
285 return nullptr;
286 }
287
289}
290
292{
293 if ( !crs )
294 return false;
295
296 PJ_CONTEXT *context = QgsProjContext::get();
297
298 switch ( proj_get_type( crs ) )
299 {
300 case PJ_TYPE_COMPOUND_CRS:
301 {
302 int i = 0;
303 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
304 while ( res )
305 {
306 if ( hasVerticalAxis( res.get() ) )
307 return true;
308 i++;
309 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
310 }
311 return false;
312 }
313
314 case PJ_TYPE_BOUND_CRS:
315 {
316 return hasVerticalAxis( proj_get_source_crs( context, crs ) );
317 }
318
319 // maybe other types to handle like this??
320
321 default:
322 break;
323 }
324
325 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
326 if ( !pjCs )
327 return false;
328
329 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
330 for ( int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
331 {
332 const char *outDirection = nullptr;
333 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
334 nullptr,
335 nullptr,
336 &outDirection,
337 nullptr,
338 nullptr,
339 nullptr,
340 nullptr
341 );
342 const QString outDirectionString = QString( outDirection );
343 if ( outDirectionString.compare( "geocentricZ"_L1, Qt::CaseInsensitive ) == 0
344 || outDirectionString.compare( "up"_L1, Qt::CaseInsensitive ) == 0
345 || outDirectionString.compare( "down"_L1, Qt::CaseInsensitive ) == 0 )
346 {
347 return true;
348 }
349 }
350 return false;
351}
352
354{
355 if ( !crs )
356 return nullptr;
357
358 PJ_CONTEXT *context = QgsProjContext::get();
359 switch ( proj_get_type( crs ) )
360 {
361 case PJ_TYPE_BOUND_CRS:
362 return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
363
364 // maybe other types to handle??
365
366 default:
367 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
368 }
369
370#ifndef _MSC_VER // unreachable
371 return nullptr;
372#endif
373}
374
376{
377 if ( !crs )
378 return nullptr;
379
380 PJ_CONTEXT *context = QgsProjContext::get();
382 if ( !candidate ) // purely vertical CRS
383 candidate = unboundCrs( crs );
384
385 if ( !candidate )
386 return nullptr;
387
388 return QgsProjUtils::proj_pj_unique_ptr( proj_crs_get_datum_ensemble( context, candidate.get() ) );
389}
390
391void QgsProjUtils::proj_collecting_logger( void *user_data, int /*level*/, const char *message )
392{
393 QStringList *dest = reinterpret_cast< QStringList * >( user_data );
394 QString messageString( message );
395 messageString.replace( "internal_proj_create: "_L1, QString() );
396 dest->append( messageString );
397}
398
399void QgsProjUtils::proj_silent_logger( void * /*user_data*/, int /*level*/, const char * /*message*/ )
400{
401}
402
403void QgsProjUtils::proj_logger( void *, int level, const char *message )
404{
405#ifdef QGISDEBUG
406 if ( level == PJ_LOG_ERROR )
407 {
408 const QString messageString( message );
409 if ( messageString == "push: Invalid latitude"_L1 )
410 {
411 // these messages tend to spam the console as they can be repeated 1000s of times
412 QgsDebugMsgLevel( messageString, 3 );
413 }
414 else
415 {
416 QgsDebugError( messageString );
417 }
418 }
419 else if ( level == PJ_LOG_DEBUG )
420 {
421 QgsDebugMsgLevel( QString( message ), 3 );
422 }
423#else
424 ( void )level;
425 ( void )message;
426#endif
427}
428
429QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::createCompoundCrs( const PJ *horizontalCrs, const PJ *verticalCrs, QStringList *errors )
430{
431 if ( !horizontalCrs || !verticalCrs )
432 return nullptr;
433
434 PJ_CONTEXT *context = QgsProjContext::get();
435 // collect errors instead of dumping them to terminal
436
438
439 // const cast here is for compatibility with proj < 9.5
440 QgsProjUtils::proj_pj_unique_ptr compoundCrs( proj_create_compound_crs( context,
441 nullptr,
442 const_cast< PJ *>( horizontalCrs ),
443 const_cast< PJ * >( verticalCrs ) ) );
444
445 if ( errors )
446 *errors = projLogger.errors();
447
448 return compoundCrs;
449}
450
451bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
452{
453 authName.clear();
454 authCode.clear();
455
456 if ( !crs )
457 return false;
458
459 int *confidence = nullptr;
460 if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
461 {
462 const int count = proj_list_get_count( crsList );
463 int bestConfidence = 0;
465 for ( int i = 0; i < count; ++i )
466 {
467 if ( confidence[i] >= bestConfidence )
468 {
469 QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
470 switch ( proj_get_type( candidateCrs.get() ) )
471 {
472 case PJ_TYPE_BOUND_CRS:
473 // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
474 // consider them a candidate for a match here (depending on the identify flags, that is!)
476 break;
477 else
478 continue;
479
480 default:
481 break;
482 }
483
484 candidateCrs = QgsProjUtils::unboundCrs( candidateCrs.get() );
485 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
486 // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
487 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == "EPSG"_L1 ) )
488 {
489 bestConfidence = confidence[i];
490 matchedCrs = std::move( candidateCrs );
491 }
492 }
493 }
494 proj_list_destroy( crsList );
495 proj_int_list_destroy( confidence );
496 if ( matchedCrs && bestConfidence >= 70 )
497 {
498 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
499 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
500 }
501 }
502 return !authName.isEmpty() && !authCode.isEmpty();
503}
504
506{
507 if ( projDef.isEmpty() )
508 return true;
509
510 PJ_CONTEXT *context = QgsProjContext::get();
511 QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
512 if ( !coordinateOperation )
513 return false;
514
515 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
516}
517
518QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
519{
520 const thread_local QRegularExpression regex( u"\\+(?:nad)?grids=(.*?)\\s"_s );
521
522 QList< QgsDatumTransform::GridDetails > grids;
523 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
524 while ( matches.hasNext() )
525 {
526 const QRegularExpressionMatch match = matches.next();
527 const QString gridName = match.captured( 1 );
529 grid.shortName = gridName;
530 const char *fullName = nullptr;
531 const char *packageName = nullptr;
532 const char *url = nullptr;
533 int directDownload = 0;
534 int openLicense = 0;
535 int available = 0;
536 proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
537 grid.fullName = QString( fullName );
538 grid.packageName = QString( packageName );
539 grid.url = QString( url );
540 grid.directDownload = directDownload;
541 grid.openLicense = openLicense;
542 grid.isAvailable = available;
543 grids.append( grid );
544 }
545 return grids;
546}
547
548#if 0
549QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
550{
551 if ( projDef.isEmpty() )
552 return QStringList();
553
554 PJ_CONTEXT *context = QgsProjContext::get();
555 QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
556 if ( !op )
557 return QStringList();
558
559 QStringList res;
560 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
561 {
562 const char *shortName = nullptr;
563 int isAvailable = 0;
564 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
565 if ( !isAvailable )
566 res << QString( shortName );
567 }
568 return res;
569}
570#endif
571
573{
574 return PROJ_VERSION_MAJOR;
575}
576
578{
579 return PROJ_VERSION_MINOR;
580}
581
583{
584 PJ_CONTEXT *context = QgsProjContext::get();
585 const char *version = proj_context_get_database_metadata( context, "EPSG.VERSION" );
586 return QString( version );
587}
588
590{
591 PJ_CONTEXT *context = QgsProjContext::get();
592 const char *date = proj_context_get_database_metadata( context, "EPSG.DATE" );
593 return QDate::fromString( date, Qt::DateFormat::ISODate );
594}
595
597{
598 PJ_CONTEXT *context = QgsProjContext::get();
599 const char *version = proj_context_get_database_metadata( context, "ESRI.VERSION" );
600 return QString( version );
601}
602
604{
605 PJ_CONTEXT *context = QgsProjContext::get();
606 const char *date = proj_context_get_database_metadata( context, "ESRI.DATE" );
607 return QDate::fromString( date, Qt::DateFormat::ISODate );
608}
609
611{
612 PJ_CONTEXT *context = QgsProjContext::get();
613 const char *version = proj_context_get_database_metadata( context, "IGNF.VERSION" );
614 return QString( version );
615}
616
618{
619 PJ_CONTEXT *context = QgsProjContext::get();
620 const char *date = proj_context_get_database_metadata( context, "IGNF.DATE" );
621 return QDate::fromString( date, Qt::DateFormat::ISODate );
622}
623
625{
626 const QString path( proj_info().searchpath );
627 QStringList paths;
628#ifdef Q_OS_WIN
629 paths = path.split( ';' );
630#else
631 paths = path.split( ':' );
632#endif
633
634 QSet<QString> existing;
635 // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
636 QStringList res;
637 res.reserve( paths.count() );
638 for ( const QString &p : std::as_const( paths ) )
639 {
640 if ( existing.contains( p ) )
641 continue;
642
643 existing.insert( p );
644 res << p;
645 }
646 return res;
647}
648
649//
650// QgsScopedProjCollectingLogger
651//
652
657
659{
660 // reset logger back to terminal output
661 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
662}
663
664//
665// QgsScopedProjSilentLogger
666//
667
672
674{
675 // reset logger back to terminal output
676 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
677}
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 proj_pj_unique_ptr createCompoundCrs(const PJ *horizontalCrs, const PJ *verticalCrs, QStringList *errors=nullptr)
Given a PROJ horizontal and vertical CRS, attempt to create a compound CRS from them.
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 bool hasVerticalAxis(const PJ *crs)
Returns true if a PROJ crs has a vertical axis.
static proj_pj_unique_ptr crsToVerticalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound crs, or some other type), extract the vertical crs from it.
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 void proj_collecting_logger(void *user_data, int level, const char *message)
QGIS proj log function which collects errors to a QStringList.
QFlags< IdentifyFlag > IdentifyFlags
static void proj_logger(void *user_data, int level, const char *message)
Default QGIS proj log function.
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.
static void proj_silent_logger(void *user_data, int level, const char *message)
QGIS proj log function which ignores errors.
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.
Scoped object for temporary swapping to an error-collecting PROJ log function.
QStringList errors() const
Returns the (possibly empty) list of collected errors.
QgsScopedProjCollectingLogger()
Constructor for QgsScopedProjCollectingLogger.
~QgsScopedProjCollectingLogger()
Returns the PROJ logger back to the default QGIS PROJ logger.
~QgsScopedProjSilentLogger()
Returns the PROJ logger back to the default QGIS PROJ logger.
QgsScopedProjSilentLogger()
Constructor for QgsScopedProjSilentLogger.
#define BUILTIN_UNREACHABLE
Definition qgis.h:7524
struct pj_ctx PJ_CONTEXT
struct PJconsts PJ
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
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.