QGIS API Documentation  3.6.0-Noosa (5873452)
qgscoordinatetransform_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatetransform_p.cpp
3  ----------------------------
4  begin : May 2017
5  copyright : (C) 2017 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 
19 #include "qgslogger.h"
20 #include "qgsapplication.h"
21 #include "qgsreadwritelocker.h"
22 
23 extern "C"
24 {
25 #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
26 #define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
27 #endif
28 #include <proj_api.h>
29 }
30 #include <sqlite3.h>
31 
32 #include <QStringList>
33 
35 
36 #ifdef USE_THREAD_LOCAL
37 thread_local QgsProjContextStore QgsCoordinateTransformPrivate::mProjContext;
38 #else
39 QThreadStorage< QgsProjContextStore * > QgsCoordinateTransformPrivate::mProjContext;
40 #endif
41 
42 QgsProjContextStore::QgsProjContextStore()
43 {
44  context = pj_ctx_alloc();
45 }
46 
47 QgsProjContextStore::~QgsProjContextStore()
48 {
49  pj_ctx_free( context );
50 }
51 
52 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
53 {
54  setFinder();
55 }
56 
57 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
58  const QgsCoordinateReferenceSystem &destination,
59  const QgsCoordinateTransformContext &context )
60  : mSourceCRS( source )
61  , mDestCRS( destination )
62 {
63  setFinder();
64  calculateTransforms( context );
65 }
66 
67 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
68  : mSourceCRS( source )
69  , mDestCRS( destination )
70  , mSourceDatumTransform( sourceDatumTransform )
71  , mDestinationDatumTransform( destDatumTransform )
72 {
73  setFinder();
74 }
75 
76 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
77  : QSharedData( other )
78  , mIsValid( other.mIsValid )
79  , mShortCircuit( other.mShortCircuit )
80  , mSourceCRS( other.mSourceCRS )
81  , mDestCRS( other.mDestCRS )
82  , mSourceDatumTransform( other.mSourceDatumTransform )
83  , mDestinationDatumTransform( other.mDestinationDatumTransform )
84 {
85  //must reinitialize to setup mSourceProjection and mDestinationProjection
86  initialize();
87 }
88 
89 QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
90 {
91  // free the proj objects
92  freeProj();
93 }
94 
95 bool QgsCoordinateTransformPrivate::checkValidity()
96 {
97  if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
98  {
99  invalidate();
100  return false;
101  }
102  return true;
103 }
104 
105 void QgsCoordinateTransformPrivate::invalidate()
106 {
107  mShortCircuit = true;
108  mIsValid = false;
109 }
110 
111 bool QgsCoordinateTransformPrivate::initialize()
112 {
113  invalidate();
114  if ( !mSourceCRS.isValid() )
115  {
116  // Pass through with no projection since we have no idea what the layer
117  // coordinates are and projecting them may not be appropriate
118  QgsDebugMsgLevel( QStringLiteral( "Source CRS is invalid!" ), 4 );
119  return false;
120  }
121 
122  if ( !mDestCRS.isValid() )
123  {
124  //No destination projection is set so we set the default output projection to
125  //be the same as input proj.
126  mDestCRS = mSourceCRS;
127  QgsDebugMsgLevel( QStringLiteral( "Destination CRS is invalid!" ), 4 );
128  return false;
129  }
130 
131  mIsValid = true;
132 
133  int sourceDatumTransform = mSourceDatumTransform;
134  int destDatumTransform = mDestinationDatumTransform;
135  bool useDefaultDatumTransform = ( sourceDatumTransform == - 1 && destDatumTransform == -1 );
136 
137  // init the projections (destination and source)
138  freeProj();
139 
140  mSourceProjString = mSourceCRS.toProj4();
141  if ( !useDefaultDatumTransform )
142  {
143  mSourceProjString = stripDatumTransform( mSourceProjString );
144  }
145  if ( sourceDatumTransform != -1 )
146  {
147  mSourceProjString += ( ' ' + QgsDatumTransform::datumTransformToProj( sourceDatumTransform ) );
148  }
149 
150  mDestProjString = mDestCRS.toProj4();
151  if ( !useDefaultDatumTransform )
152  {
153  mDestProjString = stripDatumTransform( mDestProjString );
154  }
155  if ( destDatumTransform != -1 )
156  {
157  mDestProjString += ( ' ' + QgsDatumTransform::datumTransformToProj( destDatumTransform ) );
158  }
159 
160  if ( !useDefaultDatumTransform )
161  {
162  addNullGridShifts( mSourceProjString, mDestProjString, sourceDatumTransform, destDatumTransform );
163  }
164 
165  // create proj projections for current thread
166  QPair<projPJ, projPJ> res = threadLocalProjData();
167 
168 #ifdef COORDINATE_TRANSFORM_VERBOSE
169  QgsDebugMsg( "From proj : " + mSourceCRS.toProj4() );
170  QgsDebugMsg( "To proj : " + mDestCRS.toProj4() );
171 #endif
172 
173  if ( !res.first || !res.second )
174  {
175  mIsValid = false;
176  }
177 
178 #ifdef COORDINATE_TRANSFORM_VERBOSE
179  if ( mIsValid )
180  {
181  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
182  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation for this layer was set to" ) );
183  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
184  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
185  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
186  }
187  else
188  {
189  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
190  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
191  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
192  }
193 #else
194  if ( !mIsValid )
195  {
196  QgsDebugMsg( QStringLiteral( "Coordinate transformation failed to initialize!" ) );
197  }
198 #endif
199 
200  //XXX todo overload == operator for QgsCoordinateReferenceSystem
201  //at the moment srs.parameters contains the whole proj def...soon it won't...
202  //if (mSourceCRS->toProj4() == mDestCRS->toProj4())
203  if ( mSourceCRS == mDestCRS )
204  {
205  // If the source and destination projection are the same, set the short
206  // circuit flag (no transform takes place)
207  mShortCircuit = true;
208  QgsDebugMsgLevel( QStringLiteral( "Source/Dest CRS equal, shortcircuit is set." ), 3 );
209  }
210  else
211  {
212  // Transform must take place
213  mShortCircuit = false;
214  QgsDebugMsgLevel( QStringLiteral( "Source/Dest CRS not equal, shortcircuit is not set." ), 3 );
215  }
216  return mIsValid;
217 }
218 
219 void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
220 {
221  // recalculate datum transforms from context
222  QgsDatumTransform::TransformPair transforms = context.calculateDatumTransforms( mSourceCRS, mDestCRS );
223  mSourceDatumTransform = transforms.sourceTransformId;
224  mDestinationDatumTransform = transforms.destinationTransformId;
225 }
226 
227 QPair<projPJ, projPJ> QgsCoordinateTransformPrivate::threadLocalProjData()
228 {
229  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
230 
231 #ifdef USE_THREAD_LOCAL
232  QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( mProjContext.get() ) );
233 #else
234  projCtx pContext = nullptr;
235  if ( mProjContext.hasLocalData() )
236  {
237  pContext = mProjContext.localData()->get();
238  }
239  else
240  {
241  mProjContext.setLocalData( new QgsProjContextStore() );
242  pContext = mProjContext.localData()->get();
243  }
244  QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( pContext ) );
245 #endif
246 
247  if ( it != mProjProjections.constEnd() )
248  {
249  QPair<projPJ, projPJ> res = it.value();
250  return res;
251  }
252 
253  // proj projections don't exist yet, so we need to create
255 
256 #ifdef USE_THREAD_LOCAL
257  QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( mProjContext.get(), mSourceProjString.toUtf8() ),
258  pj_init_plus_ctx( mProjContext.get(), mDestProjString.toUtf8() ) );
259  mProjProjections.insert( reinterpret_cast< uintptr_t>( mProjContext.get() ), res );
260 #else
261  QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( pContext, mSourceProjString.toUtf8() ),
262  pj_init_plus_ctx( pContext, mDestProjString.toUtf8() ) );
263  mProjProjections.insert( reinterpret_cast< uintptr_t>( pContext ), res );
264 #endif
265  return res;
266 }
267 
268 QString QgsCoordinateTransformPrivate::stripDatumTransform( const QString &proj4 ) const
269 {
270  QStringList parameterSplit = proj4.split( '+', QString::SkipEmptyParts );
271  QString currentParameter;
272  QString newProjString;
273 
274  for ( int i = 0; i < parameterSplit.size(); ++i )
275  {
276  currentParameter = parameterSplit.at( i );
277  if ( !currentParameter.startsWith( QLatin1String( "towgs84" ), Qt::CaseInsensitive )
278  && !currentParameter.startsWith( QLatin1String( "nadgrids" ), Qt::CaseInsensitive ) )
279  {
280  newProjString.append( '+' );
281  newProjString.append( currentParameter );
282  newProjString.append( ' ' );
283  }
284  }
285  return newProjString;
286 }
287 
288 void QgsCoordinateTransformPrivate::addNullGridShifts( QString &srcProjString, QString &destProjString,
289  int sourceDatumTransform, int destinationDatumTransform ) const
290 {
291  //if one transformation uses ntv2, the other one needs to be null grid shift
292  if ( destinationDatumTransform == -1 && srcProjString.contains( QLatin1String( "+nadgrids" ) ) ) //add null grid if source transformation is ntv2
293  {
294  destProjString += QLatin1String( " +nadgrids=@null" );
295  return;
296  }
297  if ( sourceDatumTransform == -1 && destProjString.contains( QLatin1String( "+nadgrids" ) ) )
298  {
299  srcProjString += QLatin1String( " +nadgrids=@null" );
300  return;
301  }
302 
303  //add null shift grid for google mercator
304  //(see e.g. http://trac.osgeo.org/proj/wiki/FAQ#ChangingEllipsoidWhycantIconvertfromWGS84toGoogleEarthVirtualGlobeMercator)
305  if ( mSourceCRS.authid().compare( QLatin1String( "EPSG:3857" ), Qt::CaseInsensitive ) == 0 && sourceDatumTransform == -1 )
306  {
307  srcProjString += QLatin1String( " +nadgrids=@null" );
308  }
309  if ( mDestCRS.authid().compare( QLatin1String( "EPSG:3857" ), Qt::CaseInsensitive ) == 0 && destinationDatumTransform == -1 )
310  {
311  destProjString += QLatin1String( " +nadgrids=@null" );
312  }
313 }
314 
315 void QgsCoordinateTransformPrivate::setFinder()
316 {
317 #if 0
318  // Attention! It should be possible to set PROJ_LIB
319  // but it can happen that it was previously set by installer
320  // (version 0.7) and the old installation was deleted
321 
322  // Another problem: PROJ checks if pj_finder was set before
323  // PROJ_LIB environment variable. pj_finder is probably set in
324  // GRASS gproj library when plugin is loaded, consequently
325  // PROJ_LIB is ignored
326 
327  pj_set_finder( finder );
328 #endif
329 }
330 
331 void QgsCoordinateTransformPrivate::freeProj()
332 {
333  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
334  QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constBegin();
335  for ( ; it != mProjProjections.constEnd(); ++it )
336  {
337  pj_free( it.value().first );
338  pj_free( it.value().second );
339  }
340  mProjProjections.clear();
341 }
342 
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Contains information about the context in which a coordinate transform is executed.
int destinationTransformId
ID for the datum transform to use when projecting to the destination CRS.
QgsDatumTransform::TransformPair calculateDatumTransforms(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the pair of source and destination datum transforms to use for a transform from the specified...
Contains datum transform information.
This class represents a coordinate reference system (CRS).
static QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID...
void changeMode(Mode mode)
Change the mode of the lock to mode.
int sourceTransformId
ID for the datum transform to use when projecting from the source CRS.
const char * finder(const char *name)