QGIS API Documentation 4.1.0-Master (60fea48833c)
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, nullptr, nullptr, nullptr, nullptr, nullptr, &outUnitAuthName, &outUnitAuthCode );
97
98 if ( outUnitAuthName && outUnitAuthCode )
99 {
100 const char *unitCategory = nullptr;
101 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
102 {
103 return QString( unitCategory ).compare( "angular"_L1, Qt::CaseInsensitive ) == 0;
104 }
105 }
106 }
107 return false;
108}
109
111{
112 //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
113 //and GDAL OGRSpatialReference::isNorthEastAxisOrder https://github.com/OSGeo/gdal/blob/release/3.10/ogr/ogrspatialreference.cpp#L419
114 if ( !crs )
115 return false;
116
117 PJ_CONTEXT *context = QgsProjContext::get();
118
120 if ( !horizCrs )
121 return false;
122
123 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, horizCrs.get() ) );
124 if ( !pjCs )
125 return false;
126
127 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
128 if ( axisCount > 0 )
129 {
130 const char *outDirection0 = nullptr;
131 const char *outDirection1 = nullptr;
132 const char *outName0 = nullptr;
133 const char *outName1 = nullptr;
134
135 proj_cs_get_axis_info( context, pjCs.get(), 0, &outName0, nullptr, &outDirection0, nullptr, nullptr, nullptr, nullptr );
136
137 proj_cs_get_axis_info( context, pjCs.get(), 1, &outName1, nullptr, &outDirection1, nullptr, nullptr, nullptr, nullptr );
138
139 if ( QString( outDirection0 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 && QString( outDirection1 ).compare( "east"_L1, Qt::CaseInsensitive ) == 0 )
140 {
141 return true;
142 }
143
144 // Handle polar projections with NE-order
145 if ( ( QString( outDirection0 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 && QString( outDirection1 ).compare( "north"_L1, Qt::CaseInsensitive ) == 0 )
146 || ( QString( outDirection0 ).compare( "south"_L1, Qt::CaseInsensitive ) == 0 && QString( outDirection1 ).compare( "south"_L1, Qt::CaseInsensitive ) == 0 ) )
147 {
148 return QString( outName0 ).startsWith( "northing"_L1, Qt::CaseInsensitive ) && QString( outName1 ).startsWith( "easting"_L1, Qt::CaseInsensitive );
149 }
150 }
151 return false;
152}
153
154bool QgsProjUtils::isDynamic( const PJ *crs )
155{
156 // ported from GDAL OGRSpatialReference::IsDynamic()
157 bool isDynamic = false;
158 PJ_CONTEXT *context = QgsProjContext::get();
159
160 // prefer horizontal crs if possible
161 proj_pj_unique_ptr candidate = crsToHorizontalCrs( crs );
162 if ( !crs )
163 candidate = unboundCrs( crs );
164
165 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
166 if ( datum )
167 {
168 const PJ_TYPE type = proj_get_type( datum.get() );
169 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME || type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
170 if ( !isDynamic )
171 {
172 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
173 const QString code( proj_get_id_code( datum.get(), 0 ) );
174 if ( authName == "EPSG"_L1 && code == "6326"_L1 )
175 {
176 isDynamic = true;
177 }
178 }
179 }
180 else
181 {
182 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
183 if ( ensemble )
184 {
185 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
186 if ( member )
187 {
188 const PJ_TYPE type = proj_get_type( member.get() );
189 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME || type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
190 }
191 }
192 }
193 return isDynamic;
194}
195
197{
198 if ( !crs )
199 return nullptr;
200
201 PJ_CONTEXT *context = QgsProjContext::get();
202 switch ( proj_get_type( crs ) )
203 {
204 case PJ_TYPE_COMPOUND_CRS:
205 {
206 int i = 0;
207 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
208 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
209 {
210 i++;
211 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
212 }
213 return res;
214 }
215
216 case PJ_TYPE_VERTICAL_CRS:
217 return nullptr;
218
219 // maybe other types to handle??
220
221 default:
222 return unboundCrs( crs );
223 }
224
225#ifndef _MSC_VER // unreachable
226 return nullptr;
227#endif
228}
229
231{
232 if ( !crs )
233 return nullptr;
234
235 PJ_CONTEXT *context = QgsProjContext::get();
236 switch ( proj_get_type( crs ) )
237 {
238 case PJ_TYPE_COMPOUND_CRS:
239 {
240 int i = 0;
241 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
242 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
243 {
244 i++;
245 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
246 }
247 return res;
248 }
249
250 case PJ_TYPE_VERTICAL_CRS:
251 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
252
253 // maybe other types to handle??
254
255 default:
256 return nullptr;
257 }
258
260}
261
263{
264 if ( !crs )
265 return false;
266
267 PJ_CONTEXT *context = QgsProjContext::get();
268
269 switch ( proj_get_type( crs ) )
270 {
271 case PJ_TYPE_COMPOUND_CRS:
272 {
273 int i = 0;
274 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
275 while ( res )
276 {
277 if ( hasVerticalAxis( res.get() ) )
278 return true;
279 i++;
280 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
281 }
282 return false;
283 }
284
285 case PJ_TYPE_BOUND_CRS:
286 {
287 return hasVerticalAxis( proj_get_source_crs( context, crs ) );
288 }
289
290 // maybe other types to handle like this??
291
292 default:
293 break;
294 }
295
296 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
297 if ( !pjCs )
298 return false;
299
300 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
301 for ( int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
302 {
303 const char *outDirection = nullptr;
304 proj_cs_get_axis_info( context, pjCs.get(), axisIndex, nullptr, nullptr, &outDirection, nullptr, nullptr, nullptr, nullptr );
305 const QString outDirectionString = QString( outDirection );
306 if ( outDirectionString.compare( "geocentricZ"_L1, Qt::CaseInsensitive ) == 0
307 || outDirectionString.compare( "up"_L1, Qt::CaseInsensitive ) == 0
308 || outDirectionString.compare( "down"_L1, Qt::CaseInsensitive ) == 0 )
309 {
310 return true;
311 }
312 }
313 return false;
314}
315
317{
318 if ( !crs )
319 return nullptr;
320
321 PJ_CONTEXT *context = QgsProjContext::get();
322 switch ( proj_get_type( crs ) )
323 {
324 case PJ_TYPE_BOUND_CRS:
325 return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
326
327 // maybe other types to handle??
328
329 default:
330 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
331 }
332
333#ifndef _MSC_VER // unreachable
334 return nullptr;
335#endif
336}
337
339{
340 if ( !crs )
341 return nullptr;
342
343 PJ_CONTEXT *context = QgsProjContext::get();
345 if ( !candidate ) // purely vertical CRS
346 candidate = unboundCrs( crs );
347
348 if ( !candidate )
349 return nullptr;
350
351 return QgsProjUtils::proj_pj_unique_ptr( proj_crs_get_datum_ensemble( context, candidate.get() ) );
352}
353
354void QgsProjUtils::proj_collecting_logger( void *user_data, int /*level*/, const char *message )
355{
356 QStringList *dest = reinterpret_cast< QStringList * >( user_data );
357 QString messageString( message );
358 messageString.replace( "internal_proj_create: "_L1, QString() );
359 dest->append( messageString );
360}
361
362void QgsProjUtils::proj_silent_logger( void * /*user_data*/, int /*level*/, const char * /*message*/ )
363{}
364
365void QgsProjUtils::proj_logger( void *, int level, const char *message )
366{
367#ifdef QGISDEBUG
368 if ( level == PJ_LOG_ERROR )
369 {
370 const QString messageString( message );
371 if ( messageString == "push: Invalid latitude"_L1 )
372 {
373 // these messages tend to spam the console as they can be repeated 1000s of times
374 QgsDebugMsgLevel( messageString, 3 );
375 }
376 else
377 {
378 QgsDebugError( messageString );
379 }
380 }
381 else if ( level == PJ_LOG_DEBUG )
382 {
383 QgsDebugMsgLevel( QString( message ), 3 );
384 }
385#else
386 ( void ) level;
387 ( void ) message;
388#endif
389}
390
391QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::createCompoundCrs( const PJ *horizontalCrs, const PJ *verticalCrs, QStringList *errors )
392{
393 if ( !horizontalCrs || !verticalCrs )
394 return nullptr;
395
396 PJ_CONTEXT *context = QgsProjContext::get();
397 // collect errors instead of dumping them to terminal
398
400
401 // const cast here is for compatibility with proj < 9.5
402 QgsProjUtils::proj_pj_unique_ptr compoundCrs( proj_create_compound_crs( context, nullptr, const_cast< PJ *>( horizontalCrs ), const_cast< PJ * >( verticalCrs ) ) );
403
404 if ( errors )
405 *errors = projLogger.errors();
406
407 return compoundCrs;
408}
409
410bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
411{
412 authName.clear();
413 authCode.clear();
414
415 if ( !crs )
416 return false;
417
418 int *confidence = nullptr;
419 if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
420 {
421 const int count = proj_list_get_count( crsList );
422 int bestConfidence = 0;
424 for ( int i = 0; i < count; ++i )
425 {
426 if ( confidence[i] >= bestConfidence )
427 {
428 QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
429 switch ( proj_get_type( candidateCrs.get() ) )
430 {
431 case PJ_TYPE_BOUND_CRS:
432 // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
433 // consider them a candidate for a match here (depending on the identify flags, that is!)
435 break;
436 else
437 continue;
438
439 default:
440 break;
441 }
442
443 candidateCrs = QgsProjUtils::unboundCrs( candidateCrs.get() );
444 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
445 // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
446 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == "EPSG"_L1 ) )
447 {
448 bestConfidence = confidence[i];
449 matchedCrs = std::move( candidateCrs );
450 }
451 }
452 }
453 proj_list_destroy( crsList );
454 proj_int_list_destroy( confidence );
455 if ( matchedCrs && bestConfidence >= 70 )
456 {
457 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
458 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
459 }
460 }
461 return !authName.isEmpty() && !authCode.isEmpty();
462}
463
465{
466 if ( projDef.isEmpty() )
467 return true;
468
469 PJ_CONTEXT *context = QgsProjContext::get();
470 QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
471 if ( !coordinateOperation )
472 return false;
473
474 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
475}
476
477QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
478{
479 const thread_local QRegularExpression regex( u"\\+(?:nad)?grids=(.*?)\\s"_s );
480
481 QList< QgsDatumTransform::GridDetails > grids;
482 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
483 while ( matches.hasNext() )
484 {
485 const QRegularExpressionMatch match = matches.next();
486 const QString gridName = match.captured( 1 );
488 grid.shortName = gridName;
489 const char *fullName = nullptr;
490 const char *packageName = nullptr;
491 const char *url = nullptr;
492 int directDownload = 0;
493 int openLicense = 0;
494 int available = 0;
495 proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
496 grid.fullName = QString( fullName );
497 grid.packageName = QString( packageName );
498 grid.url = QString( url );
499 grid.directDownload = directDownload;
500 grid.openLicense = openLicense;
501 grid.isAvailable = available;
502 grids.append( grid );
503 }
504 return grids;
505}
506
507#if 0
508QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
509{
510 if ( projDef.isEmpty() )
511 return QStringList();
512
513 PJ_CONTEXT *context = QgsProjContext::get();
514 QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
515 if ( !op )
516 return QStringList();
517
518 QStringList res;
519 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
520 {
521 const char *shortName = nullptr;
522 int isAvailable = 0;
523 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
524 if ( !isAvailable )
525 res << QString( shortName );
526 }
527 return res;
528}
529#endif
530
532{
533 return PROJ_VERSION_MAJOR;
534}
535
537{
538 return PROJ_VERSION_MINOR;
539}
540
542{
543 PJ_CONTEXT *context = QgsProjContext::get();
544 const char *version = proj_context_get_database_metadata( context, "EPSG.VERSION" );
545 return QString( version );
546}
547
549{
550 PJ_CONTEXT *context = QgsProjContext::get();
551 const char *date = proj_context_get_database_metadata( context, "EPSG.DATE" );
552 return QDate::fromString( date, Qt::DateFormat::ISODate );
553}
554
556{
557 PJ_CONTEXT *context = QgsProjContext::get();
558 const char *version = proj_context_get_database_metadata( context, "ESRI.VERSION" );
559 return QString( version );
560}
561
563{
564 PJ_CONTEXT *context = QgsProjContext::get();
565 const char *date = proj_context_get_database_metadata( context, "ESRI.DATE" );
566 return QDate::fromString( date, Qt::DateFormat::ISODate );
567}
568
570{
571 PJ_CONTEXT *context = QgsProjContext::get();
572 const char *version = proj_context_get_database_metadata( context, "IGNF.VERSION" );
573 return QString( version );
574}
575
577{
578 PJ_CONTEXT *context = QgsProjContext::get();
579 const char *date = proj_context_get_database_metadata( context, "IGNF.DATE" );
580 return QDate::fromString( date, Qt::DateFormat::ISODate );
581}
582
584{
585 const QString path( proj_info().searchpath );
586 QStringList paths;
587#ifdef Q_OS_WIN
588 paths = path.split( ';' );
589#else
590 paths = path.split( ':' );
591#endif
592
593 QSet<QString> existing;
594 // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
595 QStringList res;
596 res.reserve( paths.count() );
597 for ( const QString &p : std::as_const( paths ) )
598 {
599 if ( existing.contains( p ) )
600 continue;
601
602 existing.insert( p );
603 res << p;
604 }
605 return res;
606}
607
608//
609// QgsScopedProjCollectingLogger
610//
611
616
618{
619 // reset logger back to terminal output
620 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
621}
622
623//
624// QgsScopedProjSilentLogger
625//
626
631
633{
634 // reset logger back to terminal output
635 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
636}
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:7540
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.