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