QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
qgscoordinateoperationwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinateoperationwidget.cpp
3  ---------------------------
4  begin : December 2019
5  copyright : (C) 2019 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 
19 #include "qgscoordinatetransform.h"
21 #include "qgslogger.h"
22 #include "qgssettings.h"
23 #include "qgsproject.h"
24 #include "qgsguiutils.h"
25 #include "qgsgui.h"
26 #include "qgshelp.h"
28 
29 #include <QDir>
30 #include <QPushButton>
31 
32 #if PROJ_VERSION_MAJOR>=6
33 #include "qgsprojutils.h"
34 #include <proj.h>
35 #endif
36 
38  : QWidget( parent )
39 {
40  setupUi( this );
41 
42  mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
43  mLabelSrcDescription->setOpenExternalLinks( true );
44  mInstallGridButton->hide();
45 
46 #if PROJ_VERSION_MAJOR>=6
47  connect( mInstallGridButton, &QPushButton::clicked, this, &QgsCoordinateOperationWidget::installGrid );
48  connect( mAllowFallbackCheckBox, &QCheckBox::toggled, this, [ = ]
49  {
50  if ( !mBlockSignals )
51  emit operationChanged();
52  } );
53  mCoordinateOperationTableWidget->setColumnCount( 3 );
54 #else
55  mCoordinateOperationTableWidget->setColumnCount( 2 );
56 #endif
57 
58  QStringList headers;
59 #if PROJ_VERSION_MAJOR>=6
60  headers << tr( "Transformation" ) << tr( "Accuracy (meters)" ) << tr( "Area of Use" );
61 #else
62  headers << tr( "Source Transform" ) << tr( "Destination Transform" ) ;
63 #endif
64  mCoordinateOperationTableWidget->setHorizontalHeaderLabels( headers );
65 
66 #if PROJ_VERSION_MAJOR<6
67  mAreaCanvas->hide();
68 #endif
69 
70 #if PROJ_VERSION_MAJOR>=6
71  // proj 6 doesn't provide deprecated operations
72  mHideDeprecatedCheckBox->setVisible( false );
73  mShowSupersededCheckBox->setVisible( true );
74  mLabelDstDescription->hide();
75 #else
76  mShowSupersededCheckBox->setVisible( false );
77  mAllowFallbackCheckBox->setVisible( false );
78  QgsSettings settings;
79  mHideDeprecatedCheckBox->setChecked( settings.value( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), true ).toBool() );
80 #endif
81 
82  connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged, this, [ = ] { loadAvailableOperations(); } );
83  connect( mShowSupersededCheckBox, &QCheckBox::toggled, this, &QgsCoordinateOperationWidget::showSupersededToggled );
84  connect( mCoordinateOperationTableWidget, &QTableWidget::currentItemChanged, this, &QgsCoordinateOperationWidget::tableCurrentItemChanged );
85  connect( mCoordinateOperationTableWidget, &QTableWidget::itemDoubleClicked, this, &QgsCoordinateOperationWidget::operationDoubleClicked );
86 
87  mLabelSrcDescription->clear();
88  mLabelDstDescription->clear();
89 }
90 
92 {
93 #if PROJ_VERSION_MAJOR<6
94  ( void )canvas;
95 #else
96  if ( canvas )
97  {
98  // show canvas extent in preview widget
99  QPolygonF mainCanvasPoly = canvas->mapSettings().visiblePolygon();
100  QgsGeometry g = QgsGeometry::fromQPolygonF( mainCanvasPoly );
101  // close polygon
102  mainCanvasPoly << mainCanvasPoly.at( 0 );
103  if ( QgsProject::instance()->crs() !=
105  {
106  // reproject extent
110  g = g.densifyByCount( 5 );
111  try
112  {
113  g.transform( ct );
114  }
115  catch ( QgsCsException & )
116  {
117  }
118  }
119  mAreaCanvas->setCanvasRect( g.boundingBox() );
120  }
121 #endif
122 }
123 
125 {
126  mMakeDefaultCheckBox->setVisible( show );
127 }
128 
130 {
131  return mMakeDefaultCheckBox->isChecked();
132 }
133 
135 {
136  return !mCoordinateOperationTableWidget->selectedItems().isEmpty();
137 }
138 
139 QList<QgsCoordinateOperationWidget::OperationDetails> QgsCoordinateOperationWidget::availableOperations() const
140 {
141  QList<QgsCoordinateOperationWidget::OperationDetails> res;
142  res.reserve( mDatumTransforms.size() );
143 #if PROJ_VERSION_MAJOR>=6
144  for ( const QgsDatumTransform::TransformDetails &details : mDatumTransforms )
145  {
146  OperationDetails op;
147  op.proj = details.proj;
148  op.sourceTransformId = -1;
149  op.destinationTransformId = -1;
150  op.isAvailable = details.isAvailable;
151  res << op;
152  }
153 #else
154  for ( const QgsDatumTransform::TransformPair &details : mDatumTransforms )
155  {
156  OperationDetails op;
157  op.sourceTransformId = details.sourceTransformId;
158  op.destinationTransformId = details.destinationTransformId;
159  res << op;
160  }
161 #endif
162  return res;
163 }
164 
165 void QgsCoordinateOperationWidget::loadAvailableOperations()
166 {
167  mCoordinateOperationTableWidget->setRowCount( 0 );
168 
169  int row = 0;
170  int preferredInitialRow = -1;
171 #if PROJ_VERSION_MAJOR>=6
172  for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
173  {
174  std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
175  item->setData( ProjRole, transform.proj );
176  item->setData( AvailableRole, transform.isAvailable );
177  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
178 
179  QString name = transform.name;
180  if ( !transform.authority.isEmpty() && !transform.code.isEmpty() )
181  name += QStringLiteral( " %1 %2:%3" ).arg( QString( QChar( 0x2013 ) ), transform.authority, transform.code );
182  item->setText( name );
183 
184  if ( row == 0 ) // highlight first (preferred) operation
185  {
186  QFont f = item->font();
187  f.setBold( true );
188  item->setFont( f );
189  item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
190  }
191 
192  if ( !transform.isAvailable )
193  {
194  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
195  }
196 
197  if ( preferredInitialRow < 0 && transform.isAvailable )
198  {
199  // try to select a "preferred" entry by default
200  preferredInitialRow = row;
201  }
202 
203  QString missingMessage;
204  if ( !transform.isAvailable )
205  {
206  QStringList gridMessages;
207  QStringList missingGrids;
208  QStringList missingGridPackages;
209  QStringList missingGridUrls;
210 
211  for ( const QgsDatumTransform::GridDetails &grid : transform.grids )
212  {
213  if ( !grid.isAvailable )
214  {
215  missingGrids << grid.shortName;
216  missingGridPackages << grid.packageName;
217  missingGridUrls << grid.url;
218  QString m = tr( "This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.shortName );
219  if ( !grid.url.isEmpty() )
220  {
221  if ( !grid.packageName.isEmpty() )
222  {
223  m += ' ' + tr( "This grid is part of the <i>%1</i> package, available for download from <a href=\"%2\">%2</a>." ).arg( grid.packageName, grid.url );
224  }
225  else
226  {
227  m += ' ' + tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.url );
228  }
229  }
230  gridMessages << m;
231  }
232  }
233 
234  item->setData( MissingGridsRole, missingGrids );
235  item->setData( MissingGridPackageNamesRole, missingGridPackages );
236  item->setData( MissingGridUrlsRole, missingGridUrls );
237 
238  if ( gridMessages.count() > 1 )
239  {
240  for ( int k = 0; k < gridMessages.count(); ++k )
241  gridMessages[k] = QStringLiteral( "<li>%1</li>" ).arg( gridMessages.at( k ) );
242 
243  missingMessage = QStringLiteral( "<ul>%1</ul" ).arg( gridMessages.join( QString() ) );
244  }
245  else if ( !gridMessages.empty() )
246  {
247  missingMessage = gridMessages.constFirst();
248  }
249  }
250 
251  QStringList areasOfUse;
252  QStringList authorityCodes;
253 
254  QStringList opText;
255  QString lastSingleOpScope;
256  QString lastSingleOpRemarks;
257  for ( const QgsDatumTransform::SingleOperationDetails &singleOpDetails : transform.operationDetails )
258  {
259  QString text;
260  if ( !singleOpDetails.scope.isEmpty() )
261  {
262  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), formatScope( singleOpDetails.scope ) );
263  lastSingleOpScope = singleOpDetails.scope;
264  }
265  if ( !singleOpDetails.remarks.isEmpty() )
266  {
267  if ( !text.isEmpty() )
268  text += QLatin1String( "<br>" );
269  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), singleOpDetails.remarks );
270  lastSingleOpRemarks = singleOpDetails.remarks;
271  }
272  if ( !singleOpDetails.areaOfUse.isEmpty() )
273  {
274  if ( !areasOfUse.contains( singleOpDetails.areaOfUse ) )
275  areasOfUse << singleOpDetails.areaOfUse;
276  }
277  if ( !singleOpDetails.authority.isEmpty() && !singleOpDetails.code.isEmpty() )
278  {
279  const QString identifier = QStringLiteral( "%1:%2" ).arg( singleOpDetails.authority, singleOpDetails.code );
280  if ( !authorityCodes.contains( identifier ) )
281  authorityCodes << identifier;
282  }
283 
284  if ( !text.isEmpty() )
285  {
286  opText.append( text );
287  }
288  }
289 
290  QString text;
291  if ( !transform.scope.isEmpty() && transform.scope != lastSingleOpScope )
292  {
293  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), transform.scope );
294  }
295  if ( !transform.remarks.isEmpty() && transform.remarks != lastSingleOpRemarks )
296  {
297  if ( !text.isEmpty() )
298  text += QLatin1String( "<br>" );
299  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), transform.remarks );
300  }
301  if ( !text.isEmpty() )
302  {
303  opText.append( text );
304  }
305 
306  if ( opText.count() > 1 )
307  {
308  for ( int k = 0; k < opText.count(); ++k )
309  opText[k] = QStringLiteral( "<li>%1</li>" ).arg( opText.at( k ) );
310  }
311 
312  if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
313  areasOfUse << transform.areaOfUse;
314  item->setData( BoundsRole, transform.bounds );
315 
316  const QString id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral( "%1:%2" ).arg( transform.authority, transform.code ) : QString();
317  if ( !id.isEmpty() && !authorityCodes.contains( id ) )
318  authorityCodes << id;
319 
320  const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
321  const QColor active = palette().color( QPalette::Active, QPalette::Text );
322 
323  const QColor codeColor( static_cast< int >( active.red() * 0.6 + disabled.red() * 0.4 ),
324  static_cast< int >( active.green() * 0.6 + disabled.green() * 0.4 ),
325  static_cast< int >( active.blue() * 0.6 + disabled.blue() * 0.4 ) );
326  const QString toolTipString = QStringLiteral( "<b>%1</b>" ).arg( transform.name )
327  + ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral( "<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral( "<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
328  + ( !areasOfUse.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Area of use" ), areasOfUse.join( QLatin1String( ", " ) ) ) : QString() )
329  + ( !authorityCodes.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Identifiers" ), authorityCodes.join( QLatin1String( ", " ) ) ) : QString() )
330  + ( !missingMessage.isEmpty() ? QStringLiteral( "<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
331  + QStringLiteral( "<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
332 
333  item->setToolTip( toolTipString );
334  mCoordinateOperationTableWidget->setRowCount( row + 1 );
335  mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
336 
337  item = qgis::make_unique< QTableWidgetItem >();
338  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
339  item->setText( transform.accuracy >= 0 ? QString::number( transform.accuracy ) : tr( "Unknown" ) );
340  item->setToolTip( toolTipString );
341  if ( !transform.isAvailable )
342  {
343  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
344  }
345  mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
346 
347 #if PROJ_VERSION_MAJOR>=6
348  // area of use column
349  item = qgis::make_unique< QTableWidgetItem >();
350  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
351  item->setText( areasOfUse.join( QLatin1String( ", " ) ) );
352  item->setToolTip( toolTipString );
353  if ( !transform.isAvailable )
354  {
355  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
356  }
357  mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
358 #endif
359 
360  row++;
361  }
362 #else
364  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
365  {
366  bool itemDisabled = false;
367  bool itemHidden = false;
368 
369  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
370  continue;
371 
372  QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
373  QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
374  for ( int i = 0; i < 2; ++i )
375  {
376  std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
377  int nr = i == 0 ? transform.sourceTransformId : transform.destinationTransformId;
378  item->setData( TransformIdRole, nr );
379  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
380 
381  item->setText( QgsDatumTransform::datumTransformToProj( nr ) );
382 
383  //Describe datums in a tooltip
384  QgsDatumTransform::TransformInfo info = i == 0 ? srcInfo : destInfo;
385  if ( info.datumTransformId == -1 )
386  continue;
387 
388  if ( info.deprecated )
389  {
390  itemHidden = mHideDeprecatedCheckBox->isChecked();
391  item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
392  }
393 
394  if ( ( srcInfo.preferred && !srcInfo.deprecated ) || ( destInfo.preferred && !destInfo.deprecated ) )
395  {
396  QFont f = item->font();
397  f.setBold( true );
398  item->setFont( f );
399  item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
400  }
401 
402  if ( info.preferred && !info.deprecated && preferredInitialRow < 0 )
403  {
404  // try to select a "preferred" entry by default
405  preferredInitialRow = row;
406  }
407 
408  QString toolTipString;
409  if ( gridShiftTransformation( item->text() ) )
410  {
411  toolTipString.append( QStringLiteral( "<p><b>NTv2</b></p>" ) );
412  }
413 
414  if ( info.epsgCode > 0 )
415  toolTipString.append( QStringLiteral( "<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.epsgCode ) );
416 
417  toolTipString.append( QStringLiteral( "<p><b>Source CRS:</b> %1</p><p><b>Destination CRS:</b> %2</p>" ).arg( info.sourceCrsDescription, info.destinationCrsDescription ) );
418 
419  if ( !info.remarks.isEmpty() )
420  toolTipString.append( QStringLiteral( "<p><b>Remarks:</b> %1</p>" ).arg( info.remarks ) );
421  if ( !info.scope.isEmpty() )
422  toolTipString.append( QStringLiteral( "<p><b>Scope:</b> %1</p>" ).arg( info.scope ) );
423  if ( info.preferred )
424  toolTipString.append( "<p><b>Preferred transformation</b></p>" );
425  if ( info.deprecated )
426  toolTipString.append( "<p><b>Deprecated transformation</b></p>" );
427 
428  item->setToolTip( toolTipString );
429 
430  if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
431  {
432  itemDisabled = true;
433  }
434 
435  if ( !itemHidden )
436  {
437  if ( itemDisabled )
438  {
439  item->setFlags( Qt::NoItemFlags );
440  }
441  mCoordinateOperationTableWidget->setRowCount( row + 1 );
442  mCoordinateOperationTableWidget->setItem( row, i, item.release() );
443  }
444  }
445  row++;
446  }
448 #endif
449 
450  if ( mCoordinateOperationTableWidget->currentRow() < 0 )
451  mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
452 
453  mCoordinateOperationTableWidget->resizeColumnsToContents();
454 
455  tableCurrentItemChanged( nullptr, nullptr );
456 }
457 
459 {
460  QgsSettings settings;
461  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
462 
463  for ( int i = 0; i < 2; i++ )
464  {
465  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
466  }
467 }
468 
470 {
471  OperationDetails preferred;
472 
473 #if PROJ_VERSION_MAJOR>=6
474  // for proj 6, return the first available transform -- they are sorted by preference by proj already
475  for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
476  {
477  if ( transform.isAvailable )
478  {
479  preferred.proj = transform.proj;
480  preferred.isAvailable = transform.isAvailable;
481  break;
482  }
483  }
484  return preferred;
485 #else
486  OperationDetails preferredNonDeprecated;
487  bool foundPreferredNonDeprecated = false;
488  bool foundPreferred = false;
489  OperationDetails nonDeprecated;
490  bool foundNonDeprecated = false;
491  OperationDetails fallback;
492  bool foundFallback = false;
493 
495  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
496  {
497  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
498  continue;
499 
500  const QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
501  const QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
502  if ( !foundPreferredNonDeprecated && ( ( srcInfo.preferred && !srcInfo.deprecated ) || transform.sourceTransformId == -1 )
503  && ( ( destInfo.preferred && !destInfo.deprecated ) || transform.destinationTransformId == -1 ) )
504  {
505  preferredNonDeprecated.sourceTransformId = transform.sourceTransformId;
506  preferredNonDeprecated.destinationTransformId = transform.destinationTransformId;
507  foundPreferredNonDeprecated = true;
508  }
509  else if ( !foundPreferred && ( srcInfo.preferred || transform.sourceTransformId == -1 ) &&
510  ( destInfo.preferred || transform.destinationTransformId == -1 ) )
511  {
512  preferred.sourceTransformId = transform.sourceTransformId;
513  preferred.destinationTransformId = transform.destinationTransformId;
514  foundPreferred = true;
515  }
516  else if ( !foundNonDeprecated && ( !srcInfo.deprecated || transform.sourceTransformId == -1 )
517  && ( !destInfo.deprecated || transform.destinationTransformId == -1 ) )
518  {
519  nonDeprecated.sourceTransformId = transform.sourceTransformId;
520  nonDeprecated.destinationTransformId = transform.destinationTransformId;
521  foundNonDeprecated = true;
522  }
523  else if ( !foundFallback )
524  {
525  fallback.sourceTransformId = transform.sourceTransformId;
526  fallback.destinationTransformId = transform.destinationTransformId;
527  foundFallback = true;
528  }
529  }
531  if ( foundPreferredNonDeprecated )
532  return preferredNonDeprecated;
533  else if ( foundPreferred )
534  return preferred;
535  else if ( foundNonDeprecated )
536  return nonDeprecated;
537  else
538  return fallback;
539 #endif
540 }
541 
542 QString QgsCoordinateOperationWidget::formatScope( const QString &s )
543 {
544  QString scope = s;
545 
546  QRegularExpression reGNSS( QStringLiteral( "\\bGNSS\\b" ) );
547  scope.replace( reGNSS, QObject::tr( "GNSS (Global Navigation Satellite System)" ) );
548 
549  QRegularExpression reCORS( QStringLiteral( "\\bCORS\\b" ) );
550  scope.replace( reCORS, QObject::tr( "CORS (Continually Operating Reference Station)" ) );
551 
552  return scope;
553 }
554 
556 {
557  int row = mCoordinateOperationTableWidget->currentRow();
558  OperationDetails op;
559 
560  if ( row >= 0 )
561  {
562  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
563  op.sourceTransformId = srcItem ? srcItem->data( TransformIdRole ).toInt() : -1;
564  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
565  op.destinationTransformId = destItem ? destItem->data( TransformIdRole ).toInt() : -1;
566  op.proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
567  op.isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() : true;
568  op.allowFallback = mAllowFallbackCheckBox->isChecked();
569  }
570  else
571  {
572  op.sourceTransformId = -1;
573  op.destinationTransformId = -1;
574  op.proj = QString();
575  }
576  return op;
577 }
578 
580 {
581  int prevRow = mCoordinateOperationTableWidget->currentRow();
582  mBlockSignals++;
583  for ( int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
584  {
585  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
586 #if PROJ_VERSION_MAJOR>=6
587  if ( srcItem && srcItem->data( ProjRole ).toString() == operation.proj )
588  {
589  mCoordinateOperationTableWidget->selectRow( row );
590  break;
591  }
592 #else
593  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
594 
595  // eww, gross logic. Ah well, it's of extremely limited lifespan anyway... it'll be ripped out as soon as we can drop proj < 6 support
596  if ( ( srcItem && destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
597  operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() )
598  || ( srcItem && destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
599  operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() )
600  || ( srcItem && !destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
601  operation.destinationTransformId == -1 )
602  || ( !srcItem && destItem && operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() &&
603  operation.sourceTransformId == -1 )
604  || ( srcItem && !destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
605  operation.sourceTransformId == -1 )
606  || ( !srcItem && destItem && operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() &&
607  operation.destinationTransformId == -1 )
608  )
609  {
610  mCoordinateOperationTableWidget->selectRow( row );
611  break;
612  }
613 #endif
614  }
615 
616  bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.allowFallback;
617  mAllowFallbackCheckBox->setChecked( operation.allowFallback );
618  mBlockSignals--;
619 
620  if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
621  emit operationChanged();
622 }
623 
625 {
626 #if PROJ_VERSION_MAJOR>=6
627  const QString op = context.calculateCoordinateOperation( mSourceCrs, mDestinationCrs );
628  if ( !op.isEmpty() )
629  {
630  OperationDetails deets;
631  deets.proj = op;
632  deets.allowFallback = context.allowFallbackTransform( mSourceCrs, mDestinationCrs );
633  setSelectedOperation( deets );
634  }
635  else
636  {
638  }
639 
640 #else
641  if ( context.hasTransform( mSourceCrs, mDestinationCrs ) )
642  {
644  const QgsDatumTransform::TransformPair op = context.calculateDatumTransforms( mSourceCrs, mDestinationCrs );
646  OperationDetails deets;
647  deets.sourceTransformId = op.sourceTransformId;
648  deets.destinationTransformId = op.destinationTransformId;
649  setSelectedOperation( deets );
650  }
651  else
652  {
654  }
655 #endif
656 }
657 
659 {
660  mAllowFallbackCheckBox->setVisible( visible );
661 }
662 
663 bool QgsCoordinateOperationWidget::gridShiftTransformation( const QString &itemText ) const
664 {
665  return !itemText.isEmpty() && !itemText.contains( QLatin1String( "towgs84" ), Qt::CaseInsensitive );
666 }
667 
668 bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item ) const
669 {
670  if ( !item )
671  {
672  return true;
673  }
674 
675  QString itemText = item->text();
676  if ( itemText.isEmpty() )
677  {
678  return true;
679  }
680 
681  char *projLib = getenv( "PROJ_LIB" );
682  if ( !projLib ) //no information about installation directory
683  {
684  return true;
685  }
686 
687  QStringList itemEqualSplit = itemText.split( '=' );
688  QString filename;
689  for ( int i = 1; i < itemEqualSplit.size(); ++i )
690  {
691  if ( i > 1 )
692  {
693  filename.append( '=' );
694  }
695  filename.append( itemEqualSplit.at( i ) );
696  }
697 
698  QDir projDir( projLib );
699  if ( projDir.exists() )
700  {
701  //look if filename in directory
702  QStringList fileList = projDir.entryList();
703  QStringList::const_iterator fileIt = fileList.constBegin();
704  for ( ; fileIt != fileList.constEnd(); ++fileIt )
705  {
706 #if defined(Q_OS_WIN)
707  if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
708 #else
709  if ( fileIt->compare( filename ) == 0 )
710 #endif //Q_OS_WIN
711  {
712  return true;
713  }
714  }
715  item->setToolTip( tr( "File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
716  return false; //not found in PROJ_LIB directory
717  }
718  return true;
719 }
720 
721 void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
722 {
723  int row = mCoordinateOperationTableWidget->currentRow();
724  if ( row < 0 )
725  {
726  mLabelSrcDescription->clear();
727  mLabelDstDescription->clear();
728 #if PROJ_VERSION_MAJOR>=6
729  mAreaCanvas->hide();
730  mInstallGridButton->hide();
731 #endif
732  }
733  else
734  {
735  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
736  mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
737  if ( srcItem )
738  {
739  // find area of intersection of operation, source and dest bounding boxes
740  // see https://github.com/OSGeo/PROJ/issues/1549 for justification
741  const QgsRectangle operationRect = srcItem->data( BoundsRole ).value< QgsRectangle >();
742  const QgsRectangle sourceRect = mSourceCrs.bounds();
743  const QgsRectangle destRect = mDestinationCrs.bounds();
744  QgsRectangle rect = operationRect.intersect( sourceRect );
745  rect = rect.intersect( destRect );
746 
747  mAreaCanvas->setPreviewRect( rect );
748 #if PROJ_VERSION_MAJOR>=6
749  mAreaCanvas->show();
750 
751  const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
752  mInstallGridButton->setVisible( !missingGrids.empty() );
753  if ( !missingGrids.empty() )
754  {
755  mInstallGridButton->setText( tr( "Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
756  }
757 #endif
758  }
759  else
760  {
761  mAreaCanvas->setPreviewRect( QgsRectangle() );
762 #if PROJ_VERSION_MAJOR>=6
763  mAreaCanvas->hide();
764  mInstallGridButton->hide();
765 #endif
766  }
767  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
768  mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
769  }
770  OperationDetails newOp = selectedOperation();
771 #if PROJ_VERSION_MAJOR>=6
772  if ( newOp.proj != mPreviousOp.proj && !mBlockSignals )
773  emit operationChanged();
774 #else
775  if ( newOp.sourceTransformId != mPreviousOp.sourceTransformId ||
776  newOp.destinationTransformId != mPreviousOp.destinationTransformId )
777  emit operationChanged();
778 #endif
779  mPreviousOp = newOp;
780 }
781 
783 {
784  mSourceCrs = sourceCrs;
785 #if PROJ_VERSION_MAJOR>=6
786  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
787 #else
789  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
791 #endif
792  loadAvailableOperations();
793 }
794 
796 {
797  mDestinationCrs = destinationCrs;
798 #if PROJ_VERSION_MAJOR>=6
799  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
800 #else
802  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
804 #endif
805  loadAvailableOperations();
806 }
807 
808 void QgsCoordinateOperationWidget::showSupersededToggled( bool )
809 {
810 #if PROJ_VERSION_MAJOR>=6
811  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
812 #else
814  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
816 #endif
817  loadAvailableOperations();
818 }
819 
820 void QgsCoordinateOperationWidget::installGrid()
821 {
822 #if PROJ_VERSION_MAJOR>=6
823  int row = mCoordinateOperationTableWidget->currentRow();
824  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
825  if ( !srcItem )
826  return;
827 
828  const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
829  if ( missingGrids.empty() )
830  return;
831 
832  const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
833  const QString packageName = missingGridPackagesNames.value( 0 );
834  const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
835  const QString gridUrl = missingGridUrls.value( 0 );
836 
837  QString downloadMessage;
838  if ( !packageName.isEmpty() )
839  {
840  downloadMessage = tr( "This grid is part of the “<i>%1</i>” package, available for download from <a href=\"%2\">%2</a>." ).arg( packageName, gridUrl );
841  }
842  else if ( !gridUrl.isEmpty() )
843  {
844  downloadMessage = tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
845  }
846 
847  const QString longMessage = tr( "<p>This transformation requires the grid file “%1”, which is not available for use on the system.</p>" ).arg( missingGrids.at( 0 ) );
848 
849  QgsInstallGridShiftFileDialog *dlg = new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ), this );
850  dlg->setAttribute( Qt::WA_DeleteOnClose );
851  dlg->setWindowTitle( tr( "Install Grid File" ) );
852  dlg->setDescription( longMessage );
853  dlg->setDownloadMessage( downloadMessage );
854  dlg->exec();
855 
856 #endif
857 }
QgsDatumTransform::SingleOperationDetails::scope
QString scope
Scope of operation, from EPSG registry database.
Definition: qgsdatumtransform.h:160
QgsCoordinateOperationWidget::setSelectedOperation
void setSelectedOperation(const QgsCoordinateOperationWidget::OperationDetails &operation)
Sets the details of the operation currently selected within the widget.
Definition: qgscoordinateoperationwidget.cpp:579
QgsDatumTransform::SingleOperationDetails::code
QString code
Authority code, e.g. "8447" (for EPSG:8447).
Definition: qgsdatumtransform.h:172
QgsGeometry::transform
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false) SIP_THROW(QgsCsException)
Transforms this geometry as described by the coordinate transform ct.
Definition: qgsgeometry.cpp:2813
QgsDatumTransform::TransformInfo::destinationCrsDescription
QString destinationCrsDescription
Destination CRS description.
Definition: qgsdatumtransform.h:112
qgsinstallgridshiftdialog.h
QgsCoordinateTransformContext
Contains information about the context in which a coordinate transform is executed.
Definition: qgscoordinatetransformcontext.h:58
QgsCoordinateOperationWidget::defaultOperation
QgsCoordinateOperationWidget::OperationDetails defaultOperation() const
Returns the details of the default operation suggested by the widget.
Definition: qgscoordinateoperationwidget.cpp:469
QgsDatumTransform::SingleOperationDetails::remarks
QString remarks
Remarks for operation, from EPSG registry database.
Definition: qgsdatumtransform.h:163
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:174
QgsDatumTransform::SingleOperationDetails
Contains information about a single coordinate operation.
Definition: qgsdatumtransform.h:158
QgsCoordinateOperationWidget::sourceCrs
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source CRS for the operations shown in the widget.
Definition: qgscoordinateoperationwidget.h:70
qgscoordinateoperationwidget.h
QgsCoordinateOperationWidget::QgsCoordinateOperationWidget
QgsCoordinateOperationWidget(QWidget *parent=nullptr)
Constructor for QgsCoordinateOperationWidget.
Definition: qgscoordinateoperationwidget.cpp:37
QgsMapCanvas::mapSettings
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
Definition: qgsmapcanvas.cpp:391
qgsgui.h
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:51
QgsDatumTransform::TransformPair
Contains datum transform information.
Definition: qgsdatumtransform.h:55
QgsCoordinateOperationWidget::OperationDetails
Coordinate operation details.
Definition: qgscoordinateoperationwidget.h:40
QgsCoordinateOperationWidget::OperationDetails::allowFallback
bool allowFallback
true if fallback transforms can be used
Definition: qgscoordinateoperationwidget.h:54
QgsMapCanvas
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:85
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:468
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QgsRectangle::intersect
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:312
QgsDatumTransform::TransformInfo::preferred
bool preferred
True if transform is the preferred transform to use for the source/destination CRS combination.
Definition: qgsdatumtransform.h:121
QgsCoordinateOperationWidget::setShowFallbackOption
void setShowFallbackOption(bool visible)
Sets whether the "allow fallback" operations option is visible.
Definition: qgscoordinateoperationwidget.cpp:658
QgsRectangle
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QgsDatumTransform::GridDetails
Contains information about a projection transformation grid file.
Definition: qgsdatumtransform.h:134
QgsCoordinateReferenceSystem::bounds
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
Definition: qgscoordinatereferencesystem.cpp:1456
QgsCoordinateOperationWidget::~QgsCoordinateOperationWidget
~QgsCoordinateOperationWidget() override
Definition: qgscoordinateoperationwidget.cpp:458
qgsprojectionselectiondialog.h
QgsCoordinateOperationWidget::setShowMakeDefault
void setShowMakeDefault(bool show)
Sets whether the "make default" checkbox should be shown.
Definition: qgscoordinateoperationwidget.cpp:124
QgsCoordinateOperationWidget::makeDefaultSelected
bool makeDefaultSelected() const
Returns true if the "make default" option is selected.
Definition: qgscoordinateoperationwidget.cpp:129
Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:797
QgsCoordinateTransformContext::allowFallbackTransform
bool allowFallbackTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if approximate "ballpark" transforms may be used when transforming between a source and ...
Definition: qgscoordinatetransformcontext.cpp:217
QgsCoordinateOperationWidget::OperationDetails::proj
QString proj
Proj coordinate operation description, for Proj >= 6.0 builds only.
Definition: qgscoordinateoperationwidget.h:48
QgsCsException
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
QgsDatumTransform::GridDetails::url
QString url
Url to download grid from.
Definition: qgsdatumtransform.h:142
QgsGeometry::densifyByCount
QgsGeometry densifyByCount(int extraNodesPerSegment) const
Returns a copy of the geometry which has been densified by adding the specified number of extra nodes...
Definition: qgsgeometry.cpp:2117
QgsCoordinateOperationWidget::setMapCanvas
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas to link to the widget, which allows the widget's choices to reflect the current can...
Definition: qgscoordinateoperationwidget.cpp:91
QgsCoordinateTransform::setBallparkTransformsAreAppropriate
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
Definition: qgscoordinatetransform.cpp:935
QgsDatumTransform::datumTransformations
static Q_DECL_DEPRECATED QList< QgsDatumTransform::TransformPair > datumTransformations(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination)
Returns a list of datum transformations which are available for the given source and destination CRS.
Definition: qgsdatumtransform.cpp:73
QgsDatumTransform::TransformInfo::remarks
QString remarks
Transform remarks.
Definition: qgsdatumtransform.h:115
QgsDatumTransform::TransformInfo::deprecated
bool deprecated
True if transform is deprecated.
Definition: qgsdatumtransform.h:124
QgsMapSettings::visiblePolygon
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
Definition: qgsmapsettings.cpp:376
QgsCoordinateOperationWidget::selectedOperation
QgsCoordinateOperationWidget::OperationDetails selectedOperation() const
Returns the details of the operation currently selected within the widget.
Definition: qgscoordinateoperationwidget.cpp:555
QgsDatumTransform::TransformInfo::sourceCrsDescription
QString sourceCrsDescription
Source CRS description.
Definition: qgsdatumtransform.h:109
QgsDatumTransform::TransformInfo::datumTransformId
int datumTransformId
Datum transform ID.
Definition: qgsdatumtransform.h:97
QgsCoordinateOperationWidget::setDestinationCrs
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs for the operations shown in the widget.
Definition: qgscoordinateoperationwidget.cpp:795
qgscoordinatetransform.h
QgsCoordinateOperationWidget::operationDoubleClicked
void operationDoubleClicked()
Emitted when an operation is double-clicked in the widget.
QgsDatumTransform::GridDetails::shortName
QString shortName
Short name of transform grid.
Definition: qgsdatumtransform.h:136
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:289
QgsCoordinateOperationWidget::destinationCrs
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination CRS for the operations shown in the widget.
Definition: qgscoordinateoperationwidget.h:78
QgsCoordinateOperationWidget::availableOperations
QList< QgsCoordinateOperationWidget::OperationDetails > availableOperations() const
Returns a list of the available operations shown in the widget.
Definition: qgscoordinateoperationwidget.cpp:139
QgsDatumTransform::TransformDetails
Contains information about a coordinate transformation operation.
Definition: qgsdatumtransform.h:182
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:206
QgsCoordinateOperationWidget::OperationDetails::destinationTransformId
int destinationTransformId
Destination transform ID.
Definition: qgscoordinateoperationwidget.h:45
QgsCoordinateOperationWidget::hasSelection
bool hasSelection() const
Returns true if there is a valid selection in the widget.
Definition: qgscoordinateoperationwidget.cpp:134
QgsCoordinateOperationWidget::setSourceCrs
void setSourceCrs(const QgsCoordinateReferenceSystem &crs)
Sets the source crs for the operations shown in the widget.
Definition: qgscoordinateoperationwidget.cpp:782
QgsCoordinateOperationWidget::OperationDetails::sourceTransformId
int sourceTransformId
Source transform ID.
Definition: qgscoordinateoperationwidget.h:42
QgsCoordinateOperationWidget::OperationDetails::isAvailable
bool isAvailable
true if operation is available
Definition: qgscoordinateoperationwidget.h:51
QgsGeometry
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsCoordinateTransformContext::calculateCoordinateOperation
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...
Definition: qgscoordinatetransformcontext.cpp:195
qgsprojutils.h
qgssettings.h
QgsCoordinateTransformContext::calculateDatumTransforms
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...
Definition: qgscoordinatetransformcontext.cpp:171
QgsDatumTransform::GridDetails::isAvailable
bool isAvailable
true if grid is currently available for use
Definition: qgsdatumtransform.h:148
QgsDatumTransform::TransformInfo
Contains datum transform information.
Definition: qgsdatumtransform.h:95
QgsGeometry::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Definition: qgsgeometry.cpp:996
QgsCoordinateReferenceSystem::fromEpsgId
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
Definition: qgscoordinatereferencesystem.cpp:207
QgsGeometry::fromQPolygonF
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
Definition: qgsgeometry.cpp:3072
QgsDatumTransform::GridDetails::packageName
QString packageName
Name of package the grid is included within.
Definition: qgsdatumtransform.h:140
QgsDatumTransform::SingleOperationDetails::authority
QString authority
Authority name, e.g. EPSG.
Definition: qgsdatumtransform.h:169
QgsDatumTransform::SingleOperationDetails::areaOfUse
QString areaOfUse
Area of use, from EPSG registry database.
Definition: qgsdatumtransform.h:166
qgslogger.h
qgsguiutils.h
QgsCoordinateTransformContext::hasTransform
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...
Definition: qgscoordinatetransformcontext.cpp:157
QgsCoordinateTransform
Class for doing transforms between two map coordinate systems.
Definition: qgscoordinatetransform.h:53
Q_NOWARN_DEPRECATED_PUSH
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:796
QgsDatumTransform::TransformInfo::scope
QString scope
Scope of transform.
Definition: qgsdatumtransform.h:118
QgsDatumTransform::TransformInfo::epsgCode
int epsgCode
EPSG code for the transform, or 0 if not found in EPSG database.
Definition: qgsdatumtransform.h:100
qgshelp.h
QgsDatumTransform::datumTransformToProj
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID.
Definition: qgsdatumtransform.cpp:162
qgsproject.h
QgsDatumTransform::datumTransformInfo
static Q_DECL_DEPRECATED QgsDatumTransform::TransformInfo datumTransformInfo(int datumTransformId)
Returns detailed information about the specified datumTransformId.
Definition: qgsdatumtransform.cpp:270
QgsCoordinateOperationWidget::setSelectedOperationUsingContext
void setSelectedOperationUsingContext(const QgsCoordinateTransformContext &context)
Automatically sets the selected operation using the settings encapsulated in a transform context.
Definition: qgscoordinateoperationwidget.cpp:624
QgsCoordinateOperationWidget::operationChanged
void operationChanged()
Emitted when the operation selected in the dialog is changed.
QgsDatumTransform::operations
static QList< QgsDatumTransform::TransformDetails > operations(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, bool includeSuperseded=false)
Returns a list of coordinate operations available for transforming coordinates from the source to des...
Definition: qgsdatumtransform.cpp:28