mirror of
https://github.com/qgis/QGIS.git
synced 2025-11-22 00:14:55 -05:00
854 lines
31 KiB
C++
854 lines
31 KiB
C++
/***************************************************************************
|
|
qgscoordinateoperationwidget.cpp
|
|
---------------------------
|
|
begin : December 2019
|
|
copyright : (C) 2019 by Nyall Dawson
|
|
email : nyall dot dawson at gmail dot com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "qgscoordinateoperationwidget.h"
|
|
#include "qgscoordinatetransform.h"
|
|
#include "qgsprojectionselectiondialog.h"
|
|
#include "qgslogger.h"
|
|
#include "qgssettings.h"
|
|
#include "qgsproject.h"
|
|
#include "qgsguiutils.h"
|
|
#include "qgsgui.h"
|
|
#include "qgshelp.h"
|
|
#include "qgsinstallgridshiftdialog.h"
|
|
|
|
#include <QDir>
|
|
#include <QPushButton>
|
|
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
#include "qgsprojutils.h"
|
|
#include <proj.h>
|
|
#endif
|
|
|
|
QgsCoordinateOperationWidget::QgsCoordinateOperationWidget( QWidget *parent )
|
|
: QWidget( parent )
|
|
{
|
|
setupUi( this );
|
|
|
|
mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
|
|
mLabelSrcDescription->setOpenExternalLinks( true );
|
|
mInstallGridButton->hide();
|
|
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
connect( mInstallGridButton, &QPushButton::clicked, this, &QgsCoordinateOperationWidget::installGrid );
|
|
connect( mAllowFallbackCheckBox, &QCheckBox::toggled, this, [ = ]
|
|
{
|
|
if ( !mBlockSignals )
|
|
emit operationChanged();
|
|
} );
|
|
mCoordinateOperationTableWidget->setColumnCount( 3 );
|
|
#else
|
|
mCoordinateOperationTableWidget->setColumnCount( 2 );
|
|
#endif
|
|
|
|
QStringList headers;
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
headers << tr( "Transformation" ) << tr( "Accuracy (meters)" ) << tr( "Area of Use" );
|
|
#else
|
|
headers << tr( "Source Transform" ) << tr( "Destination Transform" ) ;
|
|
#endif
|
|
mCoordinateOperationTableWidget->setHorizontalHeaderLabels( headers );
|
|
|
|
#if PROJ_VERSION_MAJOR<6
|
|
mAreaCanvas->hide();
|
|
#endif
|
|
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
// proj 6 doesn't provide deprecated operations
|
|
mHideDeprecatedCheckBox->setVisible( false );
|
|
mShowSupersededCheckBox->setVisible( true );
|
|
mLabelDstDescription->hide();
|
|
#else
|
|
mShowSupersededCheckBox->setVisible( false );
|
|
mAllowFallbackCheckBox->setVisible( false );
|
|
QgsSettings settings;
|
|
mHideDeprecatedCheckBox->setChecked( settings.value( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), true ).toBool() );
|
|
#endif
|
|
|
|
connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged, this, [ = ] { loadAvailableOperations(); } );
|
|
connect( mShowSupersededCheckBox, &QCheckBox::toggled, this, &QgsCoordinateOperationWidget::showSupersededToggled );
|
|
connect( mCoordinateOperationTableWidget, &QTableWidget::currentItemChanged, this, &QgsCoordinateOperationWidget::tableCurrentItemChanged );
|
|
connect( mCoordinateOperationTableWidget, &QTableWidget::itemDoubleClicked, this, &QgsCoordinateOperationWidget::operationDoubleClicked );
|
|
|
|
mLabelSrcDescription->clear();
|
|
mLabelDstDescription->clear();
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setMapCanvas( QgsMapCanvas *canvas )
|
|
{
|
|
#if PROJ_VERSION_MAJOR<6
|
|
( void )canvas;
|
|
#else
|
|
if ( canvas )
|
|
{
|
|
// show canvas extent in preview widget
|
|
QPolygonF mainCanvasPoly = canvas->mapSettings().visiblePolygon();
|
|
QgsGeometry g = QgsGeometry::fromQPolygonF( mainCanvasPoly );
|
|
// close polygon
|
|
mainCanvasPoly << mainCanvasPoly.at( 0 );
|
|
if ( QgsProject::instance()->crs() !=
|
|
QgsCoordinateReferenceSystem::fromEpsgId( 4326 ) )
|
|
{
|
|
// reproject extent
|
|
QgsCoordinateTransform ct( QgsProject::instance()->crs(),
|
|
QgsCoordinateReferenceSystem::fromEpsgId( 4326 ), QgsProject::instance() );
|
|
ct.setBallparkTransformsAreAppropriate( true );
|
|
g = g.densifyByCount( 5 );
|
|
try
|
|
{
|
|
g.transform( ct );
|
|
}
|
|
catch ( QgsCsException & )
|
|
{
|
|
}
|
|
}
|
|
mAreaCanvas->setCanvasRect( g.boundingBox() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setShowMakeDefault( bool show )
|
|
{
|
|
mMakeDefaultCheckBox->setVisible( show );
|
|
}
|
|
|
|
bool QgsCoordinateOperationWidget::makeDefaultSelected() const
|
|
{
|
|
return mMakeDefaultCheckBox->isChecked();
|
|
}
|
|
|
|
bool QgsCoordinateOperationWidget::hasSelection() const
|
|
{
|
|
return !mCoordinateOperationTableWidget->selectedItems().isEmpty();
|
|
}
|
|
|
|
QList<QgsCoordinateOperationWidget::OperationDetails> QgsCoordinateOperationWidget::availableOperations() const
|
|
{
|
|
QList<QgsCoordinateOperationWidget::OperationDetails> res;
|
|
res.reserve( mDatumTransforms.size() );
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
for ( const QgsDatumTransform::TransformDetails &details : mDatumTransforms )
|
|
{
|
|
OperationDetails op;
|
|
op.proj = details.proj;
|
|
op.sourceTransformId = -1;
|
|
op.destinationTransformId = -1;
|
|
op.isAvailable = details.isAvailable;
|
|
res << op;
|
|
}
|
|
#else
|
|
for ( const QgsDatumTransform::TransformPair &details : mDatumTransforms )
|
|
{
|
|
OperationDetails op;
|
|
op.sourceTransformId = details.sourceTransformId;
|
|
op.destinationTransformId = details.destinationTransformId;
|
|
res << op;
|
|
}
|
|
#endif
|
|
return res;
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::loadAvailableOperations()
|
|
{
|
|
mCoordinateOperationTableWidget->setRowCount( 0 );
|
|
|
|
int row = 0;
|
|
int preferredInitialRow = -1;
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
|
|
{
|
|
std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
|
|
item->setData( ProjRole, transform.proj );
|
|
item->setData( AvailableRole, transform.isAvailable );
|
|
item->setFlags( item->flags() & ~Qt::ItemIsEditable );
|
|
|
|
QString name = transform.name;
|
|
if ( !transform.authority.isEmpty() && !transform.code.isEmpty() )
|
|
name += QStringLiteral( " %1 %2:%3" ).arg( QString( QChar( 0x2013 ) ), transform.authority, transform.code );
|
|
item->setText( name );
|
|
|
|
if ( row == 0 ) // highlight first (preferred) operation
|
|
{
|
|
QFont f = item->font();
|
|
f.setBold( true );
|
|
item->setFont( f );
|
|
item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
|
|
}
|
|
|
|
if ( !transform.isAvailable )
|
|
{
|
|
item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
|
|
}
|
|
|
|
if ( preferredInitialRow < 0 && transform.isAvailable )
|
|
{
|
|
// try to select a "preferred" entry by default
|
|
preferredInitialRow = row;
|
|
}
|
|
|
|
QString missingMessage;
|
|
if ( !transform.isAvailable )
|
|
{
|
|
QStringList gridMessages;
|
|
QStringList missingGrids;
|
|
QStringList missingGridPackages;
|
|
QStringList missingGridUrls;
|
|
|
|
for ( const QgsDatumTransform::GridDetails &grid : transform.grids )
|
|
{
|
|
if ( !grid.isAvailable )
|
|
{
|
|
missingGrids << grid.shortName;
|
|
missingGridPackages << grid.packageName;
|
|
missingGridUrls << grid.url;
|
|
QString m = tr( "This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.shortName );
|
|
if ( !grid.url.isEmpty() )
|
|
{
|
|
if ( !grid.packageName.isEmpty() )
|
|
{
|
|
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 );
|
|
}
|
|
else
|
|
{
|
|
m += ' ' + tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.url );
|
|
}
|
|
}
|
|
gridMessages << m;
|
|
}
|
|
}
|
|
|
|
item->setData( MissingGridsRole, missingGrids );
|
|
item->setData( MissingGridPackageNamesRole, missingGridPackages );
|
|
item->setData( MissingGridUrlsRole, missingGridUrls );
|
|
|
|
if ( gridMessages.count() > 1 )
|
|
{
|
|
for ( int k = 0; k < gridMessages.count(); ++k )
|
|
gridMessages[k] = QStringLiteral( "<li>%1</li>" ).arg( gridMessages.at( k ) );
|
|
|
|
missingMessage = QStringLiteral( "<ul>%1</ul" ).arg( gridMessages.join( QString() ) );
|
|
}
|
|
else if ( !gridMessages.empty() )
|
|
{
|
|
missingMessage = gridMessages.constFirst();
|
|
}
|
|
}
|
|
|
|
QStringList areasOfUse;
|
|
QStringList authorityCodes;
|
|
|
|
QStringList opText;
|
|
for ( const QgsDatumTransform::SingleOperationDetails &singleOpDetails : transform.operationDetails )
|
|
{
|
|
QString text;
|
|
if ( !singleOpDetails.scope.isEmpty() )
|
|
{
|
|
text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), formatScope( singleOpDetails.scope ) );
|
|
}
|
|
if ( !singleOpDetails.remarks.isEmpty() )
|
|
{
|
|
if ( !text.isEmpty() )
|
|
text += QStringLiteral( "<br>" );
|
|
text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), singleOpDetails.remarks );
|
|
}
|
|
if ( !singleOpDetails.areaOfUse.isEmpty() )
|
|
{
|
|
if ( !areasOfUse.contains( singleOpDetails.areaOfUse ) )
|
|
areasOfUse << singleOpDetails.areaOfUse;
|
|
}
|
|
if ( !singleOpDetails.authority.isEmpty() && !singleOpDetails.code.isEmpty() )
|
|
{
|
|
const QString identifier = QStringLiteral( "%1:%2" ).arg( singleOpDetails.authority, singleOpDetails.code );
|
|
if ( !authorityCodes.contains( identifier ) )
|
|
authorityCodes << identifier;
|
|
}
|
|
|
|
if ( !text.isEmpty() )
|
|
{
|
|
opText.append( text );
|
|
}
|
|
}
|
|
|
|
QString text;
|
|
if ( !transform.scope.isEmpty() )
|
|
{
|
|
text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Scope" ), transform.scope );
|
|
}
|
|
if ( !transform.remarks.isEmpty() )
|
|
{
|
|
if ( !text.isEmpty() )
|
|
text += QStringLiteral( "<br>" );
|
|
text += QStringLiteral( "<b>%1</b>: %2" ).arg( tr( "Remarks" ), transform.remarks );
|
|
}
|
|
if ( !text.isEmpty() )
|
|
{
|
|
opText.append( text );
|
|
}
|
|
|
|
if ( opText.count() > 1 )
|
|
{
|
|
for ( int k = 0; k < opText.count(); ++k )
|
|
opText[k] = QStringLiteral( "<li>%1</li>" ).arg( opText.at( k ) );
|
|
}
|
|
|
|
if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
|
|
areasOfUse << transform.areaOfUse;
|
|
item->setData( BoundsRole, transform.bounds );
|
|
|
|
const QString id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? QStringLiteral( "%1:%2" ).arg( transform.authority, transform.code ) : QString();
|
|
if ( !id.isEmpty() && !authorityCodes.contains( id ) )
|
|
authorityCodes << id;
|
|
|
|
const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
|
|
const QColor active = palette().color( QPalette::Active, QPalette::Text );
|
|
|
|
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 ) );
|
|
const QString toolTipString = QStringLiteral( "<b>%1</b>" ).arg( transform.name )
|
|
+ ( !opText.empty() ? ( opText.count() == 1 ? QStringLiteral( "<p>%1</p>" ).arg( opText.at( 0 ) ) : QStringLiteral( "<ul>%1</ul>" ).arg( opText.join( QString() ) ) ) : QString() )
|
|
+ ( !areasOfUse.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Area of use" ), areasOfUse.join( QStringLiteral( ", " ) ) ) : QString() )
|
|
+ ( !authorityCodes.empty() ? QStringLiteral( "<p><b>%1</b>: %2</p>" ).arg( tr( "Identifiers" ), authorityCodes.join( QStringLiteral( ", " ) ) ) : QString() )
|
|
+ ( !missingMessage.isEmpty() ? QStringLiteral( "<p><b style=\"color: red\">%1</b></p>" ).arg( missingMessage ) : QString() )
|
|
+ QStringLiteral( "<p><code style=\"color: %1\">%2</code></p>" ).arg( codeColor.name(), transform.proj );
|
|
|
|
item->setToolTip( toolTipString );
|
|
mCoordinateOperationTableWidget->setRowCount( row + 1 );
|
|
mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
|
|
|
|
item = qgis::make_unique< QTableWidgetItem >();
|
|
item->setFlags( item->flags() & ~Qt::ItemIsEditable );
|
|
item->setText( transform.accuracy >= 0 ? QString::number( transform.accuracy ) : tr( "Unknown" ) );
|
|
item->setToolTip( toolTipString );
|
|
if ( !transform.isAvailable )
|
|
{
|
|
item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
|
|
}
|
|
mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
|
|
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
// area of use column
|
|
item = qgis::make_unique< QTableWidgetItem >();
|
|
item->setFlags( item->flags() & ~Qt::ItemIsEditable );
|
|
item->setText( areasOfUse.join( QStringLiteral( ", " ) ) );
|
|
item->setToolTip( toolTipString );
|
|
if ( !transform.isAvailable )
|
|
{
|
|
item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
|
|
}
|
|
mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
|
|
#endif
|
|
|
|
row++;
|
|
}
|
|
#else
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
|
|
{
|
|
bool itemDisabled = false;
|
|
bool itemHidden = false;
|
|
|
|
if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
|
|
continue;
|
|
|
|
QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
|
|
QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
|
|
for ( int i = 0; i < 2; ++i )
|
|
{
|
|
std::unique_ptr< QTableWidgetItem > item = qgis::make_unique< QTableWidgetItem >();
|
|
int nr = i == 0 ? transform.sourceTransformId : transform.destinationTransformId;
|
|
item->setData( TransformIdRole, nr );
|
|
item->setFlags( item->flags() & ~Qt::ItemIsEditable );
|
|
|
|
item->setText( QgsDatumTransform::datumTransformToProj( nr ) );
|
|
|
|
//Describe datums in a tooltip
|
|
QgsDatumTransform::TransformInfo info = i == 0 ? srcInfo : destInfo;
|
|
if ( info.datumTransformId == -1 )
|
|
continue;
|
|
|
|
if ( info.deprecated )
|
|
{
|
|
itemHidden = mHideDeprecatedCheckBox->isChecked();
|
|
item->setForeground( QBrush( QColor( 255, 0, 0 ) ) );
|
|
}
|
|
|
|
if ( ( srcInfo.preferred && !srcInfo.deprecated ) || ( destInfo.preferred && !destInfo.deprecated ) )
|
|
{
|
|
QFont f = item->font();
|
|
f.setBold( true );
|
|
item->setFont( f );
|
|
item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
|
|
}
|
|
|
|
if ( info.preferred && !info.deprecated && preferredInitialRow < 0 )
|
|
{
|
|
// try to select a "preferred" entry by default
|
|
preferredInitialRow = row;
|
|
}
|
|
|
|
QString toolTipString;
|
|
if ( gridShiftTransformation( item->text() ) )
|
|
{
|
|
toolTipString.append( QStringLiteral( "<p><b>NTv2</b></p>" ) );
|
|
}
|
|
|
|
if ( info.epsgCode > 0 )
|
|
toolTipString.append( QStringLiteral( "<p><b>EPSG Transformations Code:</b> %1</p>" ).arg( info.epsgCode ) );
|
|
|
|
toolTipString.append( QStringLiteral( "<p><b>Source CRS:</b> %1</p><p><b>Destination CRS:</b> %2</p>" ).arg( info.sourceCrsDescription, info.destinationCrsDescription ) );
|
|
|
|
if ( !info.remarks.isEmpty() )
|
|
toolTipString.append( QStringLiteral( "<p><b>Remarks:</b> %1</p>" ).arg( info.remarks ) );
|
|
if ( !info.scope.isEmpty() )
|
|
toolTipString.append( QStringLiteral( "<p><b>Scope:</b> %1</p>" ).arg( info.scope ) );
|
|
if ( info.preferred )
|
|
toolTipString.append( "<p><b>Preferred transformation</b></p>" );
|
|
if ( info.deprecated )
|
|
toolTipString.append( "<p><b>Deprecated transformation</b></p>" );
|
|
|
|
item->setToolTip( toolTipString );
|
|
|
|
if ( gridShiftTransformation( item->text() ) && !testGridShiftFileAvailability( item.get() ) )
|
|
{
|
|
itemDisabled = true;
|
|
}
|
|
|
|
if ( !itemHidden )
|
|
{
|
|
if ( itemDisabled )
|
|
{
|
|
item->setFlags( Qt::NoItemFlags );
|
|
}
|
|
mCoordinateOperationTableWidget->setRowCount( row + 1 );
|
|
mCoordinateOperationTableWidget->setItem( row, i, item.release() );
|
|
}
|
|
}
|
|
row++;
|
|
}
|
|
Q_NOWARN_DEPRECATED_POP
|
|
#endif
|
|
|
|
if ( mCoordinateOperationTableWidget->currentRow() < 0 )
|
|
mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
|
|
|
|
mCoordinateOperationTableWidget->resizeColumnsToContents();
|
|
|
|
tableCurrentItemChanged( nullptr, nullptr );
|
|
}
|
|
|
|
QgsCoordinateOperationWidget::~QgsCoordinateOperationWidget()
|
|
{
|
|
QgsSettings settings;
|
|
settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/hideDeprecated" ), mHideDeprecatedCheckBox->isChecked() );
|
|
|
|
for ( int i = 0; i < 2; i++ )
|
|
{
|
|
settings.setValue( QStringLiteral( "Windows/DatumTransformDialog/columnWidths/%1" ).arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
|
|
}
|
|
}
|
|
|
|
QgsCoordinateOperationWidget::OperationDetails QgsCoordinateOperationWidget::defaultOperation() const
|
|
{
|
|
OperationDetails preferred;
|
|
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
// for proj 6, return the first available transform -- they are sorted by preference by proj already
|
|
for ( const QgsDatumTransform::TransformDetails &transform : qgis::as_const( mDatumTransforms ) )
|
|
{
|
|
if ( transform.isAvailable )
|
|
{
|
|
preferred.proj = transform.proj;
|
|
preferred.isAvailable = transform.isAvailable;
|
|
break;
|
|
}
|
|
}
|
|
return preferred;
|
|
#else
|
|
OperationDetails preferredNonDeprecated;
|
|
bool foundPreferredNonDeprecated = false;
|
|
bool foundPreferred = false;
|
|
OperationDetails nonDeprecated;
|
|
bool foundNonDeprecated = false;
|
|
OperationDetails fallback;
|
|
bool foundFallback = false;
|
|
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
for ( const QgsDatumTransform::TransformPair &transform : qgis::as_const( mDatumTransforms ) )
|
|
{
|
|
if ( transform.sourceTransformId == -1 && transform.destinationTransformId == -1 )
|
|
continue;
|
|
|
|
const QgsDatumTransform::TransformInfo srcInfo = QgsDatumTransform::datumTransformInfo( transform.sourceTransformId );
|
|
const QgsDatumTransform::TransformInfo destInfo = QgsDatumTransform::datumTransformInfo( transform.destinationTransformId );
|
|
if ( !foundPreferredNonDeprecated && ( ( srcInfo.preferred && !srcInfo.deprecated ) || transform.sourceTransformId == -1 )
|
|
&& ( ( destInfo.preferred && !destInfo.deprecated ) || transform.destinationTransformId == -1 ) )
|
|
{
|
|
preferredNonDeprecated.sourceTransformId = transform.sourceTransformId;
|
|
preferredNonDeprecated.destinationTransformId = transform.destinationTransformId;
|
|
foundPreferredNonDeprecated = true;
|
|
}
|
|
else if ( !foundPreferred && ( srcInfo.preferred || transform.sourceTransformId == -1 ) &&
|
|
( destInfo.preferred || transform.destinationTransformId == -1 ) )
|
|
{
|
|
preferred.sourceTransformId = transform.sourceTransformId;
|
|
preferred.destinationTransformId = transform.destinationTransformId;
|
|
foundPreferred = true;
|
|
}
|
|
else if ( !foundNonDeprecated && ( !srcInfo.deprecated || transform.sourceTransformId == -1 )
|
|
&& ( !destInfo.deprecated || transform.destinationTransformId == -1 ) )
|
|
{
|
|
nonDeprecated.sourceTransformId = transform.sourceTransformId;
|
|
nonDeprecated.destinationTransformId = transform.destinationTransformId;
|
|
foundNonDeprecated = true;
|
|
}
|
|
else if ( !foundFallback )
|
|
{
|
|
fallback.sourceTransformId = transform.sourceTransformId;
|
|
fallback.destinationTransformId = transform.destinationTransformId;
|
|
foundFallback = true;
|
|
}
|
|
}
|
|
Q_NOWARN_DEPRECATED_POP
|
|
if ( foundPreferredNonDeprecated )
|
|
return preferredNonDeprecated;
|
|
else if ( foundPreferred )
|
|
return preferred;
|
|
else if ( foundNonDeprecated )
|
|
return nonDeprecated;
|
|
else
|
|
return fallback;
|
|
#endif
|
|
}
|
|
|
|
QString QgsCoordinateOperationWidget::formatScope( const QString &s )
|
|
{
|
|
QString scope = s;
|
|
|
|
QRegularExpression reGNSS( QStringLiteral( "\\bGNSS\\b" ) );
|
|
scope.replace( reGNSS, QObject::tr( "GNSS (Global Navigation Satellite System)" ) );
|
|
|
|
QRegularExpression reCORS( QStringLiteral( "\\bCORS\\b" ) );
|
|
scope.replace( reCORS, QObject::tr( "CORS (Continually Operating Reference Station)" ) );
|
|
|
|
return scope;
|
|
}
|
|
|
|
QgsCoordinateOperationWidget::OperationDetails QgsCoordinateOperationWidget::selectedOperation() const
|
|
{
|
|
int row = mCoordinateOperationTableWidget->currentRow();
|
|
OperationDetails op;
|
|
|
|
if ( row >= 0 )
|
|
{
|
|
QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
|
|
op.sourceTransformId = srcItem ? srcItem->data( TransformIdRole ).toInt() : -1;
|
|
QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
|
|
op.destinationTransformId = destItem ? destItem->data( TransformIdRole ).toInt() : -1;
|
|
op.proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
|
|
op.isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() : true;
|
|
op.allowFallback = mAllowFallbackCheckBox->isChecked();
|
|
}
|
|
else
|
|
{
|
|
op.sourceTransformId = -1;
|
|
op.destinationTransformId = -1;
|
|
op.proj = QString();
|
|
}
|
|
return op;
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setSelectedOperation( const QgsCoordinateOperationWidget::OperationDetails &operation )
|
|
{
|
|
int prevRow = mCoordinateOperationTableWidget->currentRow();
|
|
mBlockSignals++;
|
|
for ( int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
|
|
{
|
|
QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
if ( srcItem && srcItem->data( ProjRole ).toString() == operation.proj )
|
|
{
|
|
mCoordinateOperationTableWidget->selectRow( row );
|
|
break;
|
|
}
|
|
#else
|
|
QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
|
|
|
|
// 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
|
|
if ( ( srcItem && destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
|
|
operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() )
|
|
|| ( srcItem && destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
|
|
operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() )
|
|
|| ( srcItem && !destItem && operation.sourceTransformId == srcItem->data( TransformIdRole ).toInt() &&
|
|
operation.destinationTransformId == -1 )
|
|
|| ( !srcItem && destItem && operation.destinationTransformId == destItem->data( TransformIdRole ).toInt() &&
|
|
operation.sourceTransformId == -1 )
|
|
|| ( srcItem && !destItem && operation.destinationTransformId == srcItem->data( TransformIdRole ).toInt() &&
|
|
operation.sourceTransformId == -1 )
|
|
|| ( !srcItem && destItem && operation.sourceTransformId == destItem->data( TransformIdRole ).toInt() &&
|
|
operation.destinationTransformId == -1 )
|
|
)
|
|
{
|
|
mCoordinateOperationTableWidget->selectRow( row );
|
|
break;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.allowFallback;
|
|
mAllowFallbackCheckBox->setChecked( operation.allowFallback );
|
|
mBlockSignals--;
|
|
|
|
if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
|
|
emit operationChanged();
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setSelectedOperationUsingContext( const QgsCoordinateTransformContext &context )
|
|
{
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
const QString op = context.calculateCoordinateOperation( mSourceCrs, mDestinationCrs );
|
|
if ( !op.isEmpty() )
|
|
{
|
|
OperationDetails deets;
|
|
deets.proj = op;
|
|
deets.allowFallback = context.allowFallbackTransform( mSourceCrs, mDestinationCrs );
|
|
setSelectedOperation( deets );
|
|
}
|
|
else
|
|
{
|
|
setSelectedOperation( defaultOperation() );
|
|
}
|
|
|
|
#else
|
|
if ( context.hasTransform( mSourceCrs, mDestinationCrs ) )
|
|
{
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
const QgsDatumTransform::TransformPair op = context.calculateDatumTransforms( mSourceCrs, mDestinationCrs );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
OperationDetails deets;
|
|
deets.sourceTransformId = op.sourceTransformId;
|
|
deets.destinationTransformId = op.destinationTransformId;
|
|
setSelectedOperation( deets );
|
|
}
|
|
else
|
|
{
|
|
setSelectedOperation( defaultOperation() );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setShowFallbackOption( bool visible )
|
|
{
|
|
mAllowFallbackCheckBox->setVisible( visible );
|
|
}
|
|
|
|
bool QgsCoordinateOperationWidget::gridShiftTransformation( const QString &itemText ) const
|
|
{
|
|
return !itemText.isEmpty() && !itemText.contains( QLatin1String( "towgs84" ), Qt::CaseInsensitive );
|
|
}
|
|
|
|
bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item ) const
|
|
{
|
|
if ( !item )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QString itemText = item->text();
|
|
if ( itemText.isEmpty() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
char *projLib = getenv( "PROJ_LIB" );
|
|
if ( !projLib ) //no information about installation directory
|
|
{
|
|
return true;
|
|
}
|
|
|
|
QStringList itemEqualSplit = itemText.split( '=' );
|
|
QString filename;
|
|
for ( int i = 1; i < itemEqualSplit.size(); ++i )
|
|
{
|
|
if ( i > 1 )
|
|
{
|
|
filename.append( '=' );
|
|
}
|
|
filename.append( itemEqualSplit.at( i ) );
|
|
}
|
|
|
|
QDir projDir( projLib );
|
|
if ( projDir.exists() )
|
|
{
|
|
//look if filename in directory
|
|
QStringList fileList = projDir.entryList();
|
|
QStringList::const_iterator fileIt = fileList.constBegin();
|
|
for ( ; fileIt != fileList.constEnd(); ++fileIt )
|
|
{
|
|
#if defined(Q_OS_WIN)
|
|
if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
|
|
#else
|
|
if ( fileIt->compare( filename ) == 0 )
|
|
#endif //Q_OS_WIN
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
item->setToolTip( tr( "File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
|
|
return false; //not found in PROJ_LIB directory
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
|
|
{
|
|
int row = mCoordinateOperationTableWidget->currentRow();
|
|
if ( row < 0 )
|
|
{
|
|
mLabelSrcDescription->clear();
|
|
mLabelDstDescription->clear();
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mAreaCanvas->hide();
|
|
mInstallGridButton->hide();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
|
|
mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
|
|
if ( srcItem )
|
|
{
|
|
// find area of intersection of operation, source and dest bounding boxes
|
|
// see https://github.com/OSGeo/PROJ/issues/1549 for justification
|
|
const QgsRectangle operationRect = srcItem->data( BoundsRole ).value< QgsRectangle >();
|
|
const QgsRectangle sourceRect = mSourceCrs.bounds();
|
|
const QgsRectangle destRect = mDestinationCrs.bounds();
|
|
QgsRectangle rect = operationRect.intersect( sourceRect );
|
|
rect = rect.intersect( destRect );
|
|
|
|
mAreaCanvas->setPreviewRect( rect );
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mAreaCanvas->show();
|
|
|
|
const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
|
|
mInstallGridButton->setVisible( !missingGrids.empty() );
|
|
if ( !missingGrids.empty() )
|
|
{
|
|
mInstallGridButton->setText( tr( "Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
mAreaCanvas->setPreviewRect( QgsRectangle() );
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mAreaCanvas->hide();
|
|
mInstallGridButton->hide();
|
|
#endif
|
|
}
|
|
QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
|
|
mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
|
|
}
|
|
OperationDetails newOp = selectedOperation();
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
if ( newOp.proj != mPreviousOp.proj && !mBlockSignals )
|
|
emit operationChanged();
|
|
#else
|
|
if ( newOp.sourceTransformId != mPreviousOp.sourceTransformId ||
|
|
newOp.destinationTransformId != mPreviousOp.destinationTransformId )
|
|
emit operationChanged();
|
|
#endif
|
|
mPreviousOp = newOp;
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setSourceCrs( const QgsCoordinateReferenceSystem &sourceCrs )
|
|
{
|
|
mSourceCrs = sourceCrs;
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
|
|
#else
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
#endif
|
|
loadAvailableOperations();
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::setDestinationCrs( const QgsCoordinateReferenceSystem &destinationCrs )
|
|
{
|
|
mDestinationCrs = destinationCrs;
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
|
|
#else
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
#endif
|
|
loadAvailableOperations();
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::showSupersededToggled( bool )
|
|
{
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
mDatumTransforms = QgsDatumTransform::operations( mSourceCrs, mDestinationCrs, mShowSupersededCheckBox->isChecked() );
|
|
#else
|
|
Q_NOWARN_DEPRECATED_PUSH
|
|
mDatumTransforms = QgsDatumTransform::datumTransformations( mSourceCrs, mDestinationCrs );
|
|
Q_NOWARN_DEPRECATED_POP
|
|
#endif
|
|
loadAvailableOperations();
|
|
}
|
|
|
|
void QgsCoordinateOperationWidget::installGrid()
|
|
{
|
|
#if PROJ_VERSION_MAJOR>=6
|
|
int row = mCoordinateOperationTableWidget->currentRow();
|
|
QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
|
|
if ( !srcItem )
|
|
return;
|
|
|
|
const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
|
|
if ( missingGrids.empty() )
|
|
return;
|
|
|
|
const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
|
|
const QString packageName = missingGridPackagesNames.value( 0 );
|
|
const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
|
|
const QString gridUrl = missingGridUrls.value( 0 );
|
|
|
|
QString downloadMessage;
|
|
if ( !packageName.isEmpty() )
|
|
{
|
|
downloadMessage = tr( "This grid is part of the “<i>%1</i>” package, available for download from <a href=\"%2\">%2</a>." ).arg( packageName, gridUrl );
|
|
}
|
|
else if ( !gridUrl.isEmpty() )
|
|
{
|
|
downloadMessage = tr( "This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
|
|
}
|
|
|
|
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 ) );
|
|
|
|
QgsInstallGridShiftFileDialog *dlg = new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ), this );
|
|
dlg->setAttribute( Qt::WA_DeleteOnClose );
|
|
dlg->setWindowTitle( tr( "Install Grid File" ) );
|
|
dlg->setDescription( longMessage );
|
|
dlg->setDownloadMessage( downloadMessage );
|
|
dlg->exec();
|
|
|
|
#endif
|
|
}
|