30 #include <QPushButton>
32 #if PROJ_VERSION_MAJOR>=6
42 mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
43 mLabelSrcDescription->setOpenExternalLinks(
true );
44 mInstallGridButton->hide();
46 #if PROJ_VERSION_MAJOR>=6
47 connect( mInstallGridButton, &QPushButton::clicked,
this, &QgsCoordinateOperationWidget::installGrid );
48 connect( mAllowFallbackCheckBox, &QCheckBox::toggled,
this, [ = ]
53 mCoordinateOperationTableWidget->setColumnCount( 3 );
55 mCoordinateOperationTableWidget->setColumnCount( 2 );
59 #if PROJ_VERSION_MAJOR>=6
60 headers << tr(
"Transformation" ) << tr(
"Accuracy (meters)" ) << tr(
"Area of Use" );
62 headers << tr(
"Source Transform" ) << tr(
"Destination Transform" ) ;
64 mCoordinateOperationTableWidget->setHorizontalHeaderLabels( headers );
66 #if PROJ_VERSION_MAJOR<6
70 #if PROJ_VERSION_MAJOR>=6
72 mHideDeprecatedCheckBox->setVisible(
false );
73 mShowSupersededCheckBox->setVisible(
true );
74 mLabelDstDescription->hide();
76 mShowSupersededCheckBox->setVisible(
false );
77 mAllowFallbackCheckBox->setVisible(
false );
79 mHideDeprecatedCheckBox->setChecked( settings.
value( QStringLiteral(
"Windows/DatumTransformDialog/hideDeprecated" ),
true ).toBool() );
82 connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged,
this, [ = ] { loadAvailableOperations(); } );
83 connect( mShowSupersededCheckBox, &QCheckBox::toggled,
this, &QgsCoordinateOperationWidget::showSupersededToggled );
84 connect( mCoordinateOperationTableWidget, &QTableWidget::currentItemChanged,
this, &QgsCoordinateOperationWidget::tableCurrentItemChanged );
87 mLabelSrcDescription->clear();
88 mLabelDstDescription->clear();
93 #if PROJ_VERSION_MAJOR<6
102 mainCanvasPoly << mainCanvasPoly.at( 0 );
126 mMakeDefaultCheckBox->setVisible( show );
131 return mMakeDefaultCheckBox->isChecked();
136 return !mCoordinateOperationTableWidget->selectedItems().isEmpty();
141 QList<QgsCoordinateOperationWidget::OperationDetails> res;
142 res.reserve( mDatumTransforms.size() );
143 #if PROJ_VERSION_MAJOR>=6
147 op.
proj = details.proj;
165 void QgsCoordinateOperationWidget::loadAvailableOperations()
167 mCoordinateOperationTableWidget->setRowCount( 0 );
170 int preferredInitialRow = -1;
171 #if PROJ_VERSION_MAJOR>=6
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 );
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 );
186 QFont f = item->font();
189 item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
192 if ( !transform.isAvailable )
194 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
197 if ( preferredInitialRow < 0 && transform.isAvailable )
200 preferredInitialRow = row;
203 QString missingMessage;
204 if ( !transform.isAvailable )
206 QStringList gridMessages;
207 QStringList missingGrids;
208 QStringList missingGridPackages;
209 QStringList missingGridUrls;
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() )
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 );
227 m +=
' ' + tr(
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.
url );
234 item->setData( MissingGridsRole, missingGrids );
235 item->setData( MissingGridPackageNamesRole, missingGridPackages );
236 item->setData( MissingGridUrlsRole, missingGridUrls );
238 if ( gridMessages.count() > 1 )
240 for (
int k = 0; k < gridMessages.count(); ++k )
241 gridMessages[k] = QStringLiteral(
"<li>%1</li>" ).arg( gridMessages.at( k ) );
243 missingMessage = QStringLiteral(
"<ul>%1</ul" ).arg( gridMessages.join( QString() ) );
245 else if ( !gridMessages.empty() )
247 missingMessage = gridMessages.constFirst();
251 QStringList areasOfUse;
252 QStringList authorityCodes;
258 if ( !singleOpDetails.
scope.isEmpty() )
260 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Scope" ), formatScope( singleOpDetails.
scope ) );
262 if ( !singleOpDetails.
remarks.isEmpty() )
264 if ( !text.isEmpty() )
265 text += QStringLiteral(
"<br>" );
266 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Remarks" ), singleOpDetails.
remarks );
268 if ( !singleOpDetails.
areaOfUse.isEmpty() )
270 if ( !areasOfUse.contains( singleOpDetails.
areaOfUse ) )
273 if ( !singleOpDetails.
authority.isEmpty() && !singleOpDetails.
code.isEmpty() )
275 const QString identifier = QStringLiteral(
"%1:%2" ).arg( singleOpDetails.
authority, singleOpDetails.
code );
276 if ( !authorityCodes.contains( identifier ) )
277 authorityCodes << identifier;
280 if ( !text.isEmpty() )
282 opText.append( text );
287 if ( !transform.scope.isEmpty() )
289 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Scope" ), transform.scope );
291 if ( !transform.remarks.isEmpty() )
293 if ( !text.isEmpty() )
294 text += QStringLiteral(
"<br>" );
295 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Remarks" ), transform.remarks );
297 if ( !text.isEmpty() )
299 opText.append( text );
302 if ( opText.count() > 1 )
304 for (
int k = 0; k < opText.count(); ++k )
305 opText[k] = QStringLiteral(
"<li>%1</li>" ).arg( opText.at( k ) );
308 if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
309 areasOfUse << transform.areaOfUse;
310 item->setData( BoundsRole, transform.bounds );
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;
316 const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
317 const QColor active = palette().color( QPalette::Active, QPalette::Text );
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 );
329 item->setToolTip( toolTipString );
330 mCoordinateOperationTableWidget->setRowCount( row + 1 );
331 mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
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 )
339 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
341 mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
343 #if PROJ_VERSION_MAJOR>=6
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 )
351 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
353 mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
362 bool itemDisabled =
false;
363 bool itemHidden =
false;
365 if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
370 for (
int i = 0; i < 2; ++i )
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 );
386 itemHidden = mHideDeprecatedCheckBox->isChecked();
387 item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
392 QFont f = item->font();
395 item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
401 preferredInitialRow = row;
404 QString toolTipString;
405 if ( gridShiftTransformation( item->text() ) )
407 toolTipString.append( QStringLiteral(
"<p><b>NTv2</b></p>" ) );
411 toolTipString.append( QStringLiteral(
"<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.
epsgCode ) );
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 ) );
420 toolTipString.append(
"<p><b>Preferred transformation</b></p>" );
422 toolTipString.append(
"<p><b>Deprecated transformation</b></p>" );
424 item->setToolTip( toolTipString );
426 if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
435 item->setFlags( Qt::NoItemFlags );
437 mCoordinateOperationTableWidget->setRowCount( row + 1 );
438 mCoordinateOperationTableWidget->setItem( row, i, item.release() );
446 if ( mCoordinateOperationTableWidget->currentRow() < 0 )
447 mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
449 mCoordinateOperationTableWidget->resizeColumnsToContents();
451 tableCurrentItemChanged(
nullptr,
nullptr );
457 settings.
setValue( QStringLiteral(
"Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
459 for (
int i = 0; i < 2; i++ )
461 settings.
setValue( QStringLiteral(
"Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
469 #if PROJ_VERSION_MAJOR>=6
473 if ( transform.isAvailable )
475 preferred.
proj = transform.proj;
483 bool foundPreferredNonDeprecated =
false;
484 bool foundPreferred =
false;
486 bool foundNonDeprecated =
false;
488 bool foundFallback =
false;
493 if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
498 if ( !foundPreferredNonDeprecated && ( ( srcInfo.
preferred && !srcInfo.
deprecated ) || transform.sourceTransformId == -1 )
499 && ( ( destInfo.
preferred && !destInfo.
deprecated ) || transform.destinationTransformId == -1 ) )
503 foundPreferredNonDeprecated =
true;
505 else if ( !foundPreferred && ( srcInfo.
preferred || transform.sourceTransformId == -1 ) &&
506 ( destInfo.
preferred || transform.destinationTransformId == -1 ) )
510 foundPreferred =
true;
512 else if ( !foundNonDeprecated && ( !srcInfo.
deprecated || transform.sourceTransformId == -1 )
513 && ( !destInfo.
deprecated || transform.destinationTransformId == -1 ) )
517 foundNonDeprecated =
true;
519 else if ( !foundFallback )
523 foundFallback =
true;
527 if ( foundPreferredNonDeprecated )
528 return preferredNonDeprecated;
529 else if ( foundPreferred )
531 else if ( foundNonDeprecated )
532 return nonDeprecated;
538 QString QgsCoordinateOperationWidget::formatScope(
const QString &s )
542 QRegularExpression reGNSS( QStringLiteral(
"\\bGNSS\\b" ) );
543 scope.replace( reGNSS, QObject::tr(
"GNSS (Global Navigation Satellite System)" ) );
545 QRegularExpression reCORS( QStringLiteral(
"\\bCORS\\b" ) );
546 scope.replace( reCORS, QObject::tr(
"CORS (Continually Operating Reference Station)" ) );
553 int row = mCoordinateOperationTableWidget->currentRow();
558 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
560 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
562 op.
proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
563 op.
isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() :
true;
577 int prevRow = mCoordinateOperationTableWidget->currentRow();
579 for (
int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
581 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
582 #if PROJ_VERSION_MAJOR>=6
583 if ( srcItem && srcItem->data( ProjRole ).toString() == operation.
proj )
585 mCoordinateOperationTableWidget->selectRow( row );
589 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
592 if ( ( srcItem && destItem && operation.
sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
594 || ( srcItem && destItem && operation.
destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
596 || ( srcItem && !destItem && operation.
sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
598 || ( !srcItem && destItem && operation.
destinationTransformId == destItem->data( TransformIdRole ).toInt() &&
600 || ( srcItem && !destItem && operation.
destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
602 || ( !srcItem && destItem && operation.
sourceTransformId == destItem->data( TransformIdRole ).toInt() &&
606 mCoordinateOperationTableWidget->selectRow( row );
612 bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.
allowFallback;
613 mAllowFallbackCheckBox->setChecked( operation.
allowFallback );
616 if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
622 #if PROJ_VERSION_MAJOR>=6
637 if ( context.
hasTransform( mSourceCrs, mDestinationCrs ) )
644 deets.destinationTransformId = op.destinationTransformId;
656 mAllowFallbackCheckBox->setVisible( visible );
659 bool QgsCoordinateOperationWidget::gridShiftTransformation(
const QString &itemText )
const
661 return !itemText.isEmpty() && !itemText.contains( QLatin1String(
"towgs84" ), Qt::CaseInsensitive );
664 bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item )
const
671 QString itemText = item->text();
672 if ( itemText.isEmpty() )
677 char *projLib = getenv(
"PROJ_LIB" );
683 QStringList itemEqualSplit = itemText.split(
'=' );
685 for (
int i = 1; i < itemEqualSplit.size(); ++i )
689 filename.append(
'=' );
691 filename.append( itemEqualSplit.at( i ) );
694 QDir projDir( projLib );
695 if ( projDir.exists() )
698 QStringList fileList = projDir.entryList();
699 QStringList::const_iterator fileIt = fileList.constBegin();
700 for ( ; fileIt != fileList.constEnd(); ++fileIt )
702 #if defined(Q_OS_WIN)
703 if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
705 if ( fileIt->compare( filename ) == 0 )
711 item->setToolTip( tr(
"File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
717 void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
719 int row = mCoordinateOperationTableWidget->currentRow();
722 mLabelSrcDescription->clear();
723 mLabelDstDescription->clear();
724 #if PROJ_VERSION_MAJOR>=6
726 mInstallGridButton->hide();
731 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
732 mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
743 mAreaCanvas->setPreviewRect( rect );
744 #if PROJ_VERSION_MAJOR>=6
747 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
748 mInstallGridButton->setVisible( !missingGrids.empty() );
749 if ( !missingGrids.empty() )
751 mInstallGridButton->setText( tr(
"Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
758 #if PROJ_VERSION_MAJOR>=6
760 mInstallGridButton->hide();
763 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
764 mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
767 #if PROJ_VERSION_MAJOR>=6
768 if ( newOp.proj != mPreviousOp.
proj && !mBlockSignals )
781 #if PROJ_VERSION_MAJOR>=6
788 loadAvailableOperations();
794 #if PROJ_VERSION_MAJOR>=6
801 loadAvailableOperations();
804 void QgsCoordinateOperationWidget::showSupersededToggled(
bool )
806 #if PROJ_VERSION_MAJOR>=6
813 loadAvailableOperations();
816 void QgsCoordinateOperationWidget::installGrid()
818 #if PROJ_VERSION_MAJOR>=6
819 int row = mCoordinateOperationTableWidget->currentRow();
820 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
824 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
825 if ( missingGrids.empty() )
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 );
833 QString downloadMessage;
834 if ( !packageName.isEmpty() )
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 );
838 else if ( !gridUrl.isEmpty() )
840 downloadMessage = tr(
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
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 ) );
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 );