20#include <proj_experimental.h>
28#include <QRegularExpression>
32using namespace Qt::StringLiterals;
34#if defined( USE_THREAD_LOCAL ) && !defined( Q_OS_WIN )
37QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
42 mContext = proj_context_create();
49 QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
50 QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
51 proj_context_destroy( mContext );
56#if defined( USE_THREAD_LOCAL ) && !defined( Q_OS_WIN )
57 return sProjContext.mContext;
60 if ( sProjContext.hasLocalData() )
62 pContext = sProjContext.localData()->mContext;
67 pContext = sProjContext.localData()->mContext;
75 proj_destroy(
object );
80 const QString crsDef = u
"%1 +type=crs"_s.arg( projDef );
83 if ( !projSingleOperation )
87 if ( !coordinateSystem )
90 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
93 const char *outUnitAuthName =
nullptr;
94 const char *outUnitAuthCode =
nullptr;
96 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &outUnitAuthName, &outUnitAuthCode );
98 if ( outUnitAuthName && outUnitAuthCode )
100 const char *unitCategory =
nullptr;
101 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode,
nullptr,
nullptr, &unitCategory ) )
103 return QString( unitCategory ).compare(
"angular"_L1, Qt::CaseInsensitive ) == 0;
127 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
130 const char *outDirection0 =
nullptr;
131 const char *outDirection1 =
nullptr;
132 const char *outName0 =
nullptr;
133 const char *outName1 =
nullptr;
135 proj_cs_get_axis_info( context, pjCs.get(), 0, &outName0,
nullptr, &outDirection0,
nullptr,
nullptr,
nullptr,
nullptr );
137 proj_cs_get_axis_info( context, pjCs.get(), 1, &outName1,
nullptr, &outDirection1,
nullptr,
nullptr,
nullptr,
nullptr );
139 if ( QString( outDirection0 ).compare(
"north"_L1, Qt::CaseInsensitive ) == 0 && QString( outDirection1 ).compare(
"east"_L1, Qt::CaseInsensitive ) == 0 )
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 ) )
148 return QString( outName0 ).startsWith(
"northing"_L1, Qt::CaseInsensitive ) && QString( outName1 ).startsWith(
"easting"_L1, Qt::CaseInsensitive );
165 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) :
nullptr );
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;
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 )
182 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) :
nullptr );
185 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
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;
202 switch ( proj_get_type( crs ) )
204 case PJ_TYPE_COMPOUND_CRS:
208 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
211 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
216 case PJ_TYPE_VERTICAL_CRS:
236 switch ( proj_get_type( crs ) )
238 case PJ_TYPE_COMPOUND_CRS:
242 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
245 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
250 case PJ_TYPE_VERTICAL_CRS:
269 switch ( proj_get_type( crs ) )
271 case PJ_TYPE_COMPOUND_CRS:
280 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
285 case PJ_TYPE_BOUND_CRS:
300 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
301 for (
int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
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 )
322 switch ( proj_get_type( crs ) )
324 case PJ_TYPE_BOUND_CRS:
356 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
357 QString messageString( message );
358 messageString.replace(
"internal_proj_create: "_L1, QString() );
359 dest->append( messageString );
368 if ( level == PJ_LOG_ERROR )
370 const QString messageString( message );
371 if ( messageString ==
"push: Invalid latitude"_L1 )
381 else if ( level == PJ_LOG_DEBUG )
393 if ( !horizontalCrs || !verticalCrs )
405 *errors = projLogger.
errors();
418 int *confidence =
nullptr;
419 if ( PJ_OBJ_LIST *crsList = proj_identify(
QgsProjContext::get(), crs,
nullptr,
nullptr, &confidence ) )
421 const int count = proj_list_get_count( crsList );
422 int bestConfidence = 0;
424 for (
int i = 0; i < count; ++i )
426 if ( confidence[i] >= bestConfidence )
429 switch ( proj_get_type( candidateCrs.get() ) )
431 case PJ_TYPE_BOUND_CRS:
444 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
446 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName ==
"EPSG"_L1 ) )
448 bestConfidence = confidence[i];
449 matchedCrs = std::move( candidateCrs );
453 proj_list_destroy( crsList );
454 proj_int_list_destroy( confidence );
455 if ( matchedCrs && bestConfidence >= 70 )
457 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
458 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
461 return !authName.isEmpty() && !authCode.isEmpty();
466 if ( projDef.isEmpty() )
471 if ( !coordinateOperation )
474 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
479 const thread_local QRegularExpression regex( u
"\\+(?:nad)?grids=(.*?)\\s"_s );
481 QList< QgsDatumTransform::GridDetails > grids;
482 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
483 while ( matches.hasNext() )
485 const QRegularExpressionMatch match = matches.next();
486 const QString gridName = match.captured( 1 );
489 const char *fullName =
nullptr;
490 const char *packageName =
nullptr;
491 const char *url =
nullptr;
492 int directDownload = 0;
495 proj_grid_get_info_from_database(
QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
496 grid.
fullName = QString( fullName );
498 grid.
url = QString( url );
502 grids.append( grid );
508QStringList QgsProjUtils::nonAvailableGrids(
const QString &projDef )
510 if ( projDef.isEmpty() )
511 return QStringList();
516 return QStringList();
519 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
521 const char *shortName =
nullptr;
523 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &isAvailable );
525 res << QString( shortName );
533 return PROJ_VERSION_MAJOR;
538 return PROJ_VERSION_MINOR;
544 const char *version = proj_context_get_database_metadata( context,
"EPSG.VERSION" );
545 return QString( version );
551 const char *date = proj_context_get_database_metadata( context,
"EPSG.DATE" );
552 return QDate::fromString( date, Qt::DateFormat::ISODate );
558 const char *version = proj_context_get_database_metadata( context,
"ESRI.VERSION" );
559 return QString( version );
565 const char *date = proj_context_get_database_metadata( context,
"ESRI.DATE" );
566 return QDate::fromString( date, Qt::DateFormat::ISODate );
572 const char *version = proj_context_get_database_metadata( context,
"IGNF.VERSION" );
573 return QString( version );
579 const char *date = proj_context_get_database_metadata( context,
"IGNF.DATE" );
580 return QDate::fromString( date, Qt::DateFormat::ISODate );
585 const QString path( proj_info().searchpath );
588 paths = path.split(
';' );
590 paths = path.split(
':' );
593 QSet<QString> existing;
596 res.reserve( paths.count() );
597 for (
const QString &p : std::as_const( paths ) )
599 if ( existing.contains( p ) )
602 existing.insert( p );
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
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.