QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 
25 {
27 }
28 
29 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
30 template<>
31 bool qMapLessThanKey<QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem>>( const QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem> &key1,
32  const QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem> &key2 )
33 {
34  const QPair< QString, QString > key1String = qMakePair( crsToKey( key1.first ), crsToKey( key1.second ) );
35  const QPair< QString, QString > key2String = qMakePair( crsToKey( key2.first ), crsToKey( key2.second ) );
36  return key1String < key2String;
37 }
38 #endif
39 
41  : d( new QgsCoordinateTransformContextPrivate() )
42 {}
43 
45 
47  : d( rhs.d )
48 {}
49 
51 {
52  d = rhs.d;
53  return *this;
54 }
55 
57 {
58  if ( d == rhs.d )
59  return true;
60 
61  d->mLock.lockForRead();
62  rhs.d->mLock.lockForRead();
63  const bool equal = d->mSourceDestDatumTransforms == rhs.d->mSourceDestDatumTransforms;
64  d->mLock.unlock();
65  rhs.d->mLock.unlock();
66  return equal;
67 }
68 
70 {
71  d.detach();
72  // play it safe
73  d->mLock.lockForWrite();
74  d->mSourceDestDatumTransforms.clear();
75  d->mLock.unlock();
76 }
77 
79 {
80  return QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair>();
81 }
82 
83 QMap<QPair<QString, QString>, QString> QgsCoordinateTransformContext::coordinateOperations() const
84 {
85  d->mLock.lockForRead();
86  auto res = d->mSourceDestDatumTransforms;
87  res.detach();
88  d->mLock.unlock();
89  QMap<QPair<QString, QString>, QString> results;
90  for ( auto it = res.constBegin(); it != res.constEnd(); ++it )
91  results.insert( qMakePair( it.key().first.authid(), it.key().second.authid() ), it.value().operation );
92 
93  return results;
94 }
95 
96 bool QgsCoordinateTransformContext::addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransform, int destinationTransform )
97 {
98  if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
99  return false;
100  Q_UNUSED( sourceTransform )
101  Q_UNUSED( destinationTransform )
102  return false;
103 }
104 
105 bool QgsCoordinateTransformContext::addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString, bool allowFallback )
106 {
107  if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
108  return false;
109  d.detach();
110  d->mLock.lockForWrite();
111  QgsCoordinateTransformContextPrivate::OperationDetails details;
112  details.operation = coordinateOperationProjString;
113  details.allowFallback = allowFallback;
114  d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs, destinationCrs ), details );
115  d->mLock.unlock();
116  return true;
117 }
118 
120 {
121  removeCoordinateOperation( sourceCrs, destinationCrs );
122 }
123 
125 {
126  d->mSourceDestDatumTransforms.remove( qMakePair( sourceCrs, destinationCrs ) );
127 }
128 
130 {
131  const QString t = calculateCoordinateOperation( source, destination );
132  return !t.isEmpty();
133 }
134 
136 {
137  Q_UNUSED( source )
138  Q_UNUSED( destination )
139  return QgsDatumTransform::TransformPair( -1, -1 );
140 }
141 
143 {
144  if ( !source.isValid() || !destination.isValid() )
145  return QString();
146 
147  d->mLock.lockForRead();
148  QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
149  if ( res.operation.isEmpty() )
150  {
151  // try to reverse
152  res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
153  }
154  d->mLock.unlock();
155  return res.operation;
156 }
157 
159 {
160  if ( !source.isValid() || !destination.isValid() )
161  return false;
162 
163  d->mLock.lockForRead();
164  QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
165  if ( res.operation.isEmpty() )
166  {
167  // try to reverse
168  res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
169  }
170  d->mLock.unlock();
171  return res.allowFallback;
172 }
173 
175 {
176  if ( !source.isValid() || !destination.isValid() )
177  return false;
178 
179  d->mLock.lockForRead();
180  QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
181  if ( !res.operation.isEmpty() )
182  {
183  d->mLock.unlock();
184  return false;
185  }
186  // see if the reverse operation is present
187  res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
188  if ( !res.operation.isEmpty() )
189  {
190  d->mLock.unlock();
191  return true;
192  }
193 
194  d->mLock.unlock();
195  return false;
196 }
197 
198 bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const QgsReadWriteContext &, QStringList &missingTransforms )
199 {
200  d.detach();
201  d->mLock.lockForWrite();
202 
203  d->mSourceDestDatumTransforms.clear();
204 
205  const QDomNodeList contextNodes = element.elementsByTagName( QStringLiteral( "transformContext" ) );
206  if ( contextNodes.count() < 1 )
207  {
208  d->mLock.unlock();
209  return true;
210  }
211 
212  missingTransforms.clear();
213  bool result = true;
214 
215  const QDomElement contextElem = contextNodes.at( 0 ).toElement();
216 
217  // src/dest transforms
218  const QDomNodeList srcDestNodes = contextElem.elementsByTagName( QStringLiteral( "srcDest" ) );
219  for ( int i = 0; i < srcDestNodes.size(); ++i )
220  {
221  const QDomElement transformElem = srcDestNodes.at( i ).toElement();
222 
223  const QDomElement srcElem = transformElem.firstChildElement( QStringLiteral( "src" ) );
224  const QDomElement destElem = transformElem.firstChildElement( QStringLiteral( "dest" ) );
225 
228  if ( !srcElem.isNull() && !destElem.isNull() )
229  {
230  srcCrs.readXml( srcElem );
231  destCrs.readXml( destElem );
232  }
233  else
234  {
235  // for older project compatibility
236  const QString key1 = transformElem.attribute( QStringLiteral( "source" ) );
237  const QString key2 = transformElem.attribute( QStringLiteral( "dest" ) );
238  srcCrs = QgsCoordinateReferenceSystem( key1 );
239  destCrs = QgsCoordinateReferenceSystem( key2 );
240  }
241 
242  if ( !srcCrs.isValid() || !destCrs.isValid() )
243  continue;
244 
245  const QString coordinateOp = transformElem.attribute( QStringLiteral( "coordinateOp" ) );
246  const bool allowFallback = transformElem.attribute( QStringLiteral( "allowFallback" ), QStringLiteral( "1" ) ).toInt();
247 
248  // try to instantiate operation, and check for missing grids
249  if ( !QgsProjUtils::coordinateOperationIsAvailable( coordinateOp ) )
250  {
251  // not possible in current Proj 6 api!
252  // QgsCoordinateTransform will alert users to this, we don't need to use missingTransforms here
253  result = false;
254  }
255 
256  QgsCoordinateTransformContextPrivate::OperationDetails deets;
257  deets.operation = coordinateOp;
258  deets.allowFallback = allowFallback;
259  d->mSourceDestDatumTransforms.insert( qMakePair( srcCrs, destCrs ), deets );
260  }
261 
262  d->mLock.unlock();
263  return result;
264 }
265 
266 void QgsCoordinateTransformContext::writeXml( QDomElement &element, const QgsReadWriteContext & ) const
267 {
268  d->mLock.lockForRead();
269 
270  QDomDocument doc = element.ownerDocument();
271 
272  QDomElement contextElem = doc.createElement( QStringLiteral( "transformContext" ) );
273 
274  //src/dest transforms
275  for ( auto it = d->mSourceDestDatumTransforms.constBegin(); it != d->mSourceDestDatumTransforms.constEnd(); ++ it )
276  {
277  QDomElement transformElem = doc.createElement( QStringLiteral( "srcDest" ) );
278  QDomElement srcElem = doc.createElement( QStringLiteral( "src" ) );
279  QDomElement destElem = doc.createElement( QStringLiteral( "dest" ) );
280 
281  it.key().first.writeXml( srcElem, doc );
282  it.key().second.writeXml( destElem, doc );
283 
284  transformElem.appendChild( srcElem );
285  transformElem.appendChild( destElem );
286 
287  transformElem.setAttribute( QStringLiteral( "coordinateOp" ), it.value().operation );
288  transformElem.setAttribute( QStringLiteral( "allowFallback" ), it.value().allowFallback ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
289  contextElem.appendChild( transformElem );
290  }
291 
292  element.appendChild( contextElem );
293  d->mLock.unlock();
294 }
295 
297 {
298  d.detach();
299  d->mLock.lockForWrite();
300 
301  d->mSourceDestDatumTransforms.clear();
302 
303  QgsSettings settings;
304  settings.beginGroup( QStringLiteral( "/Projections" ) );
305  const QStringList projectionKeys = settings.allKeys();
306 
307  //collect src and dest entries that belong together
308  QMap< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem >, QgsCoordinateTransformContextPrivate::OperationDetails > transforms;
309  QStringList::const_iterator pkeyIt = projectionKeys.constBegin();
310  for ( ; pkeyIt != projectionKeys.constEnd(); ++pkeyIt )
311  {
312  if ( pkeyIt->contains( QLatin1String( "coordinateOp" ) ) )
313  {
314  const QStringList split = pkeyIt->split( '/' );
315  QString srcAuthId, destAuthId;
316  if ( ! split.isEmpty() )
317  {
318  srcAuthId = split.at( 0 );
319  }
320  if ( split.size() > 1 )
321  {
322  destAuthId = split.at( 1 ).split( '_' ).at( 0 );
323  }
324 
325  if ( srcAuthId.isEmpty() || destAuthId.isEmpty() )
326  continue;
327 
328  const QString proj = settings.value( *pkeyIt ).toString();
329  const bool allowFallback = settings.value( QStringLiteral( "%1//%2_allowFallback" ).arg( srcAuthId, destAuthId ) ).toBool();
330  QgsCoordinateTransformContextPrivate::OperationDetails deets;
331  deets.operation = proj;
332  deets.allowFallback = allowFallback;
333  transforms[ qMakePair( QgsCoordinateReferenceSystem( srcAuthId ), QgsCoordinateReferenceSystem( destAuthId ) )] = deets;
334  }
335  }
336 
337  // add transforms to context
338  auto transformIt = transforms.constBegin();
339  for ( ; transformIt != transforms.constEnd(); ++transformIt )
340  {
341  d->mSourceDestDatumTransforms.insert( transformIt.key(), transformIt.value() );
342  }
343 
344  d->mLock.unlock();
345  settings.endGroup();
346 }
347 
349 {
350  QgsSettings settings;
351  settings.beginGroup( QStringLiteral( "/Projections" ) );
352  const QStringList groupKeys = settings.allKeys();
353  QStringList::const_iterator groupKeyIt = groupKeys.constBegin();
354  for ( ; groupKeyIt != groupKeys.constEnd(); ++groupKeyIt )
355  {
356  if ( groupKeyIt->contains( QLatin1String( "srcTransform" ) ) || groupKeyIt->contains( QLatin1String( "destTransform" ) ) || groupKeyIt->contains( QLatin1String( "coordinateOp" ) ) )
357  {
358  settings.remove( *groupKeyIt );
359  }
360  }
361 
362  for ( auto transformIt = d->mSourceDestDatumTransforms.constBegin(); transformIt != d->mSourceDestDatumTransforms.constEnd(); ++transformIt )
363  {
364  const QString srcAuthId = transformIt.key().first.authid();
365  const QString destAuthId = transformIt.key().second.authid();
366 
367  if ( srcAuthId.isEmpty() || destAuthId.isEmpty() )
368  continue; // not so nice, but alternative would be to shove whole CRS wkt into the settings values...
369 
370  const QString proj = transformIt.value().operation;
371  const bool allowFallback = transformIt.value().allowFallback;
372  settings.setValue( srcAuthId + "//" + destAuthId + "_coordinateOp", proj );
373  settings.setValue( srcAuthId + "//" + destAuthId + "_allowFallback", allowFallback );
374  }
375 
376  settings.endGroup();
377 }
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
QString authid() const
Returns the authority identifier for the CRS.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Contains information about the context in which a coordinate transform is executed.
void clear()
Clears all stored transform information from the context.
bool allowFallbackTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if approximate "ballpark" transforms may be used when transforming between a source and ...
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...
void readSettings()
Reads the context's state from application settings.
void writeSettings()
Write the context's state to application settings.
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...
QMap< QPair< QString, QString >, QString > coordinateOperations() const
Returns the stored mapping for source to destination CRS pairs to associated coordinate operation to ...
void writeXml(QDomElement &element, const QgsReadWriteContext &context) const
Writes the context's state to a DOM element.
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...
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.
bool readXml(const QDomElement &element, const QgsReadWriteContext &context, QStringList &missingTransforms)
Reads the context's state from a DOM element.
bool addCoordinateOperation(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString, bool allowFallback=true)
Adds a new coordinateOperationProjString to use when projecting coordinates from the specified source...
void removeCoordinateOperation(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs)
Removes the coordinate operation for the specified sourceCrs and destinationCrs.
QgsCoordinateTransformContext()
Constructor for QgsCoordinateTransformContext.
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...
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
bool operator==(const QgsCoordinateTransformContext &rhs) const
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...
QgsCoordinateTransformContext & operator=(const QgsCoordinateTransformContext &rhs)
Assignment operator.
static bool coordinateOperationIsAvailable(const QString &projDef)
Returns true if a coordinate operation (specified via proj string) is available.
The class is used as a container of context for various read/write operations on other objects.
QString crsToKey(const QgsCoordinateReferenceSystem &crs)
const QgsCoordinateReferenceSystem & crs
Contains datum transform information.