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