24#include <QRegularExpression>
28#include <proj_experimental.h>
30#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
33QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
38 mContext = proj_context_create();
45 QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
46 QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
47 proj_context_destroy( mContext );
52#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
53 return sProjContext.mContext;
56 if ( sProjContext.hasLocalData() )
58 pContext = sProjContext.localData()->mContext;
63 pContext = sProjContext.localData()->mContext;
71 proj_destroy(
object );
76 const QString crsDef = QStringLiteral(
"%1 +type=crs" ).arg( projDef );
79 if ( !projSingleOperation )
83 if ( !coordinateSystem )
86 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
89 const char *outUnitAuthName =
nullptr;
90 const char *outUnitAuthCode =
nullptr;
92 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
101 if ( outUnitAuthName && outUnitAuthCode )
103 const char *unitCategory =
nullptr;
104 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode,
nullptr,
nullptr, &unitCategory ) )
106 return QString( unitCategory ).compare( QLatin1String(
"angular" ), Qt::CaseInsensitive ) == 0;
124 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
127 const char *outDirection =
nullptr;
130 proj_cs_get_axis_info( context, pjCs.get(), 0,
139 return QString( outDirection ).compare( QLatin1String(
"north" ), Qt::CaseInsensitive ) == 0;
155 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
158 const PJ_TYPE type = proj_get_type( datum.get() );
159 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
160 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
163 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
164 const QString code( proj_get_id_code( datum.get(), 0 ) );
165 if ( authName == QLatin1String(
"EPSG" ) && code == QLatin1String(
"6326" ) )
173 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
176 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
179 const PJ_TYPE type = proj_get_type( member.get() );
180 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
181 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
194 switch ( proj_get_type(
crs ) )
196 case PJ_TYPE_COMPOUND_CRS:
200 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
203 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
208 case PJ_TYPE_VERTICAL_CRS:
228 switch ( proj_get_type(
crs ) )
230 case PJ_TYPE_COMPOUND_CRS:
234 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
237 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
242 case PJ_TYPE_VERTICAL_CRS:
261 switch ( proj_get_type(
crs ) )
263 case PJ_TYPE_COMPOUND_CRS:
272 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
287 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
288 for (
int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
290 const char *outDirection =
nullptr;
291 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
300 const QString outDirectionString = QString( outDirection );
301 if ( outDirectionString.compare( QLatin1String(
"geocentricZ" ), Qt::CaseInsensitive ) == 0
302 || outDirectionString.compare( QLatin1String(
"up" ), Qt::CaseInsensitive ) == 0
303 || outDirectionString.compare( QLatin1String(
"down" ), Qt::CaseInsensitive ) == 0 )
317 switch ( proj_get_type(
crs ) )
319 case PJ_TYPE_BOUND_CRS:
338#if PROJ_VERSION_MAJOR>=8
349 throw QgsNotSupportedException( QObject::tr(
"Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
353static void proj_collecting_logger(
void *user_data,
int ,
const char *message )
355 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
356 dest->append( QString( message ) );
359static void proj_logger(
void *,
int level,
const char *message )
362 if ( level == PJ_LOG_ERROR )
366 else if ( level == PJ_LOG_DEBUG )
378 if ( !horizontalCrs || !verticalCrs )
384 QStringList tempErrors;
385 proj_log_func( context, &tempErrors, proj_collecting_logger );
390 const_cast< PJ *
>( horizontalCrs ),
391 const_cast< PJ *
>( verticalCrs ) ) );
394 proj_log_func( context,
nullptr, proj_logger );
396 *errors = tempErrors;
409 int *confidence =
nullptr;
412 const int count = proj_list_get_count( crsList );
413 int bestConfidence = 0;
415 for (
int i = 0; i < count; ++i )
417 if ( confidence[i] >= bestConfidence )
420 switch ( proj_get_type( candidateCrs.get() ) )
422 case PJ_TYPE_BOUND_CRS:
435 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
437 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String(
"EPSG" ) ) )
439 bestConfidence = confidence[i];
440 matchedCrs = std::move( candidateCrs );
444 proj_list_destroy( crsList );
445 proj_int_list_destroy( confidence );
446 if ( matchedCrs && bestConfidence >= 70 )
448 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
449 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
452 return !authName.isEmpty() && !authCode.isEmpty();
457 if ( projDef.isEmpty() )
462 if ( !coordinateOperation )
465 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
470 const thread_local QRegularExpression regex( QStringLiteral(
"\\+(?:nad)?grids=(.*?)\\s" ) );
472 QList< QgsDatumTransform::GridDetails > grids;
473 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
474 while ( matches.hasNext() )
476 const QRegularExpressionMatch match = matches.next();
477 const QString gridName = match.captured( 1 );
480 const char *fullName =
nullptr;
481 const char *packageName =
nullptr;
482 const char *url =
nullptr;
483 int directDownload = 0;
486 proj_grid_get_info_from_database(
QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
487 grid.
fullName = QString( fullName );
489 grid.
url = QString( url );
493 grids.append( grid );
499QStringList QgsProjUtils::nonAvailableGrids(
const QString &projDef )
501 if ( projDef.isEmpty() )
502 return QStringList();
507 return QStringList();
510 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
512 const char *shortName =
nullptr;
514 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &isAvailable );
516 res << QString( shortName );
524 return PROJ_VERSION_MAJOR;
529 return PROJ_VERSION_MINOR;
535 const char *version = proj_context_get_database_metadata( context,
"EPSG.VERSION" );
536 return QString( version );
542 const char *date = proj_context_get_database_metadata( context,
"EPSG.DATE" );
543 return QDate::fromString( date, Qt::DateFormat::ISODate );
549 const char *version = proj_context_get_database_metadata( context,
"ESRI.VERSION" );
550 return QString( version );
556 const char *date = proj_context_get_database_metadata( context,
"ESRI.DATE" );
557 return QDate::fromString( date, Qt::DateFormat::ISODate );
563 const char *version = proj_context_get_database_metadata( context,
"IGNF.VERSION" );
564 return QString( version );
570 const char *date = proj_context_get_database_metadata( context,
"IGNF.DATE" );
571 return QDate::fromString( date, Qt::DateFormat::ISODate );
576 const QString path( proj_info().searchpath );
579 paths = path.split(
';' );
581 paths = path.split(
':' );
584 QSet<QString> existing;
587 res.reserve( paths.count() );
588 for (
const QString &p : std::as_const( paths ) )
590 if ( existing.contains( p ) )
593 existing.insert( p );
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 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.
QFlags< IdentifyFlag > IdentifyFlags
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.
#define BUILTIN_UNREACHABLE
struct projCtx_t PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
struct projCtx_t PJ_CONTEXT
const QgsCoordinateReferenceSystem & crs
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.