QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsdatumtransformdialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdatumtransformdialog.cpp
3  ---------------------------
4  begin : November 2013
5  copyright : (C) 2013 by Marco Hugentobler
6  email : marco.hugentobler at sourcepole dot ch
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"
27 
28 #include <QDir>
29 #include <QPushButton>
30 
31 #if PROJ_VERSION_MAJOR>=6
32 #include "qgsprojutils.h"
33 #include <proj.h>
34 #endif
35 
36 bool QgsDatumTransformDialog::run( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, QWidget *parent, QgsMapCanvas *mapCanvas, const QString &windowTitle )
37 {
38  if ( sourceCrs == destinationCrs )
39  return true;
40 
42  if ( context.hasTransform( sourceCrs, destinationCrs ) )
43  {
44  return true;
45  }
46 
47  QgsDatumTransformDialog dlg( sourceCrs, destinationCrs, false, true, true, qMakePair( -1, -1 ), parent, nullptr, QString(), mapCanvas );
48  if ( !windowTitle.isEmpty() )
49  dlg.setWindowTitle( windowTitle );
50 
51  if ( dlg.shouldAskUserForSelection() )
52  {
53  if ( dlg.exec() )
54  {
55  const TransformInfo dt = dlg.selectedDatumTransform();
62  return true;
63  }
64  else
65  {
66  return false;
67  }
68  }
69  else
70  {
71  dlg.applyDefaultTransform();
72  return true;
73  }
74 }
75 
77  const QgsCoordinateReferenceSystem &dCrs, const bool allowCrsChanges, const bool showMakeDefault, const bool forceChoice,
78  QPair<int, int> selectedDatumTransforms,
79  QWidget *parent,
80  Qt::WindowFlags f, const QString &selectedProj, QgsMapCanvas *mapCanvas )
81  : QDialog( parent, f )
82  , mPreviousCursorOverride( qgis::make_unique< QgsTemporaryCursorRestoreOverride >() ) // this dialog is often shown while cursor overrides are in place, so temporarily remove them
83 {
84  setupUi( this );
85 
86  QgsCoordinateReferenceSystem sourceCrs = sCrs;
87  QgsCoordinateReferenceSystem destinationCrs = dCrs;
88 
90 
91  mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
92  mLabelSrcDescription->setOpenExternalLinks( true );
93 
94  if ( !showMakeDefault )
95  mMakeDefaultCheckBox->setVisible( false );
96 
97  if ( forceChoice )
98  {
99  mButtonBox->removeButton( mButtonBox->button( QDialogButtonBox::Cancel ) );
100  setWindowFlags( windowFlags() | Qt::CustomizeWindowHint );
101  setWindowFlags( windowFlags() & ~Qt::WindowCloseButtonHint );
102  }
103 
104 #if PROJ_VERSION_MAJOR>=6
105  mDatumTransformTableWidget->setColumnCount( 3 );
106 #else
107  mDatumTransformTableWidget->setColumnCount( 2 );
108 #endif
109 
110  QStringList headers;
111 #if PROJ_VERSION_MAJOR>=6
112  headers << tr( "Transformation" ) << tr( "Accuracy (meters)" ) << tr( "Area of Use" );
113 #else
114  headers << tr( "Source Transform" ) << tr( "Destination Transform" ) ;
115 #endif
116  mDatumTransformTableWidget->setHorizontalHeaderLabels( headers );
117 
118 #if PROJ_VERSION_MAJOR>=6
119  if ( !sourceCrs.isValid() )
120  sourceCrs = QgsProject::instance()->crs();
121  if ( !sourceCrs.isValid() )
122  sourceCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
123  if ( !destinationCrs.isValid() )
124  destinationCrs = QgsProject::instance()->crs();
125  if ( !destinationCrs.isValid() )
126  destinationCrs = QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) );
127 
128  mSourceProjectionSelectionWidget->setOptionVisible( QgsProjectionSelectionWidget::CrsNotSet, false );
129  mDestinationProjectionSelectionWidget->setOptionVisible( QgsProjectionSelectionWidget::CrsNotSet, false );
130 #endif
131 
132  mSourceProjectionSelectionWidget->setCrs( sourceCrs );
133  mDestinationProjectionSelectionWidget->setCrs( destinationCrs );
134  if ( !allowCrsChanges )
135  {
136  mCrsStackedWidget->setCurrentIndex( 1 );
137  mSourceProjectionSelectionWidget->setEnabled( false );
138  mDestinationProjectionSelectionWidget->setEnabled( false );
139  mSourceCrsLabel->setText( QgsProjectionSelectionWidget::crsOptionText( sourceCrs ) );
140  mDestCrsLabel->setText( QgsProjectionSelectionWidget::crsOptionText( destinationCrs ) );
141  }
142 
143 #if PROJ_VERSION_MAJOR<6
144  mAreaCanvas->hide();
145  ( void )mapCanvas;
146 #else
147  if ( mapCanvas )
148  {
149  // show canvas extent in preview widget
150  QPolygonF mainCanvasPoly = mapCanvas->mapSettings().visiblePolygon();
151  QgsGeometry g = QgsGeometry::fromQPolygonF( mainCanvasPoly );
152  // close polygon
153  mainCanvasPoly << mainCanvasPoly.at( 0 );
154  if ( QgsProject::instance()->crs() !=
156  {
157  // reproject extent
160 
161  g = g.densifyByCount( 5 );
162  try
163  {
164  g.transform( ct );
165  }
166  catch ( QgsCsException & )
167  {
168  }
169  }
170  mAreaCanvas->setCanvasRect( g.boundingBox() );
171  }
172 #endif
173 
174 #if PROJ_VERSION_MAJOR>=6
175  // proj 6 doesn't provide deprecated operations
176  mHideDeprecatedCheckBox->setVisible( false );
177 
178 #if PROJ_VERSION_MAJOR>6 || PROJ_VERSION_MINOR>=2
179  mShowSupersededCheckBox->setVisible( true );
180 #else
181  mShowSupersededCheckBox->setVisible( false );
182 #endif
183 
184  mLabelDstDescription->hide();
185 #else
186  mShowSupersededCheckBox->setVisible( false );
187  QgsSettings settings;
188  mHideDeprecatedCheckBox->setChecked( settings.value( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), true ).toBool() );
189 #endif
190 
191  connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged, this, [ = ] { load(); } );
192  connect( mShowSupersededCheckBox, &QCheckBox::toggled, this, &QgsDatumTransformDialog::showSupersededToggled );
193  connect( mDatumTransformTableWidget, &QTableWidget::currentItemChanged, this, &QgsDatumTransformDialog::tableCurrentItemChanged );
194 
195  connect( mSourceProjectionSelectionWidget, &QgsProjectionSelectionWidget::crsChanged, this, &QgsDatumTransformDialog::setSourceCrs );
196  connect( mDestinationProjectionSelectionWidget, &QgsProjectionSelectionWidget::crsChanged, this, &QgsDatumTransformDialog::setDestinationCrs );
197 
198  //get list of datum transforms
199  mSourceCrs = sourceCrs;
200  mDestinationCrs = destinationCrs;
201 #if PROJ_VERSION_MAJOR>=6
202  mDatumTransforms = QgsDatumTransform::operations( sourceCrs, destinationCrs, mShowSupersededCheckBox->isChecked() );
203 #else
205  mDatumTransforms = QgsDatumTransform::datumTransformations( sourceCrs, destinationCrs );
207 #endif
208  mLabelSrcDescription->clear();
209  mLabelDstDescription->clear();
210 
211  connect( mButtonBox, &QDialogButtonBox::helpRequested, this, [ = ]
212  {
213  QgsHelp::openHelp( QStringLiteral( "working_with_projections/working_with_projections.html" ) );
214  } );
215 
216  load( selectedDatumTransforms, selectedProj );
217 }
218 
219 void QgsDatumTransformDialog::load( QPair<int, int> selectedDatumTransforms, const QString &selectedProj )
220 {
221  mDatumTransformTableWidget->setRowCount( 0 );
222 
223  int row = 0;
224  int preferredInitialRow = -1;
225 #if PROJ_VERSION_MAJOR>=6
226  Q_UNUSED( selectedDatumTransforms )
227  for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
228  {
229  std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
230  item->setData( ProjRole, transform.proj );
231  item->setData( AvailableRole, transform.isAvailable );
232  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
233 
234  QString name = transform.name;
235  if ( !transform.authority.isEmpty() && !transform.code.isEmpty() )
236  name += QStringLiteral( " — %1:%2" ).arg( transform.authority, transform.code );
237  item->setText( name );
238 
239  if ( row == 0 ) // highlight first (preferred) operation
240  {
241  QFont f = item->font();
242  f.setBold( true );
243  item->setFont( f );
244  item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
245  }
246 
247  if ( !transform.isAvailable )
248  {
249  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
250  }
251 
252  if ( preferredInitialRow < 0 && transform.isAvailable )
253  {
254  // try to select a "preferred" entry by default
255  preferredInitialRow = row;
256  }
257 
258  QString missingMessage;
259  if ( !transform.isAvailable )
260  {
261  QStringList gridMessages;
262  for ( const QgsDatumTransform::GridDetails &grid : transform.grids )
263  {
264  if ( !grid.isAvailable )
265  {
266  QString m = tr( "This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.shortName );
267  if ( !grid.url.isEmpty() )
268  {
269  if ( !grid.packageName.isEmpty() )
270  {
271  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 );
272  }
273  else
274  {
275  m += ' ' + tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.url );
276  }
277  }
278  gridMessages << m;
279  }
280  }
281 
282  if ( gridMessages.count() > 1 )
283  {
284  for ( int k = 0; k < gridMessages.count(); ++k )
285  gridMessages[k] = QStringLiteral( "<li>%1</li>" ).arg( gridMessages.at( k ) );
286 
287  missingMessage = QStringLiteral( "<ul>%1</ul" ).arg( gridMessages.join( QString() ) );
288  }
289  else if ( !gridMessages.empty() )
290  {
291  missingMessage = gridMessages.constFirst();
292  }
293  }
294 
295  QStringList areasOfUse;
296  QStringList authorityCodes;
297 
298 #if PROJ_VERSION_MAJOR > 6 || PROJ_VERSION_MINOR >= 2
299  QStringList opText;
300  for ( const QgsDatumTransform::SingleOperationDetails &singleOpDetails : transform.operationDetails )
301  {
302  QString text;
303  if ( !singleOpDetails.scope.isEmpty() )
304  {
305  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), formatScope( singleOpDetails.scope ) );
306  }
307  if ( !singleOpDetails.remarks.isEmpty() )
308  {
309  if ( !text.isEmpty() )
310  text += QStringLiteral( "<br>" );
311  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), singleOpDetails.remarks );
312  }
313  if ( !singleOpDetails.areaOfUse.isEmpty() )
314  {
315  if ( !areasOfUse.contains( singleOpDetails.areaOfUse ) )
316  areasOfUse << singleOpDetails.areaOfUse;
317  }
318  if ( !singleOpDetails.authority.isEmpty() && !singleOpDetails.code.isEmpty() )
319  {
320  const QString identifier = QStringLiteral( "%1:%2" ).arg( singleOpDetails.authority, singleOpDetails.code );
321  if ( !authorityCodes.contains( identifier ) )
322  authorityCodes << identifier;
323  }
324 
325  if ( !text.isEmpty() )
326  {
327  opText.append( text );
328  }
329  }
330 
331  QString text;
332  if ( !transform.scope.isEmpty() )
333  {
334  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), transform.scope );
335  }
336  if ( !transform.remarks.isEmpty() )
337  {
338  if ( !text.isEmpty() )
339  text += QStringLiteral( "<br>" );
340  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), transform.remarks );
341  }
342  if ( !text.isEmpty() )
343  {
344  opText.append( text );
345  }
346 
347  if ( opText.count() > 1 )
348  {
349  for ( int k = 0; k < opText.count(); ++k )
350  opText[k] = QStringLiteral( "<li>%1</li>" ).arg( opText.at( k ) );
351  }
352 #endif
353 
354  if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
355  areasOfUse << transform.areaOfUse;
356  item->setData( BoundsRole, transform.bounds );
357 
358  const QString id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral( "%1:%2" ).arg( transform.authority, transform.code ) : QString();
359  if ( !id.isEmpty() && !authorityCodes.contains( id ) )
360  authorityCodes << id;
361 
362 #if PROJ_VERSION_MAJOR > 6 || PROJ_VERSION_MINOR >= 2
363  const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
364  const QColor active = palette().color( QPalette::Active, QPalette::Text );
365 
366  const QColor codeColor( static_cast< int >( active.red() * 0.6 + disabled.red() * 0.4 ),
367  static_cast< int >( active.green() * 0.6 + disabled.green() * 0.4 ),
368  static_cast< int >( active.blue() * 0.6 + disabled.blue() * 0.4 ) );
369  const QString toolTipString = QStringLiteral( "<b>%1</b>" ).arg( transform.name )
370  + ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral( "<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral( "<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
371  + ( !areasOfUse.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Area of use" ), areasOfUse.join( QStringLiteral( ", " ) ) ) : QString() )
372  + ( !authorityCodes.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Identifiers" ), authorityCodes.join( QStringLiteral( ", " ) ) ) : QString() )
373  + ( !missingMessage.isEmpty() ? QStringLiteral( "<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
374  + QStringLiteral( "<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
375 #else
376  const QString toolTipString = QStringLiteral( "<b>%1</b>%2%3%4<p><code>%5</code></p>" ).arg( transform.name,
377  ( !transform.areaOfUse.isEmpty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Area of use" ), transform.areaOfUse ) : QString() ),
378  ( !id.isEmpty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Identifier" ), id ) : QString() ),
379  ( !missingMessage.isEmpty() ? QStringLiteral( "<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() ),
380  transform.proj );
381 #endif
382  item->setToolTip( toolTipString );
383  mDatumTransformTableWidget->setRowCount( row + 1 );
384  mDatumTransformTableWidget->setItem( row, 0, item.release() );
385 
386  item = qgis::make_unique< QTableWidgetItem >();
387  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
388  item->setText( transform.accuracy >= 0 ? QString::number( transform.accuracy ) : tr( "Unknown" ) );
389  item->setToolTip( toolTipString );
390  if ( !transform.isAvailable )
391  {
392  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
393  }
394  mDatumTransformTableWidget->setItem( row, 1, item.release() );
395 
396 #if PROJ_VERSION_MAJOR>=6
397  // area of use column
398  item = qgis::make_unique< QTableWidgetItem >();
399  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
400  item->setText( areasOfUse.join( QStringLiteral( ", " ) ) );
401  item->setToolTip( toolTipString );
402  if ( !transform.isAvailable )
403  {
404  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
405  }
406  mDatumTransformTableWidget->setItem( row, 2, item.release() );
407 #endif
408 
409  if ( transform.proj == selectedProj )
410  {
411  mDatumTransformTableWidget->selectRow( row );
412  }
413 
414  row++;
415  }
416 #else
417  Q_UNUSED( selectedProj )
418 
420  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
421  {
422  bool itemDisabled = false;
423  bool itemHidden = false;
424 
425  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
426  continue;
427 
428  QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
429  QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
430  for ( int i = 0; i < 2; ++i )
431  {
432  std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
433  int nr = i == 0 ? transform.sourceTransformId : transform.destinationTransformId;
434  item->setData( TransformIdRole, nr );
435  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
436 
437  item->setText( QgsDatumTransform::datumTransformToProj( nr ) );
438 
439  //Describe datums in a tooltip
440  QgsDatumTransform::TransformInfo info = i == 0 ? srcInfo : destInfo;
441  if ( info.datumTransformId == -1 )
442  continue;
443 
444  if ( info.deprecated )
445  {
446  itemHidden = mHideDeprecatedCheckBox->isChecked();
447  item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
448  }
449 
450  if ( ( srcInfo.preferred && !srcInfo.deprecated ) || ( destInfo.preferred && !destInfo.deprecated ) )
451  {
452  QFont f = item->font();
453  f.setBold( true );
454  item->setFont( f );
455  item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
456  }
457 
458  if ( info.preferred && !info.deprecated && preferredInitialRow < 0 )
459  {
460  // try to select a "preferred" entry by default
461  preferredInitialRow = row;
462  }
463 
464  QString toolTipString;
465  if ( gridShiftTransformation( item->text() ) )
466  {
467  toolTipString.append( QStringLiteral( "<p><b>NTv2</b></p>" ) );
468  }
469 
470  if ( info.epsgCode > 0 )
471  toolTipString.append( QStringLiteral( "<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.epsgCode ) );
472 
473  toolTipString.append( QStringLiteral( "<p><b>Source CRS:</b> %1</p><p><b>Destination CRS:</b> %2</p>" ).arg( info.sourceCrsDescription, info.destinationCrsDescription ) );
474 
475  if ( !info.remarks.isEmpty() )
476  toolTipString.append( QStringLiteral( "<p><b>Remarks:</b> %1</p>" ).arg( info.remarks ) );
477  if ( !info.scope.isEmpty() )
478  toolTipString.append( QStringLiteral( "<p><b>Scope:</b> %1</p>" ).arg( info.scope ) );
479  if ( info.preferred )
480  toolTipString.append( "<p><b>Preferred transformation</b></p>" );
481  if ( info.deprecated )
482  toolTipString.append( "<p><b>Deprecated transformation</b></p>" );
483 
484  item->setToolTip( toolTipString );
485 
486  if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
487  {
488  itemDisabled = true;
489  }
490 
491  if ( !itemHidden )
492  {
493  if ( itemDisabled )
494  {
495  item->setFlags( Qt::NoItemFlags );
496  }
497  mDatumTransformTableWidget->setRowCount( row + 1 );
498  mDatumTransformTableWidget->setItem( row, i, item.release() );
499  }
500  }
501 
502  if ( ( transform.sourceTransformId == selectedDatumTransforms.first &&
503  transform.destinationTransformId == selectedDatumTransforms.second ) ||
504  ( transform.sourceTransformId == selectedDatumTransforms.second &&
505  transform.destinationTransformId == selectedDatumTransforms.first ) )
506  {
507  mDatumTransformTableWidget->selectRow( row );
508  }
509 
510  row++;
511  }
513 #endif
514 
515  if ( mDatumTransformTableWidget->currentRow() < 0 )
516  mDatumTransformTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
517 
518  mDatumTransformTableWidget->resizeColumnsToContents();
519 
520  tableCurrentItemChanged( nullptr, nullptr );
521 }
522 
523 void QgsDatumTransformDialog::setOKButtonEnabled()
524 {
525  int row = mDatumTransformTableWidget->currentRow();
526 #if PROJ_VERSION_MAJOR>=6
527  mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( mSourceCrs.isValid() && mDestinationCrs.isValid()
528  && mDatumTransformTableWidget->item( row, 0 ) && mDatumTransformTableWidget->item( row, 0 )->data( AvailableRole ).toBool() );
529 #else
530  mButtonBox->button( QDialogButtonBox::Ok )->setEnabled( mSourceCrs.isValid() && mDestinationCrs.isValid() && row >= 0 );
531 #endif
532 }
533 
535 {
536  QgsSettings settings;
537  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
538 
539  for ( int i = 0; i < 2; i++ )
540  {
541  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mDatumTransformTableWidget->columnWidth( i ) );
542  }
543 }
544 
546 {
547  if ( mMakeDefaultCheckBox->isChecked() && !mDatumTransformTableWidget->selectedItems().isEmpty() )
548  {
549  QgsSettings settings;
550  settings.beginGroup( QStringLiteral( "/Projections" ) );
551 
553 
554  QString srcAuthId = dt.sourceCrs.authid();
555  QString destAuthId = dt.destinationCrs.authid();
556  int sourceDatumTransform = dt.sourceTransformId;
557  QString sourceDatumProj;
559  if ( sourceDatumTransform >= 0 )
560  sourceDatumProj = QgsDatumTransform::datumTransformToProj( sourceDatumTransform );
561  int destinationDatumTransform = dt.destinationTransformId;
562  QString destinationDatumProj;
563  if ( destinationDatumTransform >= 0 )
564  destinationDatumProj = QgsDatumTransform::datumTransformToProj( destinationDatumTransform );
566  settings.setValue( srcAuthId + QStringLiteral( "//" ) + destAuthId + QStringLiteral( "_srcTransform" ), sourceDatumProj );
567  settings.setValue( srcAuthId + QStringLiteral( "//" ) + destAuthId + QStringLiteral( "_destTransform" ), destinationDatumProj );
568  settings.setValue( srcAuthId + QStringLiteral( "//" ) + destAuthId + QStringLiteral( "_coordinateOp" ), dt.proj );
569  }
570  QDialog::accept();
571 }
572 
574 {
575  if ( !mButtonBox->button( QDialogButtonBox::Cancel ) )
576  return; // users HAVE to make a choice, no click on the dialog "x" to avoid this!
577 
578  QDialog::reject();
579 }
580 
581 bool QgsDatumTransformDialog::shouldAskUserForSelection() const
582 {
583  if ( mDatumTransforms.count() > 1 )
584  {
585  return QgsSettings().value( QStringLiteral( "/projections/promptWhenMultipleTransformsExist" ), false, QgsSettings::App ).toBool();
586  }
587  // TODO: show if transform grids are required, but missing
588  return false;
589 }
590 
591 QgsDatumTransformDialog::TransformInfo QgsDatumTransformDialog::defaultDatumTransform() const
592 {
593  TransformInfo preferred;
594  preferred.sourceCrs = mSourceCrs;
595  preferred.destinationCrs = mDestinationCrs;
596 
597 #if PROJ_VERSION_MAJOR>=6
598  // for proj 6, return the first available transform -- they are sorted by preference by proj already
599  for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
600  {
601  if ( transform.isAvailable )
602  {
603  preferred.proj = transform.proj;
604  break;
605  }
606  }
607  return preferred;
608 #else
609  TransformInfo preferredNonDeprecated;
610  preferredNonDeprecated.sourceCrs = mSourceCrs;
611  preferredNonDeprecated.destinationCrs = mDestinationCrs;
612  bool foundPreferredNonDeprecated = false;
613  bool foundPreferred = false;
614  TransformInfo nonDeprecated;
615  nonDeprecated.sourceCrs = mSourceCrs;
616  nonDeprecated.destinationCrs = mDestinationCrs;
617  bool foundNonDeprecated = false;
618  TransformInfo fallback;
619  fallback.sourceCrs = mSourceCrs;
620  fallback.destinationCrs = mDestinationCrs;
621  bool foundFallback = false;
622 
624  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
625  {
626  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
627  continue;
628 
629  const QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
630  const QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
631  if ( !foundPreferredNonDeprecated && ( ( srcInfo.preferred && !srcInfo.deprecated ) || transform.sourceTransformId == -1 )
632  && ( ( destInfo.preferred && !destInfo.deprecated ) || transform.destinationTransformId == -1 ) )
633  {
634  preferredNonDeprecated.sourceTransformId = transform.sourceTransformId;
635  preferredNonDeprecated.destinationTransformId = transform.destinationTransformId;
636  foundPreferredNonDeprecated = true;
637  }
638  else if ( !foundPreferred && ( srcInfo.preferred || transform.sourceTransformId == -1 ) &&
639  ( destInfo.preferred || transform.destinationTransformId == -1 ) )
640  {
641  preferred.sourceTransformId = transform.sourceTransformId;
642  preferred.destinationTransformId = transform.destinationTransformId;
643  foundPreferred = true;
644  }
645  else if ( !foundNonDeprecated && ( !srcInfo.deprecated || transform.sourceTransformId == -1 )
646  && ( !destInfo.deprecated || transform.destinationTransformId == -1 ) )
647  {
648  nonDeprecated.sourceTransformId = transform.sourceTransformId;
649  nonDeprecated.destinationTransformId = transform.destinationTransformId;
650  foundNonDeprecated = true;
651  }
652  else if ( !foundFallback )
653  {
654  fallback.sourceTransformId = transform.sourceTransformId;
655  fallback.destinationTransformId = transform.destinationTransformId;
656  foundFallback = true;
657  }
658  }
660  if ( foundPreferredNonDeprecated )
661  return preferredNonDeprecated;
662  else if ( foundPreferred )
663  return preferred;
664  else if ( foundNonDeprecated )
665  return nonDeprecated;
666  else
667  return fallback;
668 #endif
669 }
670 
671 void QgsDatumTransformDialog::applyDefaultTransform()
672 {
673  if ( mDatumTransforms.count() > 0 )
674  {
676  const TransformInfo dt = defaultDatumTransform();
680 
681 #if PROJ_VERSION_MAJOR>=6
682  // on proj 6 builds, removing a coordinate operation falls back to default
684 #else
685  context.addCoordinateOperation( dt.sourceCrs, dt.destinationCrs, dt.proj );
686 #endif
688  }
689 }
690 
691 QString QgsDatumTransformDialog::formatScope( const QString &s )
692 {
693  QString scope = s;
694 
695  QRegularExpression reGNSS( QStringLiteral( "\\bGNSS\\b" ) );
696  scope.replace( reGNSS, QObject::tr( "GNSS (Global Navigation Satellite System)" ) );
697 
698  QRegularExpression reCORS( QStringLiteral( "\\bCORS\\b" ) );
699  scope.replace( reCORS, QObject::tr( "CORS (Continually Operating Reference Station)" ) );
700 
701  return scope;
702 }
703 
705 {
706  int row = mDatumTransformTableWidget->currentRow();
707  TransformInfo sdt;
708  sdt.sourceCrs = mSourceCrs;
709  sdt.destinationCrs = mDestinationCrs;
710 
711  if ( row >= 0 )
712  {
713  QTableWidgetItem *srcItem = mDatumTransformTableWidget->item( row, 0 );
714  sdt.sourceTransformId = srcItem ? srcItem->data( TransformIdRole ).toInt() : -1;
715  QTableWidgetItem *destItem = mDatumTransformTableWidget->item( row, 1 );
716  sdt.destinationTransformId = destItem ? destItem->data( TransformIdRole ).toInt() : -1;
717  sdt.proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
718  }
719  else
720  {
721  sdt.sourceTransformId = -1;
722  sdt.destinationTransformId = -1;
723  sdt.proj = QString();
724  }
725  return sdt;
726 }
727 
728 bool QgsDatumTransformDialog::gridShiftTransformation( const QString &itemText ) const
729 {
730  return !itemText.isEmpty() && !itemText.contains( QLatin1String( "towgs84" ), Qt::CaseInsensitive );
731 }
732 
733 bool QgsDatumTransformDialog::testGridShiftFileAvailability( QTableWidgetItem *item ) const
734 {
735  if ( !item )
736  {
737  return true;
738  }
739 
740  QString itemText = item->text();
741  if ( itemText.isEmpty() )
742  {
743  return true;
744  }
745 
746  char *projLib = getenv( "PROJ_LIB" );
747  if ( !projLib ) //no information about installation directory
748  {
749  return true;
750  }
751 
752  QStringList itemEqualSplit = itemText.split( '=' );
753  QString filename;
754  for ( int i = 1; i < itemEqualSplit.size(); ++i )
755  {
756  if ( i > 1 )
757  {
758  filename.append( '=' );
759  }
760  filename.append( itemEqualSplit.at( i ) );
761  }
762 
763  QDir projDir( projLib );
764  if ( projDir.exists() )
765  {
766  //look if filename in directory
767  QStringList fileList = projDir.entryList();
768  QStringList::const_iterator fileIt = fileList.constBegin();
769  for ( ; fileIt != fileList.constEnd(); ++fileIt )
770  {
771 #if defined(Q_OS_WIN)
772  if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
773 #else
774  if ( fileIt->compare( filename ) == 0 )
775 #endif //Q_OS_WIN
776  {
777  return true;
778  }
779  }
780  item->setToolTip( tr( "File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
781  return false; //not found in PROJ_LIB directory
782  }
783  return true;
784 }
785 
786 void QgsDatumTransformDialog::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
787 {
788  int row = mDatumTransformTableWidget->currentRow();
789  if ( row < 0 )
790  {
791  mLabelSrcDescription->clear();
792  mLabelDstDescription->clear();
793 #if PROJ_VERSION_MAJOR>=6
794  mAreaCanvas->hide();
795 #endif
796  }
797  else
798  {
799  QTableWidgetItem *srcItem = mDatumTransformTableWidget->item( row, 0 );
800  mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
801  if ( srcItem )
802  {
803  // find area of intersection of operation, source and dest bounding boxes
804  // see https://github.com/OSGeo/PROJ/issues/1549 for justification
805  const QgsRectangle operationRect = srcItem->data( BoundsRole ).value< QgsRectangle >();
806  const QgsRectangle sourceRect = mSourceCrs.bounds();
807  const QgsRectangle destRect = mDestinationCrs.bounds();
808  QgsRectangle rect = operationRect.intersect( sourceRect );
809  rect = rect.intersect( destRect );
810 
811  mAreaCanvas->setPreviewRect( rect );
812 #if PROJ_VERSION_MAJOR>=6
813  mAreaCanvas->show();
814 #endif
815  }
816  else
817  {
818  mAreaCanvas->setPreviewRect( QgsRectangle() );
819 #if PROJ_VERSION_MAJOR>=6
820  mAreaCanvas->hide();
821 #endif
822  }
823  QTableWidgetItem *destItem = mDatumTransformTableWidget->item( row, 1 );
824  mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
825  }
826 
827  setOKButtonEnabled();
828 }
829 
830 void QgsDatumTransformDialog::setSourceCrs( const QgsCoordinateReferenceSystem &sourceCrs )
831 {
832  mSourceCrs = sourceCrs;
833 #if PROJ_VERSION_MAJOR>=6
834  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
835 #else
837  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
839 #endif
840  load();
841  setOKButtonEnabled();
842 }
843 
844 void QgsDatumTransformDialog::setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs )
845 {
846  mDestinationCrs = destinationCrs;
847 #if PROJ_VERSION_MAJOR>=6
848  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
849 #else
851  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
853 #endif
854  load();
855  setOKButtonEnabled();
856 }
857 
858 void QgsDatumTransformDialog::showSupersededToggled( bool )
859 {
860 #if PROJ_VERSION_MAJOR>=6
861  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
862 #else
864  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
866 #endif
867  load();
868  setOKButtonEnabled();
869 }
A rectangle specified with double values.
Definition: qgsrectangle.h:41
Dialog transformation entry info.
void removeCoordinateOperation(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs)
Removes the coordinate operation for the specified sourceCrs and destinationCrs.
QString destinationCrsDescription
Destination CRS description.
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.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
QgsCoordinateReferenceSystem destinationCrs
Destination coordinate reference system.
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...
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:649
TransformInfo selectedDatumTransform()
Returns the source and destination transforms, each being a pair of QgsCoordinateReferenceSystems and...
QString proj
Proj coordinate operation description, for Proj >= 6.0 builds only.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
Contains information about a projection transformation grid file.
const QgsCoordinateReferenceSystem & crs
int datumTransformId
Datum transform ID.
Map canvas is a class for displaying all GIS data types on a canvas.
Definition: qgsmapcanvas.h:75
QString packageName
Name of package the grid is included within.
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 deprecated
True if transform is deprecated.
int destinationTransformId
Destination transform ID.
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
QgsCoordinateReferenceSystem sourceCrs
Source coordinate reference system.
static QString crsOptionText(const QgsCoordinateReferenceSystem &crs)
Returns display text for the specified crs.
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...
QString shortName
Short name of transform grid.
QString sourceCrsDescription
Source CRS description.
bool isAvailable
true if grid is currently available for use
QgsGeometry densifyByCount(int extraNodesPerSegment) const
Returns a copy of the geometry which has been densified by adding the specified number of extra nodes...
QgsCoordinateReferenceSystem crs
Definition: qgsproject.h:95
QString remarks
Remarks for operation, from EPSG registry database.
QString areaOfUse
Area of use, from EPSG registry database.
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
Definition: qgsrectangle.h:312
QString scope
Scope of operation, from EPSG registry database.
QString url
Url to download grid from.
QString code
Authority code, e.g. "8447" (for EPSG:8447).
QString authority
Authority name, e.g. EPSG.
static bool run(const QgsCoordinateReferenceSystem &sourceCrs=QgsCoordinateReferenceSystem(), const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem(), QWidget *parent=nullptr, QgsMapCanvas *mapCanvas=nullptr, const QString &windowTitle=QString())
Runs the dialog (if required) prompting for the desired transform to use from sourceCrs to destinatio...
Contains datum transform information.
Contains information about the context in which a coordinate transform is executed.
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...
bool preferred
True if transform is the preferred transform to use for the source/destination CRS combination...
int epsgCode
EPSG code for the transform, or 0 if not found in EPSG database.
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:96
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
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:650
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.
void crsChanged(const QgsCoordinateReferenceSystem &)
Emitted when the selected CRS is changed.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:442
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
void setTransformContext(const QgsCoordinateTransformContext &context)
Sets the project&#39;s coordinate transform context, which stores various information regarding which dat...
Definition: qgsproject.cpp:698
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
static void enableAutoGeometryRestore(QWidget *widget, const QString &key=QString())
Register the widget to allow its position to be automatically saved and restored when open and closed...
Definition: qgsgui.cpp:127
static void openHelp(const QString &key)
Opens help topic for the given help key using default system web browser.
Definition: qgshelp.cpp:36
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID...
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
QPolygonF visiblePolygon() const
Returns the visible area as a polygon (may be rotated)
QString remarks
Transform remarks.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
Contains information about a coordinate transformation operation.
static Q_DECL_DEPRECATED QgsDatumTransform::TransformInfo datumTransformInfo(int datumTransformId)
Returns detailed information about the specified datumTransformId.
Temporarily removes all cursor overrides for the QApplication for the lifetime of the object...
Definition: qgsguiutils.h:235
QgsDatumTransformDialog(const QgsCoordinateReferenceSystem &sourceCrs=QgsCoordinateReferenceSystem(), const QgsCoordinateReferenceSystem &destinationCrs=QgsCoordinateReferenceSystem(), bool allowCrsChanges=false, bool showMakeDefault=true, bool forceChoice=true, QPair< int, int > selectedDatumTransforms=qMakePair(-1, -1), QWidget *parent=nullptr, Qt::WindowFlags f=nullptr, const QString &selectedProj=QString(), QgsMapCanvas *mapCanvas=nullptr)
Constructor for QgsDatumTransformDialog.
Contains information about a single coordinate operation.
QString scope
Scope of transform.
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.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.