19#include "moc_qgscoordinateoperationwidget.cpp" 
   32#include <QRegularExpression> 
   42  mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
 
   43  mLabelSrcDescription->setOpenExternalLinks( 
true );
 
   44  mInstallGridButton->hide();
 
   46  connect( mInstallGridButton, &QPushButton::clicked, 
this, &QgsCoordinateOperationWidget::installGrid );
 
   47  connect( mAllowFallbackCheckBox, &QCheckBox::toggled, 
this, [
this] {
 
   51  mCoordinateOperationTableWidget->setColumnCount( 3 );
 
   54  headers << tr( 
"Transformation" ) << tr( 
"Accuracy (meters)" ) << tr( 
"Area of Use" );
 
   55  mCoordinateOperationTableWidget->setHorizontalHeaderLabels( headers );
 
   57  mHideDeprecatedCheckBox->setVisible( 
false );
 
   58  mShowSupersededCheckBox->setVisible( 
true );
 
   59  mLabelDstDescription->hide();
 
   61  connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged, 
this, [
this] { loadAvailableOperations(); } );
 
   62  connect( mShowSupersededCheckBox, &QCheckBox::toggled, 
this, &QgsCoordinateOperationWidget::showSupersededToggled );
 
   63  connect( mCoordinateOperationTableWidget, &QTableWidget::currentItemChanged, 
this, &QgsCoordinateOperationWidget::tableCurrentItemChanged );
 
   66  mLabelSrcDescription->clear();
 
   67  mLabelDstDescription->clear();
 
 
   78    mainCanvasPoly << mainCanvasPoly.at( 0 );
 
 
   99  mMakeDefaultCheckBox->setVisible( show );
 
 
  104  return mMakeDefaultCheckBox->isChecked();
 
 
  109  return !mCoordinateOperationTableWidget->selectedItems().isEmpty();
 
 
  114  QList<QgsCoordinateOperationWidget::OperationDetails> res;
 
  115  res.reserve( mDatumTransforms.size() );
 
  119    op.
proj = details.proj;
 
 
  128void QgsCoordinateOperationWidget::loadAvailableOperations()
 
  130  mCoordinateOperationTableWidget->setRowCount( 0 );
 
  133  int preferredInitialRow = -1;
 
  137    auto item = std::make_unique<QTableWidgetItem>();
 
  138    item->setData( ProjRole, transform.proj );
 
  139    item->setData( AvailableRole, transform.isAvailable );
 
  140    item->setFlags( item->flags() & ~Qt::ItemIsEditable );
 
  142    QString name = transform.name;
 
  143    if ( !transform.authority.isEmpty() && !transform.code.isEmpty() )
 
  144      name += QStringLiteral( 
" %1 %2:%3" ).arg( QString( QChar( 0x2013 ) ), transform.authority, transform.code );
 
  145    item->setText( name );
 
  149      QFont f = item->font();
 
  152      item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
 
  155    if ( !transform.isAvailable )
 
  157      item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
 
  160    if ( preferredInitialRow < 0 && transform.isAvailable )
 
  163      preferredInitialRow = row;
 
  166    QString missingMessage;
 
  167    if ( !transform.isAvailable )
 
  169      QStringList gridMessages;
 
  170      QStringList missingGrids;
 
  171      QStringList missingGridPackages;
 
  172      QStringList missingGridUrls;
 
  176        if ( !grid.isAvailable )
 
  178          missingGrids << grid.shortName;
 
  179          missingGridPackages << grid.packageName;
 
  180          missingGridUrls << grid.url;
 
  181          QString m = tr( 
"This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.shortName );
 
  182          if ( !grid.url.isEmpty() )
 
  184            if ( !grid.packageName.isEmpty() )
 
  186              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 );
 
  190              m += 
' ' + tr( 
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.url );
 
  197      item->setData( MissingGridsRole, missingGrids );
 
  198      item->setData( MissingGridPackageNamesRole, missingGridPackages );
 
  199      item->setData( MissingGridUrlsRole, missingGridUrls );
 
  201      if ( gridMessages.count() > 1 )
 
  203        for ( 
int k = 0; k < gridMessages.count(); ++k )
 
  204          gridMessages[k] = QStringLiteral( 
"<li>%1</li>" ).arg( gridMessages.at( k ) );
 
  206        missingMessage = QStringLiteral( 
"<ul>%1</ul" ).arg( gridMessages.join( QString() ) );
 
  208      else if ( !gridMessages.empty() )
 
  210        missingMessage = gridMessages.constFirst();
 
  214    QStringList areasOfUse;
 
  215    QStringList authorityCodes;
 
  218    QString lastSingleOpScope;
 
  219    QString lastSingleOpRemarks;
 
  223      if ( !singleOpDetails.scope.isEmpty() )
 
  225        text += QStringLiteral( 
"<b>%1</b>: %2" ).arg( tr( 
"Scope" ), formatScope( singleOpDetails.scope ) );
 
  226        lastSingleOpScope = singleOpDetails.scope;
 
  228      if ( !singleOpDetails.remarks.isEmpty() )
 
  230        if ( !text.isEmpty() )
 
  231          text += QLatin1String( 
"<br>" );
 
  232        text += QStringLiteral( 
"<b>%1</b>: %2" ).arg( tr( 
"Remarks" ), singleOpDetails.remarks );
 
  233        lastSingleOpRemarks = singleOpDetails.remarks;
 
  235      if ( !singleOpDetails.areaOfUse.isEmpty() )
 
  237        if ( !areasOfUse.contains( singleOpDetails.areaOfUse ) )
 
  238          areasOfUse << singleOpDetails.areaOfUse;
 
  240      if ( !singleOpDetails.authority.isEmpty() && !singleOpDetails.code.isEmpty() )
 
  242        const QString identifier = QStringLiteral( 
"%1:%2" ).arg( singleOpDetails.authority, singleOpDetails.code );
 
  243        if ( !authorityCodes.contains( identifier ) )
 
  244          authorityCodes << identifier;
 
  247      if ( !text.isEmpty() )
 
  249        opText.append( text );
 
  254    if ( !transform.scope.isEmpty() && transform.scope != lastSingleOpScope )
 
  256      text += QStringLiteral( 
"<b>%1</b>: %2" ).arg( tr( 
"Scope" ), transform.scope );
 
  258    if ( !transform.remarks.isEmpty() && transform.remarks != lastSingleOpRemarks )
 
  260      if ( !text.isEmpty() )
 
  261        text += QLatin1String( 
"<br>" );
 
  262      text += QStringLiteral( 
"<b>%1</b>: %2" ).arg( tr( 
"Remarks" ), transform.remarks );
 
  264    if ( !text.isEmpty() )
 
  266      opText.append( text );
 
  269    if ( opText.count() > 1 )
 
  271      for ( 
int k = 0; k < opText.count(); ++k )
 
  272        opText[k] = QStringLiteral( 
"<li>%1</li>" ).arg( opText.at( k ) );
 
  275    if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
 
  276      areasOfUse << transform.areaOfUse;
 
  277    item->setData( BoundsRole, transform.bounds );
 
  279    const QString 
id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral( 
"%1:%2" ).arg( transform.authority, transform.code ) : QString();
 
  280    if ( !
id.isEmpty() && !authorityCodes.contains( 
id ) )
 
  281      authorityCodes << id;
 
  283    const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
 
  284    const QColor active = palette().color( QPalette::Active, QPalette::Text );
 
  286    const QColor codeColor( 
static_cast<int>( active.red() * 0.6 + disabled.red() * 0.4 ), 
static_cast<int>( active.green() * 0.6 + disabled.green() * 0.4 ), 
static_cast<int>( active.blue() * 0.6 + disabled.blue() * 0.4 ) );
 
  287    const QString toolTipString = QStringLiteral( 
"<b>%1</b>" ).arg( transform.name )
 
  288                                  + ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral( 
"<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral( 
"<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
 
  289                                  + ( !areasOfUse.empty() ? QStringLiteral( 
"<p><b>%1</b>: %2</p>" ).arg( tr( 
"Area of use" ), areasOfUse.join( QLatin1String( 
", " ) ) ) : QString() )
 
  290                                  + ( !authorityCodes.empty() ? QStringLiteral( 
"<p><b>%1</b>: %2</p>" ).arg( tr( 
"Identifiers" ), authorityCodes.join( QLatin1String( 
", " ) ) ) : QString() )
 
  291                                  + ( !missingMessage.isEmpty() ? QStringLiteral( 
"<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
 
  292                                  + QStringLiteral( 
"<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
 
  294    item->setToolTip( toolTipString );
 
  295    mCoordinateOperationTableWidget->setRowCount( row + 1 );
 
  296    mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
 
  298    item = std::make_unique<QTableWidgetItem>();
 
  299    item->setFlags( item->flags() & ~Qt::ItemIsEditable );
 
  300    item->setText( transform.accuracy >= 0 ? QLocale().toString( transform.accuracy ) : tr( 
"Unknown" ) );
 
  301    item->setToolTip( toolTipString );
 
  302    if ( !transform.isAvailable )
 
  304      item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
 
  306    mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
 
  309    item = std::make_unique<QTableWidgetItem>();
 
  310    item->setFlags( item->flags() & ~Qt::ItemIsEditable );
 
  311    item->setText( areasOfUse.join( QLatin1String( 
", " ) ) );
 
  312    item->setToolTip( toolTipString );
 
  313    if ( !transform.isAvailable )
 
  315      item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
 
  317    mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
 
  322  if ( mCoordinateOperationTableWidget->currentRow() < 0 )
 
  323    mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
 
  325  mCoordinateOperationTableWidget->resizeColumnsToContents();
 
  327  tableCurrentItemChanged( 
nullptr, 
nullptr );
 
  333  settings.
setValue( QStringLiteral( 
"Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
 
  335  for ( 
int i = 0; i < 2; i++ )
 
  337    settings.
setValue( QStringLiteral( 
"Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
 
 
  348    if ( transform.isAvailable )
 
  350      preferred.
proj = transform.proj;
 
 
  358QString QgsCoordinateOperationWidget::formatScope( 
const QString &s )
 
  362  const thread_local QRegularExpression reGNSS( QStringLiteral( 
"\\bGNSS\\b" ) );
 
  363  scope.replace( reGNSS, QObject::tr( 
"GNSS (Global Navigation Satellite System)" ) );
 
  365  const thread_local QRegularExpression reCORS( QStringLiteral( 
"\\bCORS\\b" ) );
 
  366  scope.replace( reCORS, QObject::tr( 
"CORS (Continually Operating Reference Station)" ) );
 
  373  int row = mCoordinateOperationTableWidget->currentRow();
 
  378    QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
 
  380    QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
 
  382    op.
proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
 
  383    op.
isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() : 
true;
 
 
  397  int prevRow = mCoordinateOperationTableWidget->currentRow();
 
  399  for ( 
int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
 
  401    QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
 
  402    if ( srcItem && srcItem->data( ProjRole ).toString() == operation.
proj )
 
  404      mCoordinateOperationTableWidget->selectRow( row );
 
  409  bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.
allowFallback;
 
  410  mAllowFallbackCheckBox->setChecked( operation.
allowFallback );
 
  413  if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
 
 
  435  mAllowFallbackCheckBox->setVisible( visible );
 
 
  438bool QgsCoordinateOperationWidget::gridShiftTransformation( 
const QString &itemText )
 const 
  440  return !itemText.isEmpty() && !itemText.contains( QLatin1String( 
"towgs84" ), Qt::CaseInsensitive );
 
  443bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item )
 const 
  450  QString itemText = item->text();
 
  451  if ( itemText.isEmpty() )
 
  456  char *projLib = getenv( 
"PROJ_LIB" );
 
  462  QStringList itemEqualSplit = itemText.split( 
'=' );
 
  464  for ( 
int i = 1; i < itemEqualSplit.size(); ++i )
 
  468      filename.append( 
'=' );
 
  470    filename.append( itemEqualSplit.at( i ) );
 
  473  QDir projDir( projLib );
 
  474  if ( projDir.exists() )
 
  477    QStringList fileList = projDir.entryList();
 
  478    QStringList::const_iterator fileIt = fileList.constBegin();
 
  479    for ( ; fileIt != fileList.constEnd(); ++fileIt )
 
  481#if defined( Q_OS_WIN ) 
  482      if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
 
  484      if ( fileIt->compare( filename ) == 0 )
 
  490    item->setToolTip( tr( 
"File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
 
  496void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
 
  498  int row = mCoordinateOperationTableWidget->currentRow();
 
  501    mLabelSrcDescription->clear();
 
  502    mLabelDstDescription->clear();
 
  504    mInstallGridButton->hide();
 
  508    QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
 
  509    mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
 
  520      mAreaCanvas->setPreviewRect( rect );
 
  523      const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
 
  524      mInstallGridButton->setVisible( !missingGrids.empty() );
 
  525      if ( !missingGrids.empty() )
 
  527        mInstallGridButton->setText( tr( 
"Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
 
  534      mInstallGridButton->hide();
 
  536    QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
 
  537    mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
 
  540  if ( newOp.proj != mPreviousOp.
proj && !mBlockSignals )
 
  549  loadAvailableOperations();
 
 
  556  loadAvailableOperations();
 
 
  559void QgsCoordinateOperationWidget::showSupersededToggled( 
bool )
 
  562  loadAvailableOperations();
 
  565void QgsCoordinateOperationWidget::installGrid()
 
  567  int row = mCoordinateOperationTableWidget->currentRow();
 
  568  QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
 
  572  const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
 
  573  if ( missingGrids.empty() )
 
  576  const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
 
  577  const QString packageName = missingGridPackagesNames.value( 0 );
 
  578  const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
 
  579  const QString gridUrl = missingGridUrls.value( 0 );
 
  581  QString downloadMessage;
 
  582  if ( !packageName.isEmpty() )
 
  584    downloadMessage = tr( 
"This grid is part of the “<i>%1</i>” package, available for download from <a href=\"%2\">%2</a>." ).arg( packageName, gridUrl );
 
  586  else if ( !gridUrl.isEmpty() )
 
  588    downloadMessage = tr( 
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
 
  591  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 ) );
 
  593  QgsInstallGridShiftFileDialog *dlg = 
new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ), 
this );
 
  594  dlg->setAttribute( Qt::WA_DeleteOnClose );
 
  595  dlg->setWindowTitle( tr( 
"Install Grid File" ) );
 
  596  dlg->setDescription( longMessage );
 
  597  dlg->setDownloadMessage( downloadMessage );
 
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...
 
Custom exception class for Coordinate Reference System related exceptions.
 
A geometry is the spatial representation of a feature.
 
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.
 
Qgis::GeometryOperationResult transform(const QgsCoordinateTransform &ct, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool transformZ=false)
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.
 
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.
 
A rectangle specified with double values.
 
QgsRectangle intersect(const QgsRectangle &rect) const
Returns the intersection with the given rectangle.
 
Stores settings for use within QGIS.
 
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
 
const QgsCoordinateReferenceSystem & crs