QGIS API Documentation  3.12.1-București (121cc00ff0)
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  }
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 }
void setSourceCrs(const QgsCoordinateReferenceSystem &crs)
Sets the source crs for the operations shown in the widget.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
void setMapCanvas(QgsMapCanvas *canvas)
Sets a map canvas to link to the widget, which allows the widget&#39;s choices to reflect the current can...
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.
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:731
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
Contains information about a projection transformation grid file.
QgsCoordinateOperationWidget::OperationDetails selectedOperation() const
Returns the details of the operation currently selected within the widget.
const QgsCoordinateReferenceSystem & crs
int datumTransformId
Datum transform ID.
void operationDoubleClicked()
Emitted when an operation is double-clicked in the widget.
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 deprecated
True if transform is deprecated.
void operationChanged()
Emitted when the operation selected in the dialog is changed.
QList< QgsCoordinateOperationWidget::OperationDetails > availableOperations() const
Returns a list of the available operations shown in the widget.
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
bool hasSelection() const
Returns true if there is a valid selection in the widget.
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...
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 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 proj
Proj coordinate operation description, for Proj >= 6.0 builds only.
QString authority
Authority name, e.g. EPSG.
Contains datum transform information.
Contains information about the context in which a coordinate transform is executed.
bool makeDefaultSelected() const
Returns true if the "make default" option is selected.
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.
void setShowFallbackOption(bool visible)
Sets whether the "allow fallback" operations option is visible.
const QgsMapSettings & mapSettings() const
Gets access to properties used for map rendering.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:732
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...
void setShowMakeDefault(bool show)
Sets whether the "make default" checkbox should be shown.
QgsCoordinateOperationWidget::OperationDetails defaultOperation() const
Returns the details of the default operation suggested by the widget.
void setSelectedOperationUsingContext(const QgsCoordinateTransformContext &context)
Automatically sets the selected operation using the settings encapsulated in a transform context...
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.
bool allowFallback
true if fallback transforms can be used
void setSelectedOperation(const QgsCoordinateOperationWidget::OperationDetails &operation)
Sets the details of the operation currently selected within the widget.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:450
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
QgsCoordinateOperationWidget(QWidget *parent=nullptr)
Constructor for QgsCoordinateOperationWidget.
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.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source CRS for the operations shown in the widget.
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)
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination CRS for the operations shown in the widget.
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.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination crs for the operations shown in the widget.
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...