Branch data Line data Source code
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 : :
18 : : #include "qgscoordinatetransform_p.h"
19 : : #include "qgslogger.h"
20 : : #include "qgsapplication.h"
21 : : #include "qgsreadwritelocker.h"
22 : : #include "qgsmessagelog.h"
23 : :
24 : : #include "qgsprojutils.h"
25 : : #include <proj.h>
26 : : #include <proj_experimental.h>
27 : :
28 : : #include <sqlite3.h>
29 : :
30 : : #include <QStringList>
31 : :
32 : : /// @cond PRIVATE
33 : :
34 : : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
35 : : const QgsCoordinateReferenceSystem &destinationCrs,
36 : 5 : const QgsDatumTransform::GridDetails &grid )> QgsCoordinateTransformPrivate::sMissingRequiredGridHandler = nullptr;
37 : :
38 : : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
39 : : const QgsCoordinateReferenceSystem &destinationCrs,
40 : : const QgsDatumTransform::TransformDetails &preferredOperation,
41 : 5 : const QgsDatumTransform::TransformDetails &availableOperation )> QgsCoordinateTransformPrivate::sMissingPreferredGridHandler = nullptr;
42 : :
43 : : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
44 : : const QgsCoordinateReferenceSystem &destinationCrs,
45 : 5 : const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
46 : :
47 : : std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
48 : : const QgsCoordinateReferenceSystem &destinationCrs,
49 : 5 : const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
50 : :
51 : : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
52 : 318 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
53 : 318 : {
54 : 318 : }
55 : : Q_NOWARN_DEPRECATED_POP
56 : :
57 : : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
58 : 1532 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
59 : : const QgsCoordinateReferenceSystem &destination,
60 : : const QgsCoordinateTransformContext &context )
61 : 1532 : : mSourceCRS( source )
62 : 1532 : , mDestCRS( destination )
63 : 1532 : {
64 : 1532 : if ( mSourceCRS != mDestCRS )
65 : 1522 : calculateTransforms( context );
66 : 1532 : }
67 : : Q_NOWARN_DEPRECATED_POP
68 : :
69 : : Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
70 : 0 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
71 : 0 : : mSourceCRS( source )
72 : 0 : , mDestCRS( destination )
73 : 1850 : , mSourceDatumTransform( sourceDatumTransform )
74 : 0 : , mDestinationDatumTransform( destDatumTransform )
75 : 0 : {
76 : 0 : }
77 : :
78 : 0 : QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
79 : 0 : : QSharedData( other )
80 : 0 : , mAvailableOpCount( other.mAvailableOpCount )
81 : 0 : , mIsValid( other.mIsValid )
82 : 0 : , mShortCircuit( other.mShortCircuit )
83 : 1850 : , mSourceCRS( other.mSourceCRS )
84 : 0 : , mDestCRS( other.mDestCRS )
85 : 0 : , mSourceDatumTransform( other.mSourceDatumTransform )
86 : 0 : , mDestinationDatumTransform( other.mDestinationDatumTransform )
87 : 0 : , mProjCoordinateOperation( other.mProjCoordinateOperation )
88 : 0 : , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
89 : 1850 : , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
90 : 0 : , mIsReversed( other.mIsReversed )
91 : 0 : , mProjLock()
92 : 0 : , mProjProjections()
93 : 0 : , mProjFallbackProjections()
94 : 0 : {
95 : 0 : }
96 : : Q_NOWARN_DEPRECATED_POP
97 : :
98 : : Q_NOWARN_DEPRECATED_PUSH
99 : 1850 : QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
100 : 1850 : {
101 : 1850 : // free the proj objects
102 : 1850 : freeProj();
103 : 3700 : }
104 : 1850 : Q_NOWARN_DEPRECATED_POP
105 : :
106 : 1532 : bool QgsCoordinateTransformPrivate::checkValidity()
107 : 1850 : {
108 : 1532 : if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
109 : : {
110 : 1520 : invalidate();
111 : 1520 : return false;
112 : : }
113 : 12 : return true;
114 : 1532 : }
115 : :
116 : 1522 : void QgsCoordinateTransformPrivate::invalidate()
117 : : {
118 : 1522 : mShortCircuit = true;
119 : 1522 : mIsValid = false;
120 : 1522 : mAvailableOpCount = -1;
121 : 1522 : }
122 : :
123 : 2 : bool QgsCoordinateTransformPrivate::initialize()
124 : : {
125 : 2 : invalidate();
126 : 2 : if ( !mSourceCRS.isValid() )
127 : : {
128 : : // Pass through with no projection since we have no idea what the layer
129 : : // coordinates are and projecting them may not be appropriate
130 : 0 : QgsDebugMsgLevel( QStringLiteral( "Source CRS is invalid!" ), 4 );
131 : 0 : return false;
132 : : }
133 : :
134 : 2 : if ( !mDestCRS.isValid() )
135 : : {
136 : : //No destination projection is set so we set the default output projection to
137 : : //be the same as input proj.
138 : 0 : mDestCRS = mSourceCRS;
139 : 0 : QgsDebugMsgLevel( QStringLiteral( "Destination CRS is invalid!" ), 4 );
140 : 0 : return false;
141 : : }
142 : :
143 : 2 : mIsValid = true;
144 : :
145 : 2 : if ( mSourceCRS == mDestCRS )
146 : : {
147 : : // If the source and destination projection are the same, set the short
148 : : // circuit flag (no transform takes place)
149 : 1 : mShortCircuit = true;
150 : 1 : return true;
151 : : }
152 : :
153 : : // init the projections (destination and source)
154 : 1 : freeProj();
155 : :
156 : : // create proj projections for current thread
157 : 1 : ProjData res = threadLocalProjData();
158 : :
159 : : #ifdef COORDINATE_TRANSFORM_VERBOSE
160 : : QgsDebugMsg( "From proj : " + mSourceCRS.toProj() );
161 : : QgsDebugMsg( "To proj : " + mDestCRS.toProj() );
162 : : #endif
163 : :
164 : 1 : if ( !res )
165 : 0 : mIsValid = false;
166 : :
167 : : #ifdef COORDINATE_TRANSFORM_VERBOSE
168 : : if ( mIsValid )
169 : : {
170 : : QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
171 : : QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation for this layer was set to" ) );
172 : : QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
173 : : QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
174 : : QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
175 : : }
176 : : else
177 : : {
178 : : QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
179 : : QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
180 : : QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
181 : : }
182 : : #else
183 : 1 : if ( !mIsValid )
184 : : {
185 : 0 : QgsDebugMsg( QStringLiteral( "Coordinate transformation failed to initialize!" ) );
186 : 0 : }
187 : : #endif
188 : :
189 : : // Transform must take place
190 : 1 : mShortCircuit = false;
191 : :
192 : 1 : return mIsValid;
193 : 2 : }
194 : :
195 : 1522 : void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
196 : : {
197 : : // recalculate datum transforms from context
198 : 1522 : if ( mSourceCRS.isValid() && mDestCRS.isValid() )
199 : : {
200 : 6 : mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
201 : 6 : mShouldReverseCoordinateOperation = context.mustReverseCoordinateOperation( mSourceCRS, mDestCRS );
202 : 6 : mAllowFallbackTransforms = context.allowFallbackTransform( mSourceCRS, mDestCRS );
203 : 6 : }
204 : : else
205 : : {
206 : 1516 : mProjCoordinateOperation.clear();
207 : 1516 : mShouldReverseCoordinateOperation = false;
208 : 1516 : mAllowFallbackTransforms = false;
209 : : }
210 : 1522 : }
211 : :
212 : 0 : static void proj_collecting_logger( void *user_data, int /*level*/, const char *message )
213 : : {
214 : 0 : QStringList *dest = reinterpret_cast< QStringList * >( user_data );
215 : 0 : dest->append( QString( message ) );
216 : 0 : }
217 : :
218 : 0 : static void proj_logger( void *, int level, const char *message )
219 : : {
220 : : #ifndef QGISDEBUG
221 : : Q_UNUSED( message )
222 : : #endif
223 : 0 : if ( level == PJ_LOG_ERROR )
224 : : {
225 : 0 : QgsDebugMsg( QString( message ) );
226 : 0 : }
227 : 0 : else if ( level == PJ_LOG_DEBUG )
228 : : {
229 : 0 : QgsDebugMsgLevel( QString( message ), 3 );
230 : 0 : }
231 : 0 : }
232 : :
233 : 27 : ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
234 : : {
235 : 27 : QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
236 : :
237 : 27 : PJ_CONTEXT *context = QgsProjContext::get();
238 : 27 : QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
239 : :
240 : 27 : if ( it != mProjProjections.constEnd() )
241 : : {
242 : 26 : ProjData res = it.value();
243 : 26 : return res;
244 : : }
245 : :
246 : : // proj projections don't exist yet, so we need to create
247 : 1 : locker.changeMode( QgsReadWriteLocker::Write );
248 : :
249 : : // use a temporary proj error collector
250 : 1 : QStringList projErrors;
251 : 1 : proj_log_func( context, &projErrors, proj_collecting_logger );
252 : :
253 : 1 : mIsReversed = false;
254 : :
255 : 1 : QgsProjUtils::proj_pj_unique_ptr transform;
256 : 1 : if ( !mProjCoordinateOperation.isEmpty() )
257 : : {
258 : 0 : transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
259 : 0 : if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
260 : : {
261 : 0 : if ( sMissingGridUsedByContextHandler )
262 : : {
263 : 0 : QgsDatumTransform::TransformDetails desired;
264 : 0 : desired.proj = mProjCoordinateOperation;
265 : 0 : desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
266 : 0 : desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
267 : 0 : sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
268 : 0 : }
269 : : else
270 : : {
271 : 0 : const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
272 : 0 : mDestCRS.authid(),
273 : 0 : mProjCoordinateOperation );
274 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
275 : 0 : }
276 : :
277 : 0 : transform.reset();
278 : 0 : }
279 : : else
280 : : {
281 : 0 : mIsReversed = mShouldReverseCoordinateOperation;
282 : : }
283 : 0 : }
284 : :
285 : 1 : QString nonAvailableError;
286 : 1 : if ( !transform ) // fallback on default proj pathway
287 : : {
288 : 1 : if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
289 : : {
290 : 0 : proj_log_func( context, nullptr, nullptr );
291 : 0 : return nullptr;
292 : : }
293 : :
294 : 1 : PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
295 : :
296 : : // We want to check ALL grids, not just those available for use
297 : 1 : proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
298 : :
299 : : // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
300 : 1 : proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
301 : :
302 : 1 : if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
303 : : {
304 : 1 : mAvailableOpCount = proj_list_get_count( ops );
305 : 1 : if ( mAvailableOpCount < 1 )
306 : : {
307 : : // huh?
308 : 0 : int errNo = proj_context_errno( context );
309 : 0 : if ( errNo && errNo != -61 )
310 : : {
311 : 0 : nonAvailableError = QString( proj_errno_string( errNo ) );
312 : 0 : }
313 : : else
314 : : {
315 : 0 : nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
316 : : }
317 : 0 : }
318 : 1 : else if ( mAvailableOpCount == 1 )
319 : : {
320 : : // only a single operation available. Can we use it?
321 : 0 : transform.reset( proj_list_get( context, ops, 0 ) );
322 : 0 : if ( transform )
323 : : {
324 : 0 : if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
325 : : {
326 : : // uh oh :( something is missing! find what it is
327 : 0 : for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
328 : : {
329 : 0 : const char *shortName = nullptr;
330 : 0 : const char *fullName = nullptr;
331 : 0 : const char *packageName = nullptr;
332 : 0 : const char *url = nullptr;
333 : 0 : int directDownload = 0;
334 : 0 : int openLicense = 0;
335 : 0 : int isAvailable = 0;
336 : 0 : proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
337 : 0 : if ( !isAvailable )
338 : : {
339 : : // found it!
340 : 0 : if ( sMissingRequiredGridHandler )
341 : : {
342 : 0 : QgsDatumTransform::GridDetails gridDetails;
343 : 0 : gridDetails.shortName = QString( shortName );
344 : 0 : gridDetails.fullName = QString( fullName );
345 : 0 : gridDetails.packageName = QString( packageName );
346 : 0 : gridDetails.url = QString( url );
347 : 0 : gridDetails.directDownload = directDownload;
348 : 0 : gridDetails.openLicense = openLicense;
349 : 0 : gridDetails.isAvailable = isAvailable;
350 : 0 : sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
351 : 0 : }
352 : : else
353 : : {
354 : 0 : const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
355 : 0 : mDestCRS.authid(),
356 : : shortName );
357 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
358 : 0 : }
359 : 0 : break;
360 : : }
361 : 0 : }
362 : 0 : }
363 : : else
364 : : {
365 : :
366 : : // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
367 : 0 : transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
368 : 0 : if ( !transform )
369 : : {
370 : 0 : const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
371 : 0 : mDestCRS.authid() );
372 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
373 : 0 : }
374 : : }
375 : 0 : }
376 : 0 : }
377 : : else
378 : : {
379 : : // multiple operations available. Can we use the best one?
380 : 1 : QgsDatumTransform::TransformDetails preferred;
381 : 1 : bool missingPreferred = false;
382 : 1 : bool stillLookingForPreferred = true;
383 : 1 : for ( int i = 0; i < mAvailableOpCount; ++ i )
384 : : {
385 : 1 : transform.reset( proj_list_get( context, ops, i ) );
386 : 1 : const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
387 : 1 : if ( stillLookingForPreferred && transform && !isInstantiable )
388 : : {
389 : : // uh oh :( something is missing blocking us from the preferred operation!
390 : 0 : QgsDatumTransform::TransformDetails candidate = QgsDatumTransform::transformDetailsFromPj( transform.get() );
391 : 0 : if ( !candidate.proj.isEmpty() )
392 : : {
393 : 0 : preferred = candidate;
394 : 0 : missingPreferred = true;
395 : 0 : stillLookingForPreferred = false;
396 : 0 : }
397 : 0 : }
398 : 1 : if ( transform && isInstantiable )
399 : : {
400 : : // found one
401 : 1 : break;
402 : : }
403 : 0 : transform.reset();
404 : 0 : }
405 : :
406 : 1 : if ( transform && missingPreferred )
407 : : {
408 : : // found a transform, but it's not the preferred
409 : 0 : QgsDatumTransform::TransformDetails available = QgsDatumTransform::transformDetailsFromPj( transform.get() );
410 : 0 : if ( sMissingPreferredGridHandler )
411 : : {
412 : 0 : sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
413 : 0 : }
414 : : else
415 : : {
416 : 0 : const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
417 : 0 : mDestCRS.authid(),
418 : 0 : available.proj,
419 : 0 : preferred.proj );
420 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
421 : 0 : }
422 : 0 : }
423 : :
424 : : // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
425 : 1 : if ( transform )
426 : 1 : transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
427 : 1 : if ( !transform )
428 : : {
429 : 0 : const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
430 : 0 : mDestCRS.authid() );
431 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
432 : 0 : }
433 : 1 : }
434 : 1 : proj_list_destroy( ops );
435 : 1 : }
436 : 1 : proj_operation_factory_context_destroy( operationContext );
437 : 1 : }
438 : :
439 : 1 : if ( !transform && nonAvailableError.isEmpty() )
440 : : {
441 : 0 : int errNo = proj_context_errno( context );
442 : 0 : if ( errNo && errNo != -61 )
443 : : {
444 : 0 : nonAvailableError = QString( proj_errno_string( errNo ) );
445 : 0 : }
446 : 0 : else if ( !projErrors.empty() )
447 : : {
448 : 0 : nonAvailableError = projErrors.constLast();
449 : 0 : }
450 : :
451 : 0 : if ( nonAvailableError.isEmpty() )
452 : : {
453 : 0 : nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
454 : 0 : }
455 : : else
456 : : {
457 : : // strip proj prefixes from error string, so that it's nicer for users
458 : 0 : nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
459 : : }
460 : 0 : }
461 : :
462 : 1 : if ( !nonAvailableError.isEmpty() )
463 : : {
464 : 0 : if ( sCoordinateOperationCreationErrorHandler )
465 : : {
466 : 0 : sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
467 : 0 : }
468 : : else
469 : : {
470 : 0 : const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
471 : 0 : mDestCRS.authid(),
472 : : nonAvailableError );
473 : 0 : QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
474 : 0 : }
475 : 0 : }
476 : :
477 : : // reset logger to terminal output
478 : 1 : proj_log_func( context, nullptr, proj_logger );
479 : :
480 : 1 : if ( !transform )
481 : : {
482 : : // ouch!
483 : 0 : return nullptr;
484 : : }
485 : :
486 : 1 : ProjData res = transform.release();
487 : 1 : mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
488 : 1 : return res;
489 : 27 : }
490 : :
491 : 0 : ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
492 : : {
493 : 0 : QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
494 : :
495 : 0 : PJ_CONTEXT *context = QgsProjContext::get();
496 : 0 : QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
497 : :
498 : 0 : if ( it != mProjFallbackProjections.constEnd() )
499 : : {
500 : 0 : ProjData res = it.value();
501 : 0 : return res;
502 : : }
503 : :
504 : : // proj projections don't exist yet, so we need to create
505 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
506 : :
507 : 0 : QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
508 : 0 : if ( transform )
509 : 0 : transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
510 : :
511 : 0 : ProjData res = transform.release();
512 : 0 : mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
513 : 0 : return res;
514 : 0 : }
515 : :
516 : 0 : void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
517 : : {
518 : 0 : sMissingRequiredGridHandler = handler;
519 : 0 : }
520 : :
521 : 0 : void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
522 : : {
523 : 0 : sMissingPreferredGridHandler = handler;
524 : 0 : }
525 : :
526 : 0 : void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
527 : : {
528 : 0 : sCoordinateOperationCreationErrorHandler = handler;
529 : 0 : }
530 : :
531 : 0 : void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
532 : : {
533 : 0 : sMissingGridUsedByContextHandler = handler;
534 : 0 : }
535 : :
536 : 1851 : void QgsCoordinateTransformPrivate::freeProj()
537 : : {
538 : 1851 : QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
539 : 1851 : if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
540 : 1850 : return;
541 : 1 : QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
542 : :
543 : : // During destruction of PJ* objects, the errno is set in the underlying
544 : : // context. Consequently the context attached to the PJ* must still exist !
545 : : // Which is not necessarily the case currently unfortunately. So
546 : : // create a temporary dummy context, and attach it to the PJ* before destroying
547 : : // it
548 : 1 : PJ_CONTEXT *tmpContext = proj_context_create();
549 : 2 : for ( ; it != mProjProjections.constEnd(); ++it )
550 : : {
551 : 1 : proj_assign_context( it.value(), tmpContext );
552 : 1 : proj_destroy( it.value() );
553 : 1 : }
554 : :
555 : 1 : it = mProjFallbackProjections.constBegin();
556 : 1 : for ( ; it != mProjFallbackProjections.constEnd(); ++it )
557 : : {
558 : 0 : proj_assign_context( it.value(), tmpContext );
559 : 0 : proj_destroy( it.value() );
560 : 0 : }
561 : :
562 : 1 : proj_context_destroy( tmpContext );
563 : 1 : mProjProjections.clear();
564 : 1 : mProjFallbackProjections.clear();
565 : 1851 : }
566 : :
567 : 0 : bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
568 : : {
569 : 0 : QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
570 : :
571 : 0 : QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
572 : 0 : if ( it != mProjProjections.end() )
573 : : {
574 : 0 : proj_destroy( it.value() );
575 : 0 : mProjProjections.erase( it );
576 : 0 : }
577 : :
578 : 0 : it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
579 : 0 : if ( it != mProjFallbackProjections.end() )
580 : : {
581 : 0 : proj_destroy( it.value() );
582 : 0 : mProjFallbackProjections.erase( it );
583 : 0 : }
584 : :
585 : 0 : return mProjProjections.isEmpty();
586 : 0 : }
587 : :
588 : : ///@endcond
|