1 /***************************************************************************
2                              qgscoordinatereferencesystem_p.h
3 
4                              --------------------------------
5     begin                : 2016
6     copyright            : (C) 2016 by Nyall Dawson
7     email                : nyall dot dawson at gmail dot com
8 ***************************************************************************/
9 
10 /***************************************************************************
11  *                                                                         *
12  *   This program is free software; you can redistribute it and/or modify  *
13  *   it under the terms of the GNU General Public License as published by  *
14  *   the Free Software Foundation; either version 2 of the License, or     *
15  *   (at your option) any later version.                                   *
16  *                                                                         *
17  ***************************************************************************/
18 #ifndef QGSCOORDINATEREFERENCESYSTEM_PRIVATE_H
19 #define QGSCOORDINATEREFERENCESYSTEM_PRIVATE_H
20 
21 /// @cond PRIVATE
22 
23 //
24 //  W A R N I N G
25 //  -------------
26 //
27 // This file is not part of the QGIS API.  It exists purely as an
28 // implementation detail.  This header file may change from version to
29 // version without notice, or even be removed.
30 //
31 
32 #include "qgscoordinatereferencesystem.h"
33 
34 #include <proj.h>
35 #include "qgsprojutils.h"
36 #include "qgsreadwritelocker.h"
37 
38 #ifdef DEBUG
39 typedef struct OGRSpatialReferenceHS *OGRSpatialReferenceH;
40 #else
41 typedef void *OGRSpatialReferenceH;
42 #endif
43 
44 class QgsCoordinateReferenceSystemPrivate : public QSharedData
45 {
46   public:
47 
QgsCoordinateReferenceSystemPrivate()48     explicit QgsCoordinateReferenceSystemPrivate()
49     {
50     }
51 
QgsCoordinateReferenceSystemPrivate(const QgsCoordinateReferenceSystemPrivate & other)52     QgsCoordinateReferenceSystemPrivate( const QgsCoordinateReferenceSystemPrivate &other )
53       : QSharedData( other )
54       , mSrsId( other.mSrsId )
55       , mDescription( other.mDescription )
56       , mProjectionAcronym( other.mProjectionAcronym )
57       , mEllipsoidAcronym( other.mEllipsoidAcronym )
58       , mIsGeographic( other.mIsGeographic )
59       , mMapUnits( other.mMapUnits )
60       , mSRID( other.mSRID )
61       , mAuthId( other.mAuthId )
62       , mIsValid( other.mIsValid )
63       , mCoordinateEpoch( other.mCoordinateEpoch )
64       , mPj()
65       , mProj4( other.mProj4 )
66       , mWktPreferred( other.mWktPreferred )
67       , mAxisInvertedDirty( other.mAxisInvertedDirty )
68       , mAxisInverted( other.mAxisInverted )
69       , mProjObjects()
70     {
71     }
72 
~QgsCoordinateReferenceSystemPrivate()73     ~QgsCoordinateReferenceSystemPrivate()
74     {
75       QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
76       if ( !mProjObjects.empty() || mPj )
77       {
78         locker.changeMode( QgsReadWriteLocker::Write );
79         cleanPjObjects();
80       }
81     }
82 
83     //! The internal sqlite3 srs.db primary key for this CRS
84     long mSrsId = 0;
85 
86     //! A textual description of the CRS
87     QString mDescription;
88 
89     //! The official proj4 acronym for the projection family
90     QString mProjectionAcronym;
91 
92     //! The official proj4 acronym for the ellipsoid
93     QString mEllipsoidAcronym;
94 
95     //! Whether this is a geographic or projected coordinate system
96     bool mIsGeographic = false;
97 
98     //! The map units for the CRS
99     QgsUnitTypes::DistanceUnit mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
100 
101     //! If available, the PostGIS spatial_ref_sys identifier for this CRS (defaults to 0)
102     long mSRID = 0;
103 
104     //! If available the authority identifier for this CRS
105     QString mAuthId;
106 
107     //! Whether this CRS is properly defined and valid
108     bool mIsValid = false;
109 
110     //! Coordinate epoch
111     double mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
112 
113     // this is the "master" proj object, to be used as a template for new proj objects created on different threads ONLY.
114     // Always use threadLocalProjObject() instead of this.
115 
116   private:
117     QgsProjUtils::proj_pj_unique_ptr mPj;
118     PJ_CONTEXT *mPjParentContext = nullptr;
119 
cleanPjObjects()120     void cleanPjObjects()
121     {
122 
123       // During destruction of PJ* objects, the errno is set in the underlying
124       // context. Consequently the context attached to the PJ* must still exist !
125       // Which is not necessarily the case currently unfortunately. So
126       // create a temporary dummy context, and attach it to the PJ* before destroying
127       // it
128       PJ_CONTEXT *tmpContext = proj_context_create();
129       for ( auto it = mProjObjects.begin(); it != mProjObjects.end(); ++it )
130       {
131         proj_assign_context( it.value(), tmpContext );
132         proj_destroy( it.value() );
133       }
134       mProjObjects.clear();
135       if ( mPj )
136       {
137         proj_assign_context( mPj.get(), tmpContext );
138         mPj.reset();
139       }
140       proj_context_destroy( tmpContext );
141     }
142 
143   public:
144 
setPj(QgsProjUtils::proj_pj_unique_ptr obj)145     void setPj( QgsProjUtils::proj_pj_unique_ptr obj )
146     {
147       const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
148       cleanPjObjects();
149 
150       mPj = std::move( obj );
151       mPjParentContext = QgsProjContext::get();
152     }
153 
hasPj()154     bool hasPj() const
155     {
156       const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
157       return static_cast< bool >( mPj );
158     }
159 
160     mutable QString mProj4;
161 
162     mutable QString mWktPreferred;
163 
164     //! True if presence of an inverted axis needs to be recalculated
165     mutable bool mAxisInvertedDirty = false;
166 
167     //! Whether this is a coordinate system has inverted axis
168     mutable bool mAxisInverted = false;
169 
170   private:
171     mutable QReadWriteLock mProjLock{};
172     mutable QMap < PJ_CONTEXT *, PJ * > mProjObjects{};
173 
174   public:
175 
threadLocalProjObject()176     PJ *threadLocalProjObject() const
177     {
178       QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
179       if ( !mPj )
180         return nullptr;
181 
182       PJ_CONTEXT *context = QgsProjContext::get();
183       const QMap < PJ_CONTEXT *, PJ * >::const_iterator it = mProjObjects.constFind( context );
184 
185       if ( it != mProjObjects.constEnd() )
186       {
187         return it.value();
188       }
189 
190       // proj object doesn't exist yet, so we need to create
191       locker.changeMode( QgsReadWriteLocker::Write );
192 
193       PJ *res = proj_clone( context, mPj.get() );
194       mProjObjects.insert( context, res );
195       return res;
196     }
197 
198     // Only meant to be called by QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread()
removeObjectsBelongingToCurrentThread(PJ_CONTEXT * pj_context)199     bool removeObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
200     {
201       const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
202 
203       const QMap < PJ_CONTEXT *, PJ * >::iterator it = mProjObjects.find( pj_context );
204       if ( it != mProjObjects.end() )
205       {
206         proj_destroy( it.value() );
207         mProjObjects.erase( it );
208       }
209 
210       if ( mPjParentContext == pj_context )
211       {
212         mPj.reset();
213         mPjParentContext = nullptr;
214       }
215 
216       return mProjObjects.isEmpty();
217     }
218 
219   private:
220     QgsCoordinateReferenceSystemPrivate &operator= ( const QgsCoordinateReferenceSystemPrivate & ) = delete;
221 
222 };
223 
224 /// @endcond
225 
226 #endif //QGSCOORDINATEREFERENCESYSTEM_PRIVATE_H
227