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;
255 QString lastSingleOpScope;
256 QString lastSingleOpRemarks;
260 if ( !singleOpDetails.
scope.isEmpty() )
262 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Scope" ), formatScope( singleOpDetails.
scope ) );
263 lastSingleOpScope = singleOpDetails.
scope;
265 if ( !singleOpDetails.
remarks.isEmpty() )
267 if ( !text.isEmpty() )
268 text += QLatin1String(
"<br>" );
269 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Remarks" ), singleOpDetails.
remarks );
270 lastSingleOpRemarks = singleOpDetails.
remarks;
272 if ( !singleOpDetails.
areaOfUse.isEmpty() )
274 if ( !areasOfUse.contains( singleOpDetails.
areaOfUse ) )
277 if ( !singleOpDetails.
authority.isEmpty() && !singleOpDetails.
code.isEmpty() )
279 const QString identifier = QStringLiteral(
"%1:%2" ).arg( singleOpDetails.
authority, singleOpDetails.
code );
280 if ( !authorityCodes.contains( identifier ) )
281 authorityCodes << identifier;
284 if ( !text.isEmpty() )
286 opText.append( text );
291 if ( !transform.scope.isEmpty() && transform.scope != lastSingleOpScope )
293 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Scope" ), transform.scope );
295 if ( !transform.remarks.isEmpty() && transform.remarks != lastSingleOpRemarks )
297 if ( !text.isEmpty() )
298 text += QLatin1String(
"<br>" );
299 text += QStringLiteral(
"<b>%1</b>: %2" ).arg( tr(
"Remarks" ), transform.remarks );
301 if ( !text.isEmpty() )
303 opText.append( text );
306 if ( opText.count() > 1 )
308 for (
int k = 0; k < opText.count(); ++k )
309 opText[k] = QStringLiteral(
"<li>%1</li>" ).arg( opText.at( k ) );
312 if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
313 areasOfUse << transform.areaOfUse;
314 item->setData( BoundsRole, transform.bounds );
316 const QString
id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral(
"%1:%2" ).arg( transform.authority, transform.code ) : QString();
317 if ( !
id.isEmpty() && !authorityCodes.contains(
id ) )
318 authorityCodes << id;
320 const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
321 const QColor active = palette().color( QPalette::Active, QPalette::Text );
323 const QColor codeColor(
static_cast< int >( active.red() * 0.6 + disabled.red() * 0.4 ),
324 static_cast< int >( active.green() * 0.6 + disabled.green() * 0.4 ),
325 static_cast< int >( active.blue() * 0.6 + disabled.blue() * 0.4 ) );
326 const QString toolTipString = QStringLiteral(
"<b>%1</b>" ).arg( transform.name )
327 + ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral(
"<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral(
"<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
328 + ( !areasOfUse.empty() ? QStringLiteral(
"<p><b>%1</b>: %2</p>" ).arg( tr(
"Area of use" ), areasOfUse.join( QLatin1String(
", " ) ) ) : QString() )
329 + ( !authorityCodes.empty() ? QStringLiteral(
"<p><b>%1</b>: %2</p>" ).arg( tr(
"Identifiers" ), authorityCodes.join( QLatin1String(
", " ) ) ) : QString() )
330 + ( !missingMessage.isEmpty() ? QStringLiteral(
"<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
331 + QStringLiteral(
"<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
333 item->setToolTip( toolTipString );
334 mCoordinateOperationTableWidget->setRowCount( row + 1 );
335 mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
337 item = qgis::make_unique< QTableWidgetItem >();
338 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
339 item->setText( transform.accuracy >= 0 ? QString::number( transform.accuracy ) : tr(
"Unknown" ) );
340 item->setToolTip( toolTipString );
341 if ( !transform.isAvailable )
343 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
345 mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
347 #if PROJ_VERSION_MAJOR>=6
349 item = qgis::make_unique< QTableWidgetItem >();
350 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
351 item->setText( areasOfUse.join( QLatin1String(
", " ) ) );
352 item->setToolTip( toolTipString );
353 if ( !transform.isAvailable )
355 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
357 mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
366 bool itemDisabled =
false;
367 bool itemHidden =
false;
369 if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
374 for (
int i = 0; i < 2; ++i )
376 std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
377 int nr = i == 0 ? transform.sourceTransformId : transform.destinationTransformId;
378 item->setData( TransformIdRole, nr );
379 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
390 itemHidden = mHideDeprecatedCheckBox->isChecked();
391 item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
396 QFont f = item->font();
399 item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
405 preferredInitialRow = row;
408 QString toolTipString;
409 if ( gridShiftTransformation( item->text() ) )
411 toolTipString.append( QStringLiteral(
"<p><b>NTv2</b></p>" ) );
415 toolTipString.append( QStringLiteral(
"<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.
epsgCode ) );
420 toolTipString.append( QStringLiteral(
"<p><b>Remarks:</b> %1</p>" ).arg( info.
remarks ) );
421 if ( !info.
scope.isEmpty() )
422 toolTipString.append( QStringLiteral(
"<p><b>Scope:</b> %1</p>" ).arg( info.
scope ) );
424 toolTipString.append(
"<p><b>Preferred transformation</b></p>" );
426 toolTipString.append(
"<p><b>Deprecated transformation</b></p>" );
428 item->setToolTip( toolTipString );
430 if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
439 item->setFlags( Qt::NoItemFlags );
441 mCoordinateOperationTableWidget->setRowCount( row + 1 );
442 mCoordinateOperationTableWidget->setItem( row, i, item.release() );
450 if ( mCoordinateOperationTableWidget->currentRow() < 0 )
451 mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
453 mCoordinateOperationTableWidget->resizeColumnsToContents();
455 tableCurrentItemChanged(
nullptr,
nullptr );
461 settings.
setValue( QStringLiteral(
"Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
463 for (
int i = 0; i < 2; i++ )
465 settings.
setValue( QStringLiteral(
"Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
473 #if PROJ_VERSION_MAJOR>=6
477 if ( transform.isAvailable )
479 preferred.
proj = transform.proj;
487 bool foundPreferredNonDeprecated =
false;
488 bool foundPreferred =
false;
490 bool foundNonDeprecated =
false;
492 bool foundFallback =
false;
497 if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
502 if ( !foundPreferredNonDeprecated && ( ( srcInfo.
preferred && !srcInfo.
deprecated ) || transform.sourceTransformId == -1 )
503 && ( ( destInfo.
preferred && !destInfo.
deprecated ) || transform.destinationTransformId == -1 ) )
507 foundPreferredNonDeprecated =
true;
509 else if ( !foundPreferred && ( srcInfo.
preferred || transform.sourceTransformId == -1 ) &&
510 ( destInfo.
preferred || transform.destinationTransformId == -1 ) )
514 foundPreferred =
true;
516 else if ( !foundNonDeprecated && ( !srcInfo.
deprecated || transform.sourceTransformId == -1 )
517 && ( !destInfo.
deprecated || transform.destinationTransformId == -1 ) )
521 foundNonDeprecated =
true;
523 else if ( !foundFallback )
527 foundFallback =
true;
531 if ( foundPreferredNonDeprecated )
532 return preferredNonDeprecated;
533 else if ( foundPreferred )
535 else if ( foundNonDeprecated )
536 return nonDeprecated;
542 QString QgsCoordinateOperationWidget::formatScope(
const QString &s )
546 QRegularExpression reGNSS( QStringLiteral(
"\\bGNSS\\b" ) );
547 scope.replace( reGNSS, QObject::tr(
"GNSS (Global Navigation Satellite System)" ) );
549 QRegularExpression reCORS( QStringLiteral(
"\\bCORS\\b" ) );
550 scope.replace( reCORS, QObject::tr(
"CORS (Continually Operating Reference Station)" ) );
557 int row = mCoordinateOperationTableWidget->currentRow();
562 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
564 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
566 op.
proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
567 op.
isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() :
true;
581 int prevRow = mCoordinateOperationTableWidget->currentRow();
583 for (
int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
585 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
586 #if PROJ_VERSION_MAJOR>=6
587 if ( srcItem && srcItem->data( ProjRole ).toString() == operation.
proj )
589 mCoordinateOperationTableWidget->selectRow( row );
593 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
596 if ( ( srcItem && destItem && operation.
sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
598 || ( srcItem && destItem && operation.
destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
600 || ( srcItem && !destItem && operation.
sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
602 || ( !srcItem && destItem && operation.
destinationTransformId == destItem->data( TransformIdRole ).toInt() &&
604 || ( srcItem && !destItem && operation.
destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
606 || ( !srcItem && destItem && operation.
sourceTransformId == destItem->data( TransformIdRole ).toInt() &&
610 mCoordinateOperationTableWidget->selectRow( row );
616 bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.
allowFallback;
617 mAllowFallbackCheckBox->setChecked( operation.
allowFallback );
620 if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
626 #if PROJ_VERSION_MAJOR>=6
641 if ( context.
hasTransform( mSourceCrs, mDestinationCrs ) )
648 deets.destinationTransformId = op.destinationTransformId;
660 mAllowFallbackCheckBox->setVisible( visible );
663 bool QgsCoordinateOperationWidget::gridShiftTransformation(
const QString &itemText )
const
665 return !itemText.isEmpty() && !itemText.contains( QLatin1String(
"towgs84" ), Qt::CaseInsensitive );
668 bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item )
const
675 QString itemText = item->text();
676 if ( itemText.isEmpty() )
681 char *projLib = getenv(
"PROJ_LIB" );
687 QStringList itemEqualSplit = itemText.split(
'=' );
689 for (
int i = 1; i < itemEqualSplit.size(); ++i )
693 filename.append(
'=' );
695 filename.append( itemEqualSplit.at( i ) );
698 QDir projDir( projLib );
699 if ( projDir.exists() )
702 QStringList fileList = projDir.entryList();
703 QStringList::const_iterator fileIt = fileList.constBegin();
704 for ( ; fileIt != fileList.constEnd(); ++fileIt )
706 #if defined(Q_OS_WIN)
707 if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
709 if ( fileIt->compare( filename ) == 0 )
715 item->setToolTip( tr(
"File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
721 void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
723 int row = mCoordinateOperationTableWidget->currentRow();
726 mLabelSrcDescription->clear();
727 mLabelDstDescription->clear();
728 #if PROJ_VERSION_MAJOR>=6
730 mInstallGridButton->hide();
735 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
736 mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
747 mAreaCanvas->setPreviewRect( rect );
748 #if PROJ_VERSION_MAJOR>=6
751 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
752 mInstallGridButton->setVisible( !missingGrids.empty() );
753 if ( !missingGrids.empty() )
755 mInstallGridButton->setText( tr(
"Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
762 #if PROJ_VERSION_MAJOR>=6
764 mInstallGridButton->hide();
767 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
768 mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
771 #if PROJ_VERSION_MAJOR>=6
772 if ( newOp.proj != mPreviousOp.
proj && !mBlockSignals )
785 #if PROJ_VERSION_MAJOR>=6
792 loadAvailableOperations();
798 #if PROJ_VERSION_MAJOR>=6
805 loadAvailableOperations();
808 void QgsCoordinateOperationWidget::showSupersededToggled(
bool )
810 #if PROJ_VERSION_MAJOR>=6
817 loadAvailableOperations();
820 void QgsCoordinateOperationWidget::installGrid()
822 #if PROJ_VERSION_MAJOR>=6
823 int row = mCoordinateOperationTableWidget->currentRow();
824 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
828 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
829 if ( missingGrids.empty() )
832 const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
833 const QString packageName = missingGridPackagesNames.value( 0 );
834 const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
835 const QString gridUrl = missingGridUrls.value( 0 );
837 QString downloadMessage;
838 if ( !packageName.isEmpty() )
840 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 else if ( !gridUrl.isEmpty() )
844 downloadMessage = tr(
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
847 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 QgsInstallGridShiftFileDialog *dlg =
new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ),
this );
850 dlg->setAttribute( Qt::WA_DeleteOnClose );
851 dlg->setWindowTitle( tr(
"Install Grid File" ) );
852 dlg->setDescription( longMessage );
853 dlg->setDownloadMessage( downloadMessage );