QGIS API Documentation  3.13.0-Master (788156190c)
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  for ( const QgsDatumTransform::SingleOperationDetails &singleOpDetails : transform.operationDetails )
256  {
257  QString text;
258  if ( !singleOpDetails.scope.isEmpty() )
259  {
260  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), formatScope( singleOpDetails.scope ) );
261  }
262  if ( !singleOpDetails.remarks.isEmpty() )
263  {
264  if ( !text.isEmpty() )
265  text += QStringLiteral( "<br>" );
266  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), singleOpDetails.remarks );
267  }
268  if ( !singleOpDetails.areaOfUse.isEmpty() )
269  {
270  if ( !areasOfUse.contains( singleOpDetails.areaOfUse ) )
271  areasOfUse << singleOpDetails.areaOfUse;
272  }
273  if ( !singleOpDetails.authority.isEmpty() && !singleOpDetails.code.isEmpty() )
274  {
275  const QString identifier = QStringLiteral( "%1:%2" ).arg( singleOpDetails.authority, singleOpDetails.code );
276  if ( !authorityCodes.contains( identifier ) )
277  authorityCodes << identifier;
278  }
279 
280  if ( !text.isEmpty() )
281  {
282  opText.append( text );
283  }
284  }
285 
286  QString text;
287  if ( !transform.scope.isEmpty() )
288  {
289  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), transform.scope );
290  }
291  if ( !transform.remarks.isEmpty() )
292  {
293  if ( !text.isEmpty() )
294  text += QStringLiteral( "<br>" );
295  text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), transform.remarks );
296  }
297  if ( !text.isEmpty() )
298  {
299  opText.append( text );
300  }
301 
302  if ( opText.count() > 1 )
303  {
304  for ( int k = 0; k < opText.count(); ++k )
305  opText[k] = QStringLiteral( "<li>%1</li>" ).arg( opText.at( k ) );
306  }
307 
308  if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
309  areasOfUse << transform.areaOfUse;
310  item->setData( BoundsRole, transform.bounds );
311 
312  const QString id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral( "%1:%2" ).arg( transform.authority, transform.code ) : QString();
313  if ( !id.isEmpty() && !authorityCodes.contains( id ) )
314  authorityCodes << id;
315 
316  const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
317  const QColor active = palette().color( QPalette::Active, QPalette::Text );
318 
319  const QColor codeColor( static_cast< int >( active.red() * 0.6 + disabled.red() * 0.4 ),
320  static_cast< int >( active.green() * 0.6 + disabled.green() * 0.4 ),
321  static_cast< int >( active.blue() * 0.6 + disabled.blue() * 0.4 ) );
322  const QString toolTipString = QStringLiteral( "<b>%1</b>" ).arg( transform.name )
323  + ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral( "<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral( "<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
324  + ( !areasOfUse.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Area of use" ), areasOfUse.join( QStringLiteral( ", " ) ) ) : QString() )
325  + ( !authorityCodes.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Identifiers" ), authorityCodes.join( QStringLiteral( ", " ) ) ) : QString() )
326  + ( !missingMessage.isEmpty() ? QStringLiteral( "<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
327  + QStringLiteral( "<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
328 
329  item->setToolTip( toolTipString );
330  mCoordinateOperationTableWidget->setRowCount( row + 1 );
331  mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
332 
333  item = qgis::make_unique< QTableWidgetItem >();
334  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
335  item->setText( transform.accuracy >= 0 ? QString::number( transform.accuracy ) : tr( "Unknown" ) );
336  item->setToolTip( toolTipString );
337  if ( !transform.isAvailable )
338  {
339  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
340  }
341  mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
342 
343 #if PROJ_VERSION_MAJOR>=6
344  // area of use column
345  item = qgis::make_unique< QTableWidgetItem >();
346  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
347  item->setText( areasOfUse.join( QStringLiteral( ", " ) ) );
348  item->setToolTip( toolTipString );
349  if ( !transform.isAvailable )
350  {
351  item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
352  }
353  mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
354 #endif
355 
356  row++;
357  }
358 #else
360  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
361  {
362  bool itemDisabled = false;
363  bool itemHidden = false;
364 
365  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
366  continue;
367 
368  QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
369  QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
370  for ( int i = 0; i < 2; ++i )
371  {
372  std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
373  int nr = i == 0 ? transform.sourceTransformId : transform.destinationTransformId;
374  item->setData( TransformIdRole, nr );
375  item->setFlags( item->flags() & ~Qt::ItemIsEditable );
376 
377  item->setText( QgsDatumTransform::datumTransformToProj( nr ) );
378 
379  //Describe datums in a tooltip
380  QgsDatumTransform::TransformInfo info = i == 0 ? srcInfo : destInfo;
381  if ( info.datumTransformId == -1 )
382  continue;
383 
384  if ( info.deprecated )
385  {
386  itemHidden = mHideDeprecatedCheckBox->isChecked();
387  item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
388  }
389 
390  if ( ( srcInfo.preferred && !srcInfo.deprecated ) || ( destInfo.preferred && !destInfo.deprecated ) )
391  {
392  QFont f = item->font();
393  f.setBold( true );
394  item->setFont( f );
395  item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
396  }
397 
398  if ( info.preferred && !info.deprecated && preferredInitialRow < 0 )
399  {
400  // try to select a "preferred" entry by default
401  preferredInitialRow = row;
402  }
403 
404  QString toolTipString;
405  if ( gridShiftTransformation( item->text() ) )
406  {
407  toolTipString.append( QStringLiteral( "<p><b>NTv2</b></p>" ) );
408  }
409 
410  if ( info.epsgCode > 0 )
411  toolTipString.append( QStringLiteral( "<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.epsgCode ) );
412 
413  toolTipString.append( QStringLiteral( "<p><b>Source CRS:</b> %1</p><p><b>Destination CRS:</b> %2</p>" ).arg( info.sourceCrsDescription, info.destinationCrsDescription ) );
414 
415  if ( !info.remarks.isEmpty() )
416  toolTipString.append( QStringLiteral( "<p><b>Remarks:</b> %1</p>" ).arg( info.remarks ) );
417  if ( !info.scope.isEmpty() )
418  toolTipString.append( QStringLiteral( "<p><b>Scope:</b> %1</p>" ).arg( info.scope ) );
419  if ( info.preferred )
420  toolTipString.append( "<p><b>Preferred transformation</b></p>" );
421  if ( info.deprecated )
422  toolTipString.append( "<p><b>Deprecated transformation</b></p>" );
423 
424  item->setToolTip( toolTipString );
425 
426  if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
427  {
428  itemDisabled = true;
429  }
430 
431  if ( !itemHidden )
432  {
433  if ( itemDisabled )
434  {
435  item->setFlags( Qt::NoItemFlags );
436  }
437  mCoordinateOperationTableWidget->setRowCount( row + 1 );
438  mCoordinateOperationTableWidget->setItem( row, i, item.release() );
439  }
440  }
441  row++;
442  }
444 #endif
445 
446  if ( mCoordinateOperationTableWidget->currentRow() < 0 )
447  mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
448 
449  mCoordinateOperationTableWidget->resizeColumnsToContents();
450 
451  tableCurrentItemChanged( nullptr, nullptr );
452 }
453 
455 {
456  QgsSettings settings;
457  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
458 
459  for ( int i = 0; i < 2; i++ )
460  {
461  settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
462  }
463 }
464 
466 {
467  OperationDetails preferred;
468 
469 #if PROJ_VERSION_MAJOR>=6
470  // for proj 6, return the first available transform -- they are sorted by preference by proj already
471  for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
472  {
473  if ( transform.isAvailable )
474  {
475  preferred.proj = transform.proj;
476  preferred.isAvailable = transform.isAvailable;
477  break;
478  }
479  }
480  return preferred;
481 #else
482  OperationDetails preferredNonDeprecated;
483  bool foundPreferredNonDeprecated = false;
484  bool foundPreferred = false;
485  OperationDetails nonDeprecated;
486  bool foundNonDeprecated = false;
487  OperationDetails fallback;
488  bool foundFallback = false;
489 
491  for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
492  {
493  if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
494  continue;
495 
496  const QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
497  const QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
498  if ( !foundPreferredNonDeprecated && ( ( srcInfo.preferred && !srcInfo.deprecated ) || transform.sourceTransformId == -1 )
499  && ( ( destInfo.preferred && !destInfo.deprecated ) || transform.destinationTransformId == -1 ) )
500  {
501  preferredNonDeprecated.sourceTransformId = transform.sourceTransformId;
502  preferredNonDeprecated.destinationTransformId = transform.destinationTransformId;
503  foundPreferredNonDeprecated = true;
504  }
505  else if ( !foundPreferred && ( srcInfo.preferred || transform.sourceTransformId == -1 ) &&
506  ( destInfo.preferred || transform.destinationTransformId == -1 ) )
507  {
508  preferred.sourceTransformId = transform.sourceTransformId;
509  preferred.destinationTransformId = transform.destinationTransformId;
510  foundPreferred = true;
511  }
512  else if ( !foundNonDeprecated && ( !srcInfo.deprecated || transform.sourceTransformId == -1 )
513  && ( !destInfo.deprecated || transform.destinationTransformId == -1 ) )
514  {
515  nonDeprecated.sourceTransformId = transform.sourceTransformId;
516  nonDeprecated.destinationTransformId = transform.destinationTransformId;
517  foundNonDeprecated = true;
518  }
519  else if ( !foundFallback )
520  {
521  fallback.sourceTransformId = transform.sourceTransformId;
522  fallback.destinationTransformId = transform.destinationTransformId;
523  foundFallback = true;
524  }
525  }
527  if ( foundPreferredNonDeprecated )
528  return preferredNonDeprecated;
529  else if ( foundPreferred )
530  return preferred;
531  else if ( foundNonDeprecated )
532  return nonDeprecated;
533  else
534  return fallback;
535 #endif
536 }
537 
538 QString QgsCoordinateOperationWidget::formatScope( const QString &s )
539 {
540  QString scope = s;
541 
542  QRegularExpression reGNSS( QStringLiteral( "\\bGNSS\\b" ) );
543  scope.replace( reGNSS, QObject::tr( "GNSS (Global Navigation Satellite System)" ) );
544 
545  QRegularExpression reCORS( QStringLiteral( "\\bCORS\\b" ) );
546  scope.replace( reCORS, QObject::tr( "CORS (Continually Operating Reference Station)" ) );
547 
548  return scope;
549 }
550 
552 {
553  int row = mCoordinateOperationTableWidget->currentRow();
554  OperationDetails op;
555 
556  if ( row >= 0 )
557  {
558  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
559  op.sourceTransformId = srcItem ? srcItem->data( TransformIdRole ).toInt() : -1;
560  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
561  op.destinationTransformId = destItem ? destItem->data( TransformIdRole ).toInt() : -1;
562  op.proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
563  op.isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() : true;
564  op.allowFallback = mAllowFallbackCheckBox->isChecked();
565  }
566  else
567  {
568  op.sourceTransformId = -1;
569  op.destinationTransformId = -1;
570  op.proj = QString();
571  }
572  return op;
573 }
574 
576 {
577  int prevRow = mCoordinateOperationTableWidget->currentRow();
578  mBlockSignals++;
579  for ( int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
580  {
581  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
582 #if PROJ_VERSION_MAJOR>=6
583  if ( srcItem && srcItem->data( ProjRole ).toString() == operation.proj )
584  {
585  mCoordinateOperationTableWidget->selectRow( row );
586  break;
587  }
588 #else
589  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
590 
591  // 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
592  if ( ( srcItem && destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
593  operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() )
594  || ( srcItem && destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
595  operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() )
596  || ( srcItem && !destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
597  operation.destinationTransformId == -1 )
598  || ( !srcItem && destItem && operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() &&
599  operation.sourceTransformId == -1 )
600  || ( srcItem && !destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
601  operation.sourceTransformId == -1 )
602  || ( !srcItem && destItem && operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() &&
603  operation.destinationTransformId == -1 )
604  )
605  {
606  mCoordinateOperationTableWidget->selectRow( row );
607  break;
608  }
609 #endif
610  }
611 
612  bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.allowFallback;
613  mAllowFallbackCheckBox->setChecked( operation.allowFallback );
614  mBlockSignals--;
615 
616  if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
617  emit operationChanged();
618 }
619 
621 {
622 #if PROJ_VERSION_MAJOR>=6
623  const QString op = context.calculateCoordinateOperation( mSourceCrs, mDestinationCrs );
624  if ( !op.isEmpty() )
625  {
626  OperationDetails deets;
627  deets.proj = op;
628  deets.allowFallback = context.allowFallbackTransform( mSourceCrs, mDestinationCrs );
629  setSelectedOperation( deets );
630  }
631  else
632  {
634  }
635 
636 #else
637  if ( context.hasTransform( mSourceCrs, mDestinationCrs ) )
638  {
640  const QgsDatumTransform::TransformPair op = context.calculateDatumTransforms( mSourceCrs, mDestinationCrs );
642  OperationDetails deets;
643  deets.sourceTransformId = op.sourceTransformId;
644  deets.destinationTransformId = op.destinationTransformId;
645  setSelectedOperation( deets );
646  }
647  else
648  {
650  }
651 #endif
652 }
653 
655 {
656  mAllowFallbackCheckBox->setVisible( visible );
657 }
658 
659 bool QgsCoordinateOperationWidget::gridShiftTransformation( const QString &itemText ) const
660 {
661  return !itemText.isEmpty() && !itemText.contains( QLatin1String( "towgs84" ), Qt::CaseInsensitive );
662 }
663 
664 bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item ) const
665 {
666  if ( !item )
667  {
668  return true;
669  }
670 
671  QString itemText = item->text();
672  if ( itemText.isEmpty() )
673  {
674  return true;
675  }
676 
677  char *projLib = getenv( "PROJ_LIB" );
678  if ( !projLib ) //no information about installation directory
679  {
680  return true;
681  }
682 
683  QStringList itemEqualSplit = itemText.split( '=' );
684  QString filename;
685  for ( int i = 1; i < itemEqualSplit.size(); ++i )
686  {
687  if ( i > 1 )
688  {
689  filename.append( '=' );
690  }
691  filename.append( itemEqualSplit.at( i ) );
692  }
693 
694  QDir projDir( projLib );
695  if ( projDir.exists() )
696  {
697  //look if filename in directory
698  QStringList fileList = projDir.entryList();
699  QStringList::const_iterator fileIt = fileList.constBegin();
700  for ( ; fileIt != fileList.constEnd(); ++fileIt )
701  {
702 #if defined(Q_OS_WIN)
703  if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
704 #else
705  if ( fileIt->compare( filename ) == 0 )
706 #endif //Q_OS_WIN
707  {
708  return true;
709  }
710  }
711  item->setToolTip( tr( "File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
712  return false; //not found in PROJ_LIB directory
713  }
714  return true;
715 }
716 
717 void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
718 {
719  int row = mCoordinateOperationTableWidget->currentRow();
720  if ( row < 0 )
721  {
722  mLabelSrcDescription->clear();
723  mLabelDstDescription->clear();
724 #if PROJ_VERSION_MAJOR>=6
725  mAreaCanvas->hide();
726  mInstallGridButton->hide();
727 #endif
728  }
729  else
730  {
731  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
732  mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
733  if ( srcItem )
734  {
735  // find area of intersection of operation, source and dest bounding boxes
736  // see https://github.com/OSGeo/PROJ/issues/1549 for justification
737  const QgsRectangle operationRect = srcItem->data( BoundsRole ).value< QgsRectangle >();
738  const QgsRectangle sourceRect = mSourceCrs.bounds();
739  const QgsRectangle destRect = mDestinationCrs.bounds();
740  QgsRectangle rect = operationRect.intersect( sourceRect );
741  rect = rect.intersect( destRect );
742 
743  mAreaCanvas->setPreviewRect( rect );
744 #if PROJ_VERSION_MAJOR>=6
745  mAreaCanvas->show();
746 
747  const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
748  mInstallGridButton->setVisible( !missingGrids.empty() );
749  if ( !missingGrids.empty() )
750  {
751  mInstallGridButton->setText( tr( "Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
752  }
753 #endif
754  }
755  else
756  {
757  mAreaCanvas->setPreviewRect( QgsRectangle() );
758 #if PROJ_VERSION_MAJOR>=6
759  mAreaCanvas->hide();
760  mInstallGridButton->hide();
761 #endif
762  }
763  QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
764  mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
765  }
766  OperationDetails newOp = selectedOperation();
767 #if PROJ_VERSION_MAJOR>=6
768  if ( newOp.proj != mPreviousOp.proj && !mBlockSignals )
769  emit operationChanged();
770 #else
771  if ( newOp.sourceTransformId != mPreviousOp.sourceTransformId ||
772  newOp.destinationTransformId != mPreviousOp.destinationTransformId )
773  emit operationChanged();
774 #endif
775  mPreviousOp = newOp;
776 }
777 
779 {
780  mSourceCrs = sourceCrs;
781 #if PROJ_VERSION_MAJOR>=6
782  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
783 #else
785  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
787 #endif
788  loadAvailableOperations();
789 }
790 
792 {
793  mDestinationCrs = destinationCrs;
794 #if PROJ_VERSION_MAJOR>=6
795  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
796 #else
798  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
800 #endif
801  loadAvailableOperations();
802 }
803 
804 void QgsCoordinateOperationWidget::showSupersededToggled( bool )
805 {
806 #if PROJ_VERSION_MAJOR>=6
807  mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
808 #else
810  mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
812 #endif
813  loadAvailableOperations();
814 }
815 
816 void QgsCoordinateOperationWidget::installGrid()
817 {
818 #if PROJ_VERSION_MAJOR>=6
819  int row = mCoordinateOperationTableWidget->currentRow();
820  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
821  if ( !srcItem )
822  return;
823 
824  const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
825  if ( missingGrids.empty() )
826  return;
827 
828  const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
829  const QString packageName = missingGridPackagesNames.value( 0 );
830  const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
831  const QString gridUrl = missingGridUrls.value( 0 );
832 
833  QString downloadMessage;
834  if ( !packageName.isEmpty() )
835  {
836  downloadMessage = tr( "This grid is part of the “<i>%1</i>” package, available for download from <a href=\"%2\">%2</a>." ).arg( packageName, gridUrl );
837  }
838  else if ( !gridUrl.isEmpty() )
839  {
840  downloadMessage = tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
841  }
842 
843  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 ) );
844 
845  QgsInstallGridShiftFileDialog *dlg = new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ), this );
846  dlg->setAttribute( Qt::WA_DeleteOnClose );
847  dlg->setWindowTitle( tr( "Install Grid File" ) );
848  dlg->setDescription( longMessage );
849  dlg->setDownloadMessage( downloadMessage );
850  dlg->exec();
851 
852 #endif
853 }
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:575
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:2692
QgsDatumTransform::TransformInfo::destinationCrsDescription
QString destinationCrsDescription
Destination CRS description.
Definition: qgsdatumtransform.h:112
qgsinstallgridshiftdialog.h
QgsCoordinateTransformContext
Definition: qgscoordinatetransformcontext.h:57
QgsCoordinateOperationWidget::defaultOperation
QgsCoordinateOperationWidget::OperationDetails defaultOperation() const
Returns the details of the default operation suggested by the widget.
Definition: qgscoordinateoperationwidget.cpp:465
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:157
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:380
qgsgui.h
crs
const QgsCoordinateReferenceSystem & crs
Definition: qgswfsgetfeature.cpp:105
QgsDatumTransform::TransformPair
Contains datum transform information.
Definition: qgsdatumtransform.h:54
QgsCoordinateOperationWidget::OperationDetails
Coordinate operation details.
Definition: qgscoordinateoperationwidget.h:39
QgsCoordinateOperationWidget::OperationDetails::allowFallback
bool allowFallback
true if fallback transforms can be used
Definition: qgscoordinateoperationwidget.h:54
QgsMapCanvas
Definition: qgsmapcanvas.h:78
QgsProject::instance
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:457
QgsSettings
Definition: qgssettings.h:61
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:654
QgsRectangle
Definition: qgsrectangle.h:41
QgsDatumTransform::GridDetails
Contains information about a projection transformation grid file.
Definition: qgsdatumtransform.h:133
QgsCoordinateReferenceSystem::bounds
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
Definition: qgscoordinatereferencesystem.cpp:1405
QgsCoordinateOperationWidget::~QgsCoordinateOperationWidget
~QgsCoordinateOperationWidget() override
Definition: qgscoordinateoperationwidget.cpp:454
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:732
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
Definition: qgsexception.h:65
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:1995
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:917
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:375
QgsCoordinateOperationWidget::selectedOperation
QgsCoordinateOperationWidget::OperationDetails selectedOperation() const
Returns the details of the operation currently selected within the widget.
Definition: qgscoordinateoperationwidget.cpp:551
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:791
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:181
QgsCoordinateReferenceSystem
Definition: qgscoordinatereferencesystem.h:209
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:778
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
Definition: qgsgeometry.h:122
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:94
QgsGeometry::boundingBox
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Definition: qgsgeometry.cpp:948
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:2951
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
Definition: qgscoordinatetransform.h:52
Q_NOWARN_DEPRECATED_PUSH
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:731
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:620
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