QGIS API Documentation 3.41.0-Master (c1f2f2e8088)
Loading...
Searching...
No Matches
qgsprojutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprojutils.h
3 -------------------
4 begin : March 2019
5 copyright : (C) 2019 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17#include "qgsprojutils.h"
18#include "qgis.h"
20#include "qgsexception.h"
21#include "qgslogger.h"
22#include <QString>
23#include <QSet>
24#include <QRegularExpression>
25#include <QDate>
26
27#include <proj.h>
28#include <proj_experimental.h>
29
30#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
31thread_local QgsProjContext QgsProjContext::sProjContext;
32#else
33QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
34#endif
35
37{
38 mContext = proj_context_create();
39}
40
42{
43 // Call removeFromCacheObjectsBelongingToCurrentThread() before
44 // destroying the context
45 QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
46 QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
47 proj_context_destroy( mContext );
48}
49
51{
52#if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
53 return sProjContext.mContext;
54#else
55 PJ_CONTEXT *pContext = nullptr;
56 if ( sProjContext.hasLocalData() )
57 {
58 pContext = sProjContext.localData()->mContext;
59 }
60 else
61 {
62 sProjContext.setLocalData( new QgsProjContext() );
63 pContext = sProjContext.localData()->mContext;
64 }
65 return pContext;
66#endif
67}
68
70{
71 proj_destroy( object );
72}
73
74bool QgsProjUtils::usesAngularUnit( const QString &projDef )
75{
76 const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
78 QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
79 if ( !projSingleOperation )
80 return false;
81
82 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
83 if ( !coordinateSystem )
84 return false;
85
86 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
87 if ( axisCount > 0 )
88 {
89 const char *outUnitAuthName = nullptr;
90 const char *outUnitAuthCode = nullptr;
91 // Read only first axis
92 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
93 nullptr,
94 nullptr,
95 nullptr,
96 nullptr,
97 nullptr,
98 &outUnitAuthName,
99 &outUnitAuthCode );
100
101 if ( outUnitAuthName && outUnitAuthCode )
102 {
103 const char *unitCategory = nullptr;
104 if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
105 {
106 return QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
107 }
108 }
109 }
110 return false;
111}
112
114{
115 //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
116 if ( !crs )
117 return false;
118
119 PJ_CONTEXT *context = QgsProjContext::get();
120
122 if ( !horizCrs )
123 return false;
124
125 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, horizCrs.get() ) );
126 if ( !pjCs )
127 return false;
128
129 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
130 if ( axisCount > 0 )
131 {
132 const char *outDirection = nullptr;
133 // Read only first axis, see if it is degrees / north
134
135 proj_cs_get_axis_info( context, pjCs.get(), 0,
136 nullptr,
137 nullptr,
138 &outDirection,
139 nullptr,
140 nullptr,
141 nullptr,
142 nullptr
143 );
144 return QString( outDirection ).compare( QLatin1String( "north" ), Qt::CaseInsensitive ) == 0;
145 }
146 return false;
147}
148
150{
151 // ported from GDAL OGRSpatialReference::IsDynamic()
152 bool isDynamic = false;
153 PJ_CONTEXT *context = QgsProjContext::get();
154
155 // prefer horizontal crs if possible
157 if ( !crs )
158 candidate = unboundCrs( crs );
159
160 proj_pj_unique_ptr datum( candidate ? proj_crs_get_datum( context, candidate.get() ) : nullptr );
161 if ( datum )
162 {
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;
166 if ( !isDynamic )
167 {
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" ) )
171 {
172 isDynamic = true;
173 }
174 }
175 }
176 else
177 {
178 proj_pj_unique_ptr ensemble( candidate ? proj_crs_get_datum_ensemble( context, candidate.get() ) : nullptr );
179 if ( ensemble )
180 {
181 proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), 0 ) );
182 if ( member )
183 {
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;
187 }
188 }
189 }
190 return isDynamic;
191}
192
194{
195 if ( !crs )
196 return nullptr;
197
198 PJ_CONTEXT *context = QgsProjContext::get();
199 switch ( proj_get_type( crs ) )
200 {
201 case PJ_TYPE_COMPOUND_CRS:
202 {
203 int i = 0;
204 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
205 while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
206 {
207 i++;
208 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
209 }
210 return res;
211 }
212
213 case PJ_TYPE_VERTICAL_CRS:
214 return nullptr;
215
216 // maybe other types to handle??
217
218 default:
219 return unboundCrs( crs );
220 }
221
222#ifndef _MSC_VER // unreachable
223 return nullptr;
224#endif
225}
226
228{
229 if ( !crs )
230 return nullptr;
231
232 PJ_CONTEXT *context = QgsProjContext::get();
233 switch ( proj_get_type( crs ) )
234 {
235 case PJ_TYPE_COMPOUND_CRS:
236 {
237 int i = 0;
238 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
239 while ( res && ( proj_get_type( res.get() ) != PJ_TYPE_VERTICAL_CRS ) )
240 {
241 i++;
242 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
243 }
244 return res;
245 }
246
247 case PJ_TYPE_VERTICAL_CRS:
248 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
249
250 // maybe other types to handle??
251
252 default:
253 return nullptr;
254 }
255
257}
258
260{
261 if ( !crs )
262 return false;
263
264 PJ_CONTEXT *context = QgsProjContext::get();
265
266 switch ( proj_get_type( crs ) )
267 {
268 case PJ_TYPE_COMPOUND_CRS:
269 {
270 int i = 0;
271 QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
272 while ( res )
273 {
274 if ( hasVerticalAxis( res.get() ) )
275 return true;
276 i++;
277 res.reset( proj_crs_get_sub_crs( context, crs, i ) );
278 }
279 return false;
280 }
281
282 case PJ_TYPE_BOUND_CRS:
283 {
284 return hasVerticalAxis( proj_get_source_crs( context, crs ) );
285 }
286
287 // maybe other types to handle like this??
288
289 default:
290 break;
291 }
292
293 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
294 if ( !pjCs )
295 return false;
296
297 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
298 for ( int axisIndex = 0; axisIndex < axisCount; ++axisIndex )
299 {
300 const char *outDirection = nullptr;
301 proj_cs_get_axis_info( context, pjCs.get(), axisIndex,
302 nullptr,
303 nullptr,
304 &outDirection,
305 nullptr,
306 nullptr,
307 nullptr,
308 nullptr
309 );
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 )
314 {
315 return true;
316 }
317 }
318 return false;
319}
320
322{
323 if ( !crs )
324 return nullptr;
325
326 PJ_CONTEXT *context = QgsProjContext::get();
327 switch ( proj_get_type( crs ) )
328 {
329 case PJ_TYPE_BOUND_CRS:
330 return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
331
332 // maybe other types to handle??
333
334 default:
335 return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
336 }
337
338#ifndef _MSC_VER // unreachable
339 return nullptr;
340#endif
341}
342
344{
345 if ( !crs )
346 return nullptr;
347
348 PJ_CONTEXT *context = QgsProjContext::get();
350 if ( !candidate ) // purely vertical CRS
351 candidate = unboundCrs( crs );
352
353 if ( !candidate )
354 return nullptr;
355
356 return QgsProjUtils::proj_pj_unique_ptr( proj_crs_get_datum_ensemble( context, candidate.get() ) );
357}
358
359void QgsProjUtils::proj_collecting_logger( void *user_data, int /*level*/, const char *message )
360{
361 QStringList *dest = reinterpret_cast< QStringList * >( user_data );
362 QString messageString( message );
363 messageString.replace( QLatin1String( "internal_proj_create: " ), QString() );
364 dest->append( messageString );
365}
366
367void QgsProjUtils::proj_silent_logger( void * /*user_data*/, int /*level*/, const char * /*message*/ )
368{
369}
370
371void QgsProjUtils::proj_logger( void *, int level, const char *message )
372{
373#ifdef QGISDEBUG
374 if ( level == PJ_LOG_ERROR )
375 {
376 const QString messageString( message );
377 if ( messageString == QLatin1String( "push: Invalid latitude" ) )
378 {
379 // these messages tend to spam the console as they can be repeated 1000s of times
380 QgsDebugMsgLevel( messageString, 3 );
381 }
382 else
383 {
384 QgsDebugError( messageString );
385 }
386 }
387 else if ( level == PJ_LOG_DEBUG )
388 {
389 QgsDebugMsgLevel( QString( message ), 3 );
390 }
391#else
392 ( void )level;
393 ( void )message;
394#endif
395}
396
397QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::createCompoundCrs( const PJ *horizontalCrs, const PJ *verticalCrs, QStringList *errors )
398{
399 if ( !horizontalCrs || !verticalCrs )
400 return nullptr;
401
402 PJ_CONTEXT *context = QgsProjContext::get();
403 // collect errors instead of dumping them to terminal
404
406
407 // const cast here is for compatibility with proj < 9.5
408 QgsProjUtils::proj_pj_unique_ptr compoundCrs( proj_create_compound_crs( context,
409 nullptr,
410 const_cast< PJ *>( horizontalCrs ),
411 const_cast< PJ * >( verticalCrs ) ) );
412
413 if ( errors )
414 *errors = projLogger.errors();
415
416 return compoundCrs;
417}
418
419bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
420{
421 authName.clear();
422 authCode.clear();
423
424 if ( !crs )
425 return false;
426
427 int *confidence = nullptr;
428 if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
429 {
430 const int count = proj_list_get_count( crsList );
431 int bestConfidence = 0;
433 for ( int i = 0; i < count; ++i )
434 {
435 if ( confidence[i] >= bestConfidence )
436 {
437 QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
438 switch ( proj_get_type( candidateCrs.get() ) )
439 {
440 case PJ_TYPE_BOUND_CRS:
441 // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
442 // consider them a candidate for a match here (depending on the identify flags, that is!)
444 break;
445 else
446 continue;
447
448 default:
449 break;
450 }
451
452 candidateCrs = QgsProjUtils::unboundCrs( candidateCrs.get() );
453 const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
454 // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
455 if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String( "EPSG" ) ) )
456 {
457 bestConfidence = confidence[i];
458 matchedCrs = std::move( candidateCrs );
459 }
460 }
461 }
462 proj_list_destroy( crsList );
463 proj_int_list_destroy( confidence );
464 if ( matchedCrs && bestConfidence >= 70 )
465 {
466 authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
467 authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
468 }
469 }
470 return !authName.isEmpty() && !authCode.isEmpty();
471}
472
474{
475 if ( projDef.isEmpty() )
476 return true;
477
478 PJ_CONTEXT *context = QgsProjContext::get();
479 QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
480 if ( !coordinateOperation )
481 return false;
482
483 return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
484}
485
486QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
487{
488 const thread_local QRegularExpression regex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
489
490 QList< QgsDatumTransform::GridDetails > grids;
491 QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
492 while ( matches.hasNext() )
493 {
494 const QRegularExpressionMatch match = matches.next();
495 const QString gridName = match.captured( 1 );
497 grid.shortName = gridName;
498 const char *fullName = nullptr;
499 const char *packageName = nullptr;
500 const char *url = nullptr;
501 int directDownload = 0;
502 int openLicense = 0;
503 int available = 0;
504 proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
505 grid.fullName = QString( fullName );
506 grid.packageName = QString( packageName );
507 grid.url = QString( url );
508 grid.directDownload = directDownload;
509 grid.openLicense = openLicense;
510 grid.isAvailable = available;
511 grids.append( grid );
512 }
513 return grids;
514}
515
516#if 0
517QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
518{
519 if ( projDef.isEmpty() )
520 return QStringList();
521
522 PJ_CONTEXT *context = QgsProjContext::get();
523 QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
524 if ( !op )
525 return QStringList();
526
527 QStringList res;
528 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
529 {
530 const char *shortName = nullptr;
531 int isAvailable = 0;
532 proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
533 if ( !isAvailable )
534 res << QString( shortName );
535 }
536 return res;
537}
538#endif
539
541{
542 return PROJ_VERSION_MAJOR;
543}
544
546{
547 return PROJ_VERSION_MINOR;
548}
549
551{
552 PJ_CONTEXT *context = QgsProjContext::get();
553 const char *version = proj_context_get_database_metadata( context, "EPSG.VERSION" );
554 return QString( version );
555}
556
558{
559 PJ_CONTEXT *context = QgsProjContext::get();
560 const char *date = proj_context_get_database_metadata( context, "EPSG.DATE" );
561 return QDate::fromString( date, Qt::DateFormat::ISODate );
562}
563
565{
566 PJ_CONTEXT *context = QgsProjContext::get();
567 const char *version = proj_context_get_database_metadata( context, "ESRI.VERSION" );
568 return QString( version );
569}
570
572{
573 PJ_CONTEXT *context = QgsProjContext::get();
574 const char *date = proj_context_get_database_metadata( context, "ESRI.DATE" );
575 return QDate::fromString( date, Qt::DateFormat::ISODate );
576}
577
579{
580 PJ_CONTEXT *context = QgsProjContext::get();
581 const char *version = proj_context_get_database_metadata( context, "IGNF.VERSION" );
582 return QString( version );
583}
584
586{
587 PJ_CONTEXT *context = QgsProjContext::get();
588 const char *date = proj_context_get_database_metadata( context, "IGNF.DATE" );
589 return QDate::fromString( date, Qt::DateFormat::ISODate );
590}
591
593{
594 const QString path( proj_info().searchpath );
595 QStringList paths;
596#ifdef Q_OS_WIN
597 paths = path.split( ';' );
598#else
599 paths = path.split( ':' );
600#endif
601
602 QSet<QString> existing;
603 // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
604 QStringList res;
605 res.reserve( paths.count() );
606 for ( const QString &p : std::as_const( paths ) )
607 {
608 if ( existing.contains( p ) )
609 continue;
610
611 existing.insert( p );
612 res << p;
613 }
614 return res;
615}
616
617//
618// QgsScopedProjCollectingLogger
619//
620
625
627{
628 // reset logger back to terminal output
629 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
630}
631
632//
633// QgsScopedProjSilentLogger
634//
635
640
642{
643 // reset logger back to terminal output
644 proj_log_func( QgsProjContext::get(), nullptr, QgsProjUtils::proj_logger );
645}
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
Definition qgis.h:6766
struct pj_ctx PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
struct pj_ctx PJ_CONTEXT
struct PJconsts PJ
const QgsCoordinateReferenceSystem & crs
Contains information about a projection transformation grid file.
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
QString fullName
Full name of transform grid.
bool directDownload
true if direct download of grid is possible
QString packageName
Name of package the grid is included within.
QString url
Url to download grid from.
bool openLicense
true if grid is available under an open license
void CORE_EXPORT operator()(PJ *object) const
Destroys an PJ object, using the correct proj calls.