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 ) );
282 case PJ_TYPE_BOUND_CRS:
297 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
298 for (
int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
300 const char *outDirection =
nullptr;
301 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
310 const QString outDirectionString = QString( outDirection );
311 if ( outDirectionString.compare( QLatin1String(
"geocentricZ" ), Qt::CaseInsensitive ) == 0
312 || outDirectionString.compare( QLatin1String(
"up" ), Qt::CaseInsensitive ) == 0
313 || outDirectionString.compare( QLatin1String(
"down" ), Qt::CaseInsensitive ) == 0 )
327 switch ( proj_get_type(
crs ) )
329 case PJ_TYPE_BOUND_CRS:
361 QStringList *dest =
reinterpret_cast< QStringList *
>( user_data );
362 QString messageString( message );
363 messageString.replace( QLatin1String(
"internal_proj_create: " ), QString() );
364 dest->append( messageString );
374 if ( level == PJ_LOG_ERROR )
376 const QString messageString( message );
377 if ( messageString == QLatin1String(
"push: Invalid latitude" ) )
387 else if ( level == PJ_LOG_DEBUG )
399 if ( !horizontalCrs || !verticalCrs )
410 const_cast< PJ *
>( horizontalCrs ),
411 const_cast< PJ *
>( verticalCrs ) ) );
414 *errors = projLogger.
errors();
427 int *confidence =
nullptr;
430 const int count = proj_list_get_count( crsList );
431 int bestConfidence = 0;
433 for (
int i = 0; i < count; ++i )
435 if ( confidence[i] >= bestConfidence )
438 switch ( proj_get_type( candidateCrs.get() ) )
440 case PJ_TYPE_BOUND_CRS:
453 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
455 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String(
"EPSG" ) ) )
457 bestConfidence = confidence[i];
458 matchedCrs = std::move( candidateCrs );
462 proj_list_destroy( crsList );
463 proj_int_list_destroy( confidence );
464 if ( matchedCrs && bestConfidence >= 70 )
466 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
467 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
470 return !authName.isEmpty() && !authCode.isEmpty();
475 if ( projDef.isEmpty() )
480 if ( !coordinateOperation )
483 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
488 const thread_local QRegularExpression regex( QStringLiteral(
"\\+(?:nad)?grids=(.*?)\\s" ) );
490 QList< QgsDatumTransform::GridDetails > grids;
491 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
492 while ( matches.hasNext() )
494 const QRegularExpressionMatch match = matches.next();
495 const QString gridName = match.captured( 1 );
498 const char *fullName =
nullptr;
499 const char *packageName =
nullptr;
500 const char *url =
nullptr;
501 int directDownload = 0;
504 proj_grid_get_info_from_database(
QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
505 grid.
fullName = QString( fullName );
507 grid.
url = QString( url );
511 grids.append( grid );
517QStringList QgsProjUtils::nonAvailableGrids(
const QString &projDef )
519 if ( projDef.isEmpty() )
520 return QStringList();
525 return QStringList();
528 for (
int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
530 const char *shortName =
nullptr;
532 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr, &isAvailable );
534 res << QString( shortName );
542 return PROJ_VERSION_MAJOR;
547 return PROJ_VERSION_MINOR;
553 const char *version = proj_context_get_database_metadata( context,
"EPSG.VERSION" );
554 return QString( version );
560 const char *date = proj_context_get_database_metadata( context,
"EPSG.DATE" );
561 return QDate::fromString( date, Qt::DateFormat::ISODate );
567 const char *version = proj_context_get_database_metadata( context,
"ESRI.VERSION" );
568 return QString( version );
574 const char *date = proj_context_get_database_metadata( context,
"ESRI.DATE" );
575 return QDate::fromString( date, Qt::DateFormat::ISODate );
581 const char *version = proj_context_get_database_metadata( context,
"IGNF.VERSION" );
582 return QString( version );
588 const char *date = proj_context_get_database_metadata( context,
"IGNF.DATE" );
589 return QDate::fromString( date, Qt::DateFormat::ISODate );
594 const QString path( proj_info().searchpath );
597 paths = path.split(
';' );
599 paths = path.split(
':' );
602 QSet<QString> existing;
605 res.reserve( paths.count() );
606 for (
const QString &p : std::as_const( paths ) )
608 if ( existing.contains( p ) )
611 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)
const QgsCoordinateReferenceSystem & crs
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.