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,
105 if ( outUnitAuthName && outUnitAuthCode )
107 const char *unitCategory =
nullptr;
108 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode,
nullptr,
nullptr, &unitCategory ) )
110 return QString( unitCategory ).compare(
"angular"_L1, Qt::CaseInsensitive ) == 0;
134 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
137 const char *outDirection0 =
nullptr;
138 const char *outDirection1 =
nullptr;
139 const char *outName0 =
nullptr;
140 const char *outName1 =
nullptr;
142 proj_cs_get_axis_info( context, pjCs.get(), 0,
152 proj_cs_get_axis_info( context, pjCs.get(), 1,
162 if ( QString( outDirection0 ).compare(
"north"_L1, Qt::CaseInsensitive ) == 0 &&
163 QString( outDirection1 ).compare(
"east"_L1, Qt::CaseInsensitive ) == 0 )
169 if ( ( QString( outDirection0 ).compare(
"north"_L1, Qt::CaseInsensitive ) == 0 &&
170 QString( outDirection1 ).compare(
"north"_L1, Qt::CaseInsensitive ) == 0 ) ||
171 ( QString( outDirection0 ).compare(
"south"_L1, Qt::CaseInsensitive ) == 0 &&
172 QString( outDirection1 ).compare(
"south"_L1, Qt::CaseInsensitive ) == 0 ) )
174 return QString( outName0 ).startsWith(
"northing"_L1, Qt::CaseInsensitive ) &&
175 QString( outName1 ).startsWith(
"easting"_L1, Qt::CaseInsensitive ) ;
192 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) :
nullptr );
195 const PJ_TYPE type = proj_get_type( datum.get() );
196 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
197 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
200 const QString authName( proj_get_id_auth_name( datum.get(), 0 ) );
201 const QString code( proj_get_id_code( datum.get(), 0 ) );
202 if ( authName ==
"EPSG"_L1 && code ==
"6326"_L1 )
210 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) :
nullptr );
213 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
216 const PJ_TYPE type = proj_get_type( member.get() );
217 isDynamic = type == PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME ||
218 type == PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME;
231 switch ( proj_get_type( crs ) )
233 case PJ_TYPE_COMPOUND_CRS:
237 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
240 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
245 case PJ_TYPE_VERTICAL_CRS:
265 switch ( proj_get_type( crs ) )
267 case PJ_TYPE_COMPOUND_CRS:
271 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
274 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
279 case PJ_TYPE_VERTICAL_CRS:
298 switch ( proj_get_type( crs ) )
300 case PJ_TYPE_COMPOUND_CRS:
309 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
314 case PJ_TYPE_BOUND_CRS:
329 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
330 for (
int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
332 const char *outDirection =
nullptr;
333 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
342 const QString outDirectionString = QString( outDirection );
343 if ( outDirectionString.compare(
"geocentricZ"_L1, Qt::CaseInsensitive ) == 0
344 || outDirectionString.compare(
"up"_L1, Qt::CaseInsensitive ) == 0
345 || outDirectionString.compare(
"down"_L1, Qt::CaseInsensitive ) == 0 )
359 switch ( proj_get_type( crs ) )
361 case PJ_TYPE_BOUND_CRS:
393 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
394 QString messageString( message );
395 messageString.replace(
"internal_proj_create: "_L1, QString() );
396 dest->append( messageString );
406 if ( level == PJ_LOG_ERROR )
408 const QString messageString( message );
409 if ( messageString ==
"push: Invalid latitude"_L1 )
419 else if ( level == PJ_LOG_DEBUG )
431 if ( !horizontalCrs || !verticalCrs )
442 const_cast< PJ *
>( horizontalCrs ),
443 const_cast< PJ *
>( verticalCrs ) ) );
446 *errors = projLogger.
errors();
459 int *confidence =
nullptr;
460 if ( PJ_OBJ_LIST *crsList = proj_identify(
QgsProjContext::get(), crs,
nullptr,
nullptr, &confidence ) )
462 const int count = proj_list_get_count( crsList );
463 int bestConfidence = 0;
465 for (
int i = 0; i < count; ++i )
467 if ( confidence[i] >= bestConfidence )
470 switch ( proj_get_type( candidateCrs.get() ) )
472 case PJ_TYPE_BOUND_CRS:
485 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
487 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName ==
"EPSG"_L1 ) )
489 bestConfidence = confidence[i];
490 matchedCrs = std::move( candidateCrs );
494 proj_list_destroy( crsList );
495 proj_int_list_destroy( confidence );
496 if ( matchedCrs && bestConfidence >= 70 )
498 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
499 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
502 return !authName.isEmpty() && !authCode.isEmpty();
507 if ( projDef.isEmpty() )
512 if ( !coordinateOperation )
515 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
520 const thread_local QRegularExpression regex( u
"\\+(?:nad)?grids=(.*?)\\s"_s );
522 QList< QgsDatumTransform::GridDetails > grids;
523 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
524 while ( matches.hasNext() )
526 const QRegularExpressionMatch match = matches.next();
527 const QString gridName = match.captured( 1 );
530 const char *fullName =
nullptr;
531 const char *packageName =
nullptr;
532 const char *url =
nullptr;
533 int directDownload = 0;
536 proj_grid_get_info_from_database(
QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
537 grid.
fullName = QString( fullName );
539 grid.
url = QString( url );
543 grids.append( grid );
549QStringList QgsProjUtils::nonAvailableGrids(
const QString &projDef )
551 if ( projDef.isEmpty() )
552 return QStringList();
557 return QStringList();
560 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
562 const char *shortName =
nullptr;
564 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &isAvailable );
566 res << QString( shortName );
574 return PROJ_VERSION_MAJOR;
579 return PROJ_VERSION_MINOR;
585 const char *version = proj_context_get_database_metadata( context,
"EPSG.VERSION" );
586 return QString( version );
592 const char *date = proj_context_get_database_metadata( context,
"EPSG.DATE" );
593 return QDate::fromString( date, Qt::DateFormat::ISODate );
599 const char *version = proj_context_get_database_metadata( context,
"ESRI.VERSION" );
600 return QString( version );
606 const char *date = proj_context_get_database_metadata( context,
"ESRI.DATE" );
607 return QDate::fromString( date, Qt::DateFormat::ISODate );
613 const char *version = proj_context_get_database_metadata( context,
"IGNF.VERSION" );
614 return QString( version );
620 const char *date = proj_context_get_database_metadata( context,
"IGNF.DATE" );
621 return QDate::fromString( date, Qt::DateFormat::ISODate );
626 const QString path( proj_info().searchpath );
629 paths = path.split(
';' );
631 paths = path.split(
':' );
634 QSet<QString> existing;
637 res.reserve( paths.count() );
638 for (
const QString &p : std::as_const( paths ) )
640 if ( existing.contains( p ) )
643 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.