QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgscoordinatetransformcontext.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatetransformcontext.cpp
3  ---------------------------------
4  begin : November 2017
5  copyright : (C) 2017 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 
20 #include "qgscoordinatetransform.h"
21 #include "qgssettings.h"
22 #include "qgsprojutils.h"
23 
24 
26  : d( new QgsCoordinateTransformContextPrivate() )
27 {}
28 
30 
32  : d( rhs.d )
33 {}
34 
36 {
37  d = rhs.d;
38  return *this;
39 }
40 
42 {
43  if ( d == rhs.d )
44  return true;
45 
46  d->mLock.lockForRead();
47  rhs.d->mLock.lockForRead();
48  bool equal = d->mSourceDestDatumTransforms == rhs.d->mSourceDestDatumTransforms;
49  d->mLock.unlock();
50  rhs.d->mLock.unlock();
51  return equal;
52 }
53 
55 {
56  d.detach();
57  // play it safe
58  d->mLock.lockForWrite();
59  d->mSourceDestDatumTransforms.clear();
60  d->mLock.unlock();
61 }
62 
64 {
65 #if PROJ_VERSION_MAJOR>=6
66  return QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair>();
67 #else
68  d->mLock.lockForRead();
69  auto res = d->mSourceDestDatumTransforms;
70  res.detach();
71  d->mLock.unlock();
72  return res;
73 #endif
74 }
75 
76 QMap<QPair<QString, QString>, QString> QgsCoordinateTransformContext::coordinateOperations() const
77 {
78 #if PROJ_VERSION_MAJOR>=6
79  d->mLock.lockForRead();
80  auto res = d->mSourceDestDatumTransforms;
81  res.detach();
82  d->mLock.unlock();
83  return res;
84 #else
85  return QMap<QPair<QString, QString>, QString>();
86 #endif
87 }
88 
89 bool QgsCoordinateTransformContext::addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransform, int destinationTransform )
90 {
91  if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
92  return false;
93 #if PROJ_VERSION_MAJOR>=6
94  Q_UNUSED( sourceTransform )
95  Q_UNUSED( destinationTransform )
96  return false;
97 #else
98  d.detach();
99  d->mLock.lockForWrite();
100  d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs.authid(), destinationCrs.authid() ), QgsDatumTransform::TransformPair( sourceTransform, destinationTransform ) );
101  d->mLock.unlock();
102  return true;
103 #endif
104 }
105 
106 bool QgsCoordinateTransformContext::addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString )
107 {
108  if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
109  return false;
110 #if PROJ_VERSION_MAJOR>=6
111  d.detach();
112  d->mLock.lockForWrite();
113  d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs.authid(), destinationCrs.authid() ), coordinateOperationProjString );
114  d->mLock.unlock();
115  return true;
116 #else
117  Q_UNUSED( coordinateOperationProjString )
118  return false;
119 #endif
120 }
121 
123 {
124  removeCoordinateOperation( sourceCrs, destinationCrs );
125 }
126 
128 {
129  d->mSourceDestDatumTransforms.remove( qMakePair( sourceCrs.authid(), destinationCrs.authid() ) );
130 }
131 
133 {
134 #if PROJ_VERSION_MAJOR>=6
135  const QString t = calculateCoordinateOperation( source, destination );
136  return !t.isEmpty();
137 #else
141  // calculateDatumTransforms already takes care of switching source and destination
142  return t.sourceTransformId != -1 || t.destinationTransformId != -1;
143 #endif
144 }
145 
147 {
148 #if PROJ_VERSION_MAJOR>=6
149  Q_UNUSED( source )
150  Q_UNUSED( destination )
151  return QgsDatumTransform::TransformPair( -1, -1 );
152 #else
153  QString srcKey = source.authid();
154  QString destKey = destination.authid();
155 
156  d->mLock.lockForRead();
157  // highest priority is exact match for source/dest pair
158  QgsDatumTransform::TransformPair res = d->mSourceDestDatumTransforms.value( qMakePair( srcKey, destKey ), QgsDatumTransform::TransformPair( -1, -1 ) );
159  if ( res.sourceTransformId == -1 && res.destinationTransformId == -1 )
160  {
161  // try to reverse
162  QgsDatumTransform::TransformPair res2 = d->mSourceDestDatumTransforms.value( qMakePair( destKey, srcKey ), QgsDatumTransform::TransformPair( -1, -1 ) );
164  }
165  d->mLock.unlock();
166  return res;
167 #endif
168 }
169 
171 {
172 #if PROJ_VERSION_MAJOR>=6
173  const QString srcKey = source.authid();
174  const QString destKey = destination.authid();
175 
176  d->mLock.lockForRead();
177  QString res = d->mSourceDestDatumTransforms.value( qMakePair( srcKey, destKey ), QString() );
178  if ( res.isEmpty() )
179  {
180  // try to reverse
181  res = d->mSourceDestDatumTransforms.value( qMakePair( destKey, srcKey ), QString() );
182  }
183  d->mLock.unlock();
184  return res;
185 #else
186  Q_UNUSED( source )
187  Q_UNUSED( destination )
188  return QString();
189 #endif
190 }
191 
192 bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const QgsReadWriteContext &, QStringList &missingTransforms )
193 {
194  d.detach();
195  d->mLock.lockForWrite();
196 
197  d->mSourceDestDatumTransforms.clear();
198 
199  const QDomNodeList contextNodes = element.elementsByTagName( QStringLiteral( "transformContext" ) );
200  if ( contextNodes.count() < 1 )
201  {
202  d->mLock.unlock();
203  return true;
204  }
205 
206  missingTransforms.clear();
207  bool result = true;
208 
209  const QDomElement contextElem = contextNodes.at( 0 ).toElement();
210 
211  // src/dest transforms
212  const QDomNodeList srcDestNodes = contextElem.elementsByTagName( QStringLiteral( "srcDest" ) );
213  for ( int i = 0; i < srcDestNodes.size(); ++i )
214  {
215  const QDomElement transformElem = srcDestNodes.at( i ).toElement();
216  const QString key1 = transformElem.attribute( QStringLiteral( "source" ) );
217  const QString key2 = transformElem.attribute( QStringLiteral( "dest" ) );
218 
219 #if PROJ_VERSION_MAJOR>=6
220  const QString coordinateOp = transformElem.attribute( QStringLiteral( "coordinateOp" ) );
221 
222  // try to instantiate operation, and check for missing grids
223  if ( !QgsProjUtils::coordinateOperationIsAvailable( coordinateOp ) )
224  {
225  // not possible in current Proj 6 api!
226  // QgsCoordinateTransform will alert users to this, we don't need to use missingTransforms here
227  result = false;
228  }
229 
230  d->mSourceDestDatumTransforms.insert( qMakePair( key1, key2 ), coordinateOp );
231 #else
232  QString value1 = transformElem.attribute( QStringLiteral( "sourceTransform" ) );
233  QString value2 = transformElem.attribute( QStringLiteral( "destTransform" ) );
234 
236  int datumId1 = -1;
237  int datumId2 = -1;
238  //warn if value1 or value2 is non-empty, yet no matching transform was found
239  if ( !value1.isEmpty() )
240  {
242  if ( datumId1 < 0 )
243  {
244  result = false;
245  missingTransforms << value1;
246  }
247  }
248  if ( !value2.isEmpty() )
249  {
251  if ( datumId2 < 0 )
252  {
253  result = false;
254  missingTransforms << value2;
255  }
256  }
258 
259  d->mSourceDestDatumTransforms.insert( qMakePair( key1, key2 ), QgsDatumTransform::TransformPair( datumId1, datumId2 ) );
260 #endif
261  }
262 
263  d->mLock.unlock();
264  return result;
265 }
266 
267 void QgsCoordinateTransformContext::writeXml( QDomElement &element, const QgsReadWriteContext & ) const
268 {
269  d->mLock.lockForRead();
270 
271  QDomElement contextElem = element.ownerDocument().createElement( QStringLiteral( "transformContext" ) );
272 
273  //src/dest transforms
274  for ( auto it = d->mSourceDestDatumTransforms.constBegin(); it != d->mSourceDestDatumTransforms.constEnd(); ++ it )
275  {
276  QDomElement transformElem = element.ownerDocument().createElement( QStringLiteral( "srcDest" ) );
277  transformElem.setAttribute( QStringLiteral( "source" ), it.key().first );
278  transformElem.setAttribute( QStringLiteral( "dest" ), it.key().second );
279 #if PROJ_VERSION_MAJOR>=6
280  transformElem.setAttribute( QStringLiteral( "coordinateOp" ), it.value() );
281 #else
283  transformElem.setAttribute( QStringLiteral( "sourceTransform" ), it.value().sourceTransformId < 0 ? QString() : QgsDatumTransform::datumTransformToProj( it.value().sourceTransformId ) );
284  transformElem.setAttribute( QStringLiteral( "destTransform" ), it.value().destinationTransformId < 0 ? QString() : QgsDatumTransform::datumTransformToProj( it.value().destinationTransformId ) );
286 #endif
287  contextElem.appendChild( transformElem );
288  }
289 
290  element.appendChild( contextElem );
291  d->mLock.unlock();
292 }
293 
295 {
296  d.detach();
297  d->mLock.lockForWrite();
298 
299  d->mSourceDestDatumTransforms.clear();
300 
301  QgsSettings settings;
302  settings.beginGroup( QStringLiteral( "/Projections" ) );
303  QStringList projectionKeys = settings.allKeys();
304 
305  //collect src and dest entries that belong together
306 #if PROJ_VERSION_MAJOR>=6
307  QMap< QPair< QString, QString >, QString > transforms;
308 #else
309  QMap< QPair< QString, QString >, QPair< int, int > > transforms;
310 #endif
311  QStringList::const_iterator pkeyIt = projectionKeys.constBegin();
312  for ( ; pkeyIt != projectionKeys.constEnd(); ++pkeyIt )
313  {
314 #if PROJ_VERSION_MAJOR>=6
315  if ( pkeyIt->contains( QLatin1String( "coordinateOp" ) ) )
316  {
317  QStringList split = pkeyIt->split( '/' );
318  QString srcAuthId, destAuthId;
319  if ( ! split.isEmpty() )
320  {
321  srcAuthId = split.at( 0 );
322  }
323  if ( split.size() > 1 )
324  {
325  destAuthId = split.at( 1 ).split( '_' ).at( 0 );
326  }
327 
328  const QString proj = settings.value( *pkeyIt ).toString();
329  transforms[ qMakePair( srcAuthId, destAuthId )] = proj;
330  }
331 #else
332  if ( pkeyIt->contains( QLatin1String( "srcTransform" ) ) || pkeyIt->contains( QLatin1String( "destTransform" ) ) )
333  {
334  QStringList split = pkeyIt->split( '/' );
335  QString srcAuthId, destAuthId;
336  if ( ! split.isEmpty() )
337  {
338  srcAuthId = split.at( 0 );
339  }
340  if ( split.size() > 1 )
341  {
342  destAuthId = split.at( 1 ).split( '_' ).at( 0 );
343  }
344 
345  QString proj = settings.value( *pkeyIt ).toString();
349  if ( pkeyIt->contains( QLatin1String( "srcTransform" ) ) )
350  {
351  transforms[ qMakePair( srcAuthId, destAuthId )].first = datumId;
352  }
353  else if ( pkeyIt->contains( QLatin1String( "destTransform" ) ) )
354  {
355  transforms[ qMakePair( srcAuthId, destAuthId )].second = datumId;
356  }
357  }
358 #endif
359  }
360 
361  // add transforms to context
362  auto transformIt = transforms.constBegin();
363  for ( ; transformIt != transforms.constEnd(); ++transformIt )
364  {
365 #if PROJ_VERSION_MAJOR>=6
366  d->mSourceDestDatumTransforms.insert( transformIt.key(), transformIt.value() );
367 #else
368  d->mSourceDestDatumTransforms.insert( transformIt.key(), QgsDatumTransform::TransformPair( transformIt.value().first, transformIt.value().second ) );
369 #endif
370  }
371 
372  d->mLock.unlock();
373  settings.endGroup();
374 }
375 
377 {
378  QgsSettings settings;
379  settings.beginGroup( QStringLiteral( "/Projections" ) );
380  QStringList groupKeys = settings.allKeys();
381  QStringList::const_iterator groupKeyIt = groupKeys.constBegin();
382  for ( ; groupKeyIt != groupKeys.constEnd(); ++groupKeyIt )
383  {
384  if ( groupKeyIt->contains( QLatin1String( "srcTransform" ) ) || groupKeyIt->contains( QLatin1String( "destTransform" ) ) || groupKeyIt->contains( QLatin1String( "coordinateOp" ) ) )
385  {
386  settings.remove( *groupKeyIt );
387  }
388  }
389 
390  for ( auto transformIt = d->mSourceDestDatumTransforms.constBegin(); transformIt != d->mSourceDestDatumTransforms.constEnd(); ++transformIt )
391  {
392  const QString srcAuthId = transformIt.key().first;
393  const QString destAuthId = transformIt.key().second;
394 
395 #if PROJ_VERSION_MAJOR>=6
396  const QString proj = transformIt.value();
397  settings.setValue( srcAuthId + "//" + destAuthId + "_coordinateOp", proj );
398 #else
399  int sourceDatumTransform = transformIt.value().sourceTransformId;
400  QString sourceDatumProj;
402  if ( sourceDatumTransform >= 0 )
403  sourceDatumProj = QgsDatumTransform::datumTransformToProj( sourceDatumTransform );
404  int destinationDatumTransform = transformIt.value().destinationTransformId;
405  QString destinationDatumProj;
406  if ( destinationDatumTransform >= 0 )
407  destinationDatumProj = QgsDatumTransform::datumTransformToProj( destinationDatumTransform );
409 
410  settings.setValue( srcAuthId + "//" + destAuthId + "_srcTransform", sourceDatumProj );
411  settings.setValue( srcAuthId + "//" + destAuthId + "_destTransform", destinationDatumProj );
412 #endif
413  }
414 
415  settings.endGroup();
416 }
The class is used as a container of context for various read/write operations on other objects...
void endGroup()
Resets the group to what it was before the corresponding beginGroup() call.
Definition: qgssettings.cpp:97
void removeCoordinateOperation(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs)
Removes the coordinate operation for the specified sourceCrs and destinationCrs.
QgsCoordinateTransformContext & operator=(const QgsCoordinateTransformContext &rhs)
Assignment operator.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
Q_DECL_DEPRECATED void removeSourceDestinationDatumTransform(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs)
Removes the source to destination datum transform pair for the specified sourceCrs and destinationCrs...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:624
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
QStringList allKeys() const
Returns a list of all keys, including subkeys, that can be read using the QSettings object...
bool operator==(const QgsCoordinateTransformContext &rhs) const
bool addCoordinateOperation(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString)
Adds a new coordinateOperationProjString to use when projecting coordinates from the specified source...
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms)
Reads the context&#39;s state from a DOM element.
QMap< QPair< QString, QString >, QString > coordinateOperations() const
Returns the stored mapping for source to destination CRS pairs to associated coordinate operation to ...
void readSettings()
Reads the context&#39;s state from application settings.
void clear()
Clears all stored transform information from the context.
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.
Q_DECL_DEPRECATED QMap< QPair< QString, QString >, QgsDatumTransform::TransformPair > sourceDestinationDatumTransforms() const
Returns the stored mapping for source to destination CRS pairs to associated datum transforms to use...
Q_DECL_DEPRECATED bool addSourceDestinationDatumTransform(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransformId, int destinationTransformId)
Adds a new sourceTransform and destinationTransform to use when projecting coordinates from the speci...
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the context&#39;s state to a DOM element.
void beginGroup(const QString &prefix, QgsSettings::Section section=QgsSettings::NoSection)
Appends prefix to the current group.
Definition: qgssettings.cpp:87
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:625
QString calculateCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the Proj coordinate operation string to use when transforming from the specified source CRS t...
Q_DECL_DEPRECATED 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.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
This class represents a coordinate reference system (CRS).
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID...
static Q_DECL_DEPRECATED int projStringToDatumTransformId(const QString &string)
Returns the datum transform ID corresponding to a specified proj string.
void writeSettings()
Write the context&#39;s state to application settings.
int sourceTransformId
ID for the datum transform to use when projecting from the source CRS.
bool hasTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the context has a valid coordinate operation to use when transforming from the specif...
QString authid() const
Returns the authority identifier for the CRS.
QgsCoordinateTransformContext()
Constructor for QgsCoordinateTransformContext.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.