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;
129 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
132 const char *outDirection =
nullptr;
135 proj_cs_get_axis_info( context, pjCs.get(), 0,
144 return QString( outDirection ).compare( QLatin1String(
"north" ), Qt::CaseInsensitive ) == 0;
160 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
163 const PJ_TYPE type = proj_get_type( datum.get() );
164 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
165 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
168 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
169 const QString code( proj_get_id_code( datum.get(), 0 ) );
170 if ( authName == QLatin1String(
"EPSG" ) && code == QLatin1String(
"6326" ) )
178 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
181 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
184 const PJ_TYPE type = proj_get_type( member.get() );
185 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
186 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
199 switch ( proj_get_type(
crs ) )
201 case PJ_TYPE_COMPOUND_CRS:
205 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
208 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
213 case PJ_TYPE_VERTICAL_CRS:
233 switch ( proj_get_type(
crs ) )
235 case PJ_TYPE_COMPOUND_CRS:
239 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
242 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
247 case PJ_TYPE_VERTICAL_CRS:
266 switch ( proj_get_type(
crs ) )
268 case PJ_TYPE_COMPOUND_CRS:
277 res.reset( proj_crs_get_sub_crs( context,
crs, i ) );
292 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
293 for (
int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
295 const char *outDirection =
nullptr;
296 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
305 const QString outDirectionString = QString( outDirection );
306 if ( outDirectionString.compare( QLatin1String(
"geocentricZ" ), Qt::CaseInsensitive ) == 0
307 || outDirectionString.compare( QLatin1String(
"up" ), Qt::CaseInsensitive ) == 0
308 || outDirectionString.compare( QLatin1String(
"down" ), Qt::CaseInsensitive ) == 0 )
322 switch ( proj_get_type(
crs ) )
324 case PJ_TYPE_BOUND_CRS:
343#if PROJ_VERSION_MAJOR>=8
354 throw QgsNotSupportedException( QObject::tr(
"Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
360 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
361 QString messageString( message );
362 messageString.replace( QLatin1String(
"internal_proj_create: " ), QString() );
363 dest->append( messageString );
369 if ( level == PJ_LOG_ERROR )
371 const QString messageString( message );
372 if ( messageString == QLatin1String(
"push: Invalid latitude" ) )
382 else if ( level == PJ_LOG_DEBUG )
394 if ( !horizontalCrs || !verticalCrs )
405 const_cast< PJ *
>( horizontalCrs ),
406 const_cast< PJ *
>( verticalCrs ) ) );
409 *errors = projLogger.
errors();
422 int *confidence =
nullptr;
425 const int count = proj_list_get_count( crsList );
426 int bestConfidence = 0;
428 for (
int i = 0; i < count; ++i )
430 if ( confidence[i] >= bestConfidence )
433 switch ( proj_get_type( candidateCrs.get() ) )
435 case PJ_TYPE_BOUND_CRS:
448 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
450 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String(
"EPSG" ) ) )
452 bestConfidence = confidence[i];
453 matchedCrs = std::move( candidateCrs );
457 proj_list_destroy( crsList );
458 proj_int_list_destroy( confidence );
459 if ( matchedCrs && bestConfidence >= 70 )
461 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
462 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
465 return !authName.isEmpty() && !authCode.isEmpty();
470 if ( projDef.isEmpty() )
475 if ( !coordinateOperation )
478 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
483 const thread_local QRegularExpression regex( QStringLiteral(
"\\+(?:nad)?grids=(.*?)\\s" ) );
485 QList< QgsDatumTransform::GridDetails > grids;
486 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
487 while ( matches.hasNext() )
489 const QRegularExpressionMatch match = matches.next();
490 const QString gridName = match.captured( 1 );
493 const char *fullName =
nullptr;
494 const char *packageName =
nullptr;
495 const char *url =
nullptr;
496 int directDownload = 0;
499 proj_grid_get_info_from_database(
QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
500 grid.
fullName = QString( fullName );
502 grid.
url = QString( url );
506 grids.append( grid );
512QStringList QgsProjUtils::nonAvailableGrids(
const QString &projDef )
514 if ( projDef.isEmpty() )
515 return QStringList();
520 return QStringList();
523 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
525 const char *shortName =
nullptr;
527 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &isAvailable );
529 res << QString( shortName );
537 return PROJ_VERSION_MAJOR;
542 return PROJ_VERSION_MINOR;
548 const char *version = proj_context_get_database_metadata( context,
"EPSG.VERSION" );
549 return QString( version );
555 const char *date = proj_context_get_database_metadata( context,
"EPSG.DATE" );
556 return QDate::fromString( date, Qt::DateFormat::ISODate );
562 const char *version = proj_context_get_database_metadata( context,
"ESRI.VERSION" );
563 return QString( version );
569 const char *date = proj_context_get_database_metadata( context,
"ESRI.DATE" );
570 return QDate::fromString( date, Qt::DateFormat::ISODate );
576 const char *version = proj_context_get_database_metadata( context,
"IGNF.VERSION" );
577 return QString( version );
583 const char *date = proj_context_get_database_metadata( context,
"IGNF.DATE" );
584 return QDate::fromString( date, Qt::DateFormat::ISODate );
589 const QString path( proj_info().searchpath );
592 paths = path.split(
';' );
594 paths = path.split(
':' );
597 QSet<QString> existing;
600 res.reserve( paths.count() );
601 for (
const QString &p : std::as_const( paths ) )
603 if ( existing.contains( p ) )
606 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.
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.
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.
#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.