35#include <QRegularExpression>
38#include "moc_qgscoordinateoperationwidget.cpp"
40using namespace Qt::StringLiterals;
47 mLabelSrcDescription->setTextInteractionFlags( Qt::TextBrowserInteraction );
48 mLabelSrcDescription->setOpenExternalLinks(
true );
49 mInstallGridButton->hide();
51 connect( mInstallGridButton, &QPushButton::clicked,
this, &QgsCoordinateOperationWidget::installGrid );
52 connect( mAllowFallbackCheckBox, &QCheckBox::toggled,
this, [
this] {
56 mCoordinateOperationTableWidget->setColumnCount( 3 );
59 headers << tr(
"Transformation" ) << tr(
"Accuracy (meters)" ) << tr(
"Area of Use" );
60 mCoordinateOperationTableWidget->setHorizontalHeaderLabels( headers );
62 mHideDeprecatedCheckBox->setVisible(
false );
63 mShowSupersededCheckBox->setVisible(
true );
64 mLabelDstDescription->hide();
66 connect( mHideDeprecatedCheckBox, &QCheckBox::stateChanged,
this, [
this] { loadAvailableOperations(); } );
67 connect( mShowSupersededCheckBox, &QCheckBox::toggled,
this, &QgsCoordinateOperationWidget::showSupersededToggled );
68 connect( mCoordinateOperationTableWidget, &QTableWidget::currentItemChanged,
this, &QgsCoordinateOperationWidget::tableCurrentItemChanged );
71 mLabelSrcDescription->clear();
72 mLabelDstDescription->clear();
83 mainCanvasPoly << mainCanvasPoly.at( 0 );
104 mMakeDefaultCheckBox->setVisible( show );
109 return mMakeDefaultCheckBox->isChecked();
114 return !mCoordinateOperationTableWidget->selectedItems().isEmpty();
119 QList<QgsCoordinateOperationWidget::OperationDetails> res;
120 res.reserve( mDatumTransforms.size() );
124 op.
proj = details.proj;
133void QgsCoordinateOperationWidget::loadAvailableOperations()
135 mCoordinateOperationTableWidget->setRowCount( 0 );
138 int preferredInitialRow = -1;
142 auto item = std::make_unique<QTableWidgetItem>();
143 item->setData( ProjRole, transform.proj );
144 item->setData( AvailableRole, transform.isAvailable );
145 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
147 QString name = transform.name;
148 if ( !transform.authority.isEmpty() && !transform.code.isEmpty() )
149 name += u
" %1 %2:%3"_s.arg( QString( QChar( 0x2013 ) ), transform.authority, transform.code );
150 item->setText( name );
154 QFont f = item->font();
157 item->setForeground( QBrush( QColor( 0, 120, 0 ) ) );
160 if ( !transform.isAvailable )
162 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
165 if ( preferredInitialRow < 0 && transform.isAvailable )
168 preferredInitialRow = row;
171 QString missingMessage;
172 if ( !transform.isAvailable )
174 QStringList gridMessages;
175 QStringList missingGrids;
176 QStringList missingGridPackages;
177 QStringList missingGridUrls;
179 for (
const QgsDatumTransform::GridDetails &grid : transform.grids )
185 missingGridUrls << grid.
url;
186 QString m = tr(
"This transformation requires the grid file “%1”, which is not available for use on the system." ).arg( grid.
shortName );
187 if ( !grid.
url.isEmpty() )
191 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 );
195 m +=
' ' + tr(
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( grid.
url );
202 item->setData( MissingGridsRole, missingGrids );
203 item->setData( MissingGridPackageNamesRole, missingGridPackages );
204 item->setData( MissingGridUrlsRole, missingGridUrls );
206 if ( gridMessages.count() > 1 )
208 for (
int k = 0; k < gridMessages.count(); ++k )
209 gridMessages[k] = u
"<li>%1</li>"_s.arg( gridMessages.at( k ) );
211 missingMessage = u
"<ul>%1</ul"_s.arg( gridMessages.join( QString() ) );
213 else if ( !gridMessages.empty() )
215 missingMessage = gridMessages.constFirst();
219 QStringList areasOfUse;
220 QStringList authorityCodes;
223 QString lastSingleOpScope;
224 QString lastSingleOpRemarks;
225 for (
const QgsDatumTransform::SingleOperationDetails &singleOpDetails : transform.operationDetails )
228 if ( !singleOpDetails.
scope.isEmpty() )
230 text += u
"<b>%1</b>: %2"_s.arg( tr(
"Scope" ), formatScope( singleOpDetails.
scope ) );
231 lastSingleOpScope = singleOpDetails.
scope;
233 if ( !singleOpDetails.
remarks.isEmpty() )
235 if ( !text.isEmpty() )
237 text += u
"<b>%1</b>: %2"_s.arg( tr(
"Remarks" ), singleOpDetails.
remarks );
238 lastSingleOpRemarks = singleOpDetails.
remarks;
240 if ( !singleOpDetails.
areaOfUse.isEmpty() )
242 if ( !areasOfUse.contains( singleOpDetails.
areaOfUse ) )
245 if ( !singleOpDetails.
authority.isEmpty() && !singleOpDetails.
code.isEmpty() )
247 const QString identifier = u
"%1:%2"_s.arg( singleOpDetails.
authority, singleOpDetails.
code );
248 if ( !authorityCodes.contains( identifier ) )
249 authorityCodes << identifier;
252 if ( !text.isEmpty() )
254 opText.append( text );
259 if ( !transform.scope.isEmpty() && transform.scope != lastSingleOpScope )
261 text += u
"<b>%1</b>: %2"_s.arg( tr(
"Scope" ), transform.scope );
263 if ( !transform.remarks.isEmpty() && transform.remarks != lastSingleOpRemarks )
265 if ( !text.isEmpty() )
267 text += u
"<b>%1</b>: %2"_s.arg( tr(
"Remarks" ), transform.remarks );
269 if ( !text.isEmpty() )
271 opText.append( text );
274 if ( opText.count() > 1 )
276 for (
int k = 0; k < opText.count(); ++k )
277 opText[k] = u
"<li>%1</li>"_s.arg( opText.at( k ) );
280 if ( !transform.areaOfUse.isEmpty() && !areasOfUse.contains( transform.areaOfUse ) )
281 areasOfUse << transform.areaOfUse;
282 item->setData( BoundsRole, transform.bounds );
284 const QString
id = !transform.authority.isEmpty() && !transform.code.isEmpty() ? u
"%1:%2"_s.arg( transform.authority, transform.code ) : QString();
285 if ( !
id.isEmpty() && !authorityCodes.contains(
id ) )
286 authorityCodes << id;
288 const QColor disabled = palette().color( QPalette::Disabled, QPalette::Text );
289 const QColor active = palette().color( QPalette::Active, QPalette::Text );
292 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 ) );
293 const QString toolTipString = u
"<b>%1</b>"_s.arg( transform.name )
294 + ( !opText.empty() ? ( opText.count() == 1 ? u
"<p>%1</p>"_s.arg( opText.at( 0 ) ) : u
"<ul>%1</ul>"_s.arg( opText.join( QString() ) ) ) : QString() )
295 + ( !areasOfUse.empty() ? u
"<p><b>%1</b>: %2</p>"_s.arg( tr(
"Area of use" ), areasOfUse.join(
", "_L1 ) ) : QString() )
296 + ( !authorityCodes.empty() ? u
"<p><b>%1</b>: %2</p>"_s.arg( tr(
"Identifiers" ), authorityCodes.join(
", "_L1 ) ) : QString() )
297 + ( !missingMessage.isEmpty() ? u
"<p><b style=\"color: red\">%1</b></p>"_s.arg( missingMessage ) : QString() )
298 + u
"<p><code style=\"color: %1\">%2</code></p>"_s.arg( codeColor.name(), transform.proj );
300 item->setToolTip( toolTipString );
301 mCoordinateOperationTableWidget->setRowCount( row + 1 );
302 mCoordinateOperationTableWidget->setItem( row, 0, item.release() );
304 item = std::make_unique<QTableWidgetItem>();
305 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
306 item->setText( transform.accuracy >= 0 ? QLocale().toString( transform.accuracy ) : tr(
"Unknown" ) );
307 item->setToolTip( toolTipString );
308 if ( !transform.isAvailable )
310 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
312 mCoordinateOperationTableWidget->setItem( row, 1, item.release() );
315 item = std::make_unique<QTableWidgetItem>();
316 item->setFlags( item->flags() & ~Qt::ItemIsEditable );
317 item->setText( areasOfUse.join(
", "_L1 ) );
318 item->setToolTip( toolTipString );
319 if ( !transform.isAvailable )
321 item->setForeground( QBrush( palette().color( QPalette::Disabled, QPalette::Text ) ) );
323 mCoordinateOperationTableWidget->setItem( row, 2, item.release() );
328 if ( mCoordinateOperationTableWidget->currentRow() < 0 )
329 mCoordinateOperationTableWidget->selectRow( preferredInitialRow >= 0 ? preferredInitialRow : 0 );
331 mCoordinateOperationTableWidget->resizeColumnsToContents();
333 tableCurrentItemChanged(
nullptr,
nullptr );
339 settings.
setValue( u
"Windows/DatumTransformDialog/hideDeprecated"_s, mHideDeprecatedCheckBox->isChecked() );
341 for (
int i = 0; i < 2; i++ )
343 settings.
setValue( u
"Windows/DatumTransformDialog/columnWidths/%1"_s.arg( i ), mCoordinateOperationTableWidget->columnWidth( i ) );
354 if ( transform.isAvailable )
356 preferred.
proj = transform.proj;
364QString QgsCoordinateOperationWidget::formatScope(
const QString &s )
368 const thread_local QRegularExpression reGNSS( u
"\\bGNSS\\b"_s );
369 scope.replace( reGNSS, QObject::tr(
"GNSS (Global Navigation Satellite System)" ) );
371 const thread_local QRegularExpression reCORS( u
"\\bCORS\\b"_s );
372 scope.replace( reCORS, QObject::tr(
"CORS (Continually Operating Reference Station)" ) );
379 int row = mCoordinateOperationTableWidget->currentRow();
384 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
386 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
388 op.
proj = srcItem ? srcItem->data( ProjRole ).toString() : QString();
389 op.
isAvailable = srcItem ? srcItem->data( AvailableRole ).toBool() :
true;
403 int prevRow = mCoordinateOperationTableWidget->currentRow();
405 for (
int row = 0; row < mCoordinateOperationTableWidget->rowCount(); ++row )
407 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
408 if ( srcItem && srcItem->data( ProjRole ).toString() == operation.
proj )
410 mCoordinateOperationTableWidget->selectRow( row );
415 bool fallbackChanged = mAllowFallbackCheckBox->isChecked() != operation.
allowFallback;
416 mAllowFallbackCheckBox->setChecked( operation.
allowFallback );
419 if ( mCoordinateOperationTableWidget->currentRow() != prevRow || fallbackChanged )
441 mAllowFallbackCheckBox->setVisible( visible );
444bool QgsCoordinateOperationWidget::gridShiftTransformation(
const QString &itemText )
const
446 return !itemText.isEmpty() && !itemText.contains(
"towgs84"_L1, Qt::CaseInsensitive );
449bool QgsCoordinateOperationWidget::testGridShiftFileAvailability( QTableWidgetItem *item )
const
456 QString itemText = item->text();
457 if ( itemText.isEmpty() )
462 char *projLib = getenv(
"PROJ_LIB" );
468 QStringList itemEqualSplit = itemText.split(
'=' );
470 for (
int i = 1; i < itemEqualSplit.size(); ++i )
474 filename.append(
'=' );
476 filename.append( itemEqualSplit.at( i ) );
479 QDir projDir( projLib );
480 if ( projDir.exists() )
483 QStringList fileList = projDir.entryList();
484 QStringList::const_iterator fileIt = fileList.constBegin();
485 for ( ; fileIt != fileList.constEnd(); ++fileIt )
487#if defined( Q_OS_WIN )
488 if ( fileIt->compare( filename, Qt::CaseInsensitive ) == 0 )
490 if ( fileIt->compare( filename ) == 0 )
496 item->setToolTip( tr(
"File '%1' not found in directory '%2'" ).arg( filename, projDir.absolutePath() ) );
502void QgsCoordinateOperationWidget::tableCurrentItemChanged( QTableWidgetItem *, QTableWidgetItem * )
504 int row = mCoordinateOperationTableWidget->currentRow();
507 mLabelSrcDescription->clear();
508 mLabelDstDescription->clear();
510 mInstallGridButton->hide();
514 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
515 mLabelSrcDescription->setText( srcItem ? srcItem->toolTip() : QString() );
520 const QgsRectangle operationRect = srcItem->data( BoundsRole ).value<QgsRectangle>();
521 const QgsRectangle sourceRect = mSourceCrs.bounds();
522 const QgsRectangle destRect = mDestinationCrs.bounds();
523 QgsRectangle rect = operationRect.
intersect( sourceRect );
526 mAreaCanvas->setPreviewRect( rect );
529 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
530 mInstallGridButton->setVisible( !missingGrids.empty() );
531 if ( !missingGrids.empty() )
533 mInstallGridButton->setText( tr(
"Install “%1” Grid…" ).arg( missingGrids.at( 0 ) ) );
538 mAreaCanvas->setPreviewRect( QgsRectangle() );
540 mInstallGridButton->hide();
542 QTableWidgetItem *destItem = mCoordinateOperationTableWidget->item( row, 1 );
543 mLabelDstDescription->setText( destItem ? destItem->toolTip() : QString() );
546 if ( newOp.proj != mPreviousOp.proj && !mBlockSignals )
555 loadAvailableOperations();
562 loadAvailableOperations();
565void QgsCoordinateOperationWidget::showSupersededToggled(
bool )
568 loadAvailableOperations();
571void QgsCoordinateOperationWidget::installGrid()
573 int row = mCoordinateOperationTableWidget->currentRow();
574 QTableWidgetItem *srcItem = mCoordinateOperationTableWidget->item( row, 0 );
578 const QStringList missingGrids = srcItem->data( MissingGridsRole ).toStringList();
579 if ( missingGrids.empty() )
582 const QStringList missingGridPackagesNames = srcItem->data( MissingGridPackageNamesRole ).toStringList();
583 const QString packageName = missingGridPackagesNames.value( 0 );
584 const QStringList missingGridUrls = srcItem->data( MissingGridUrlsRole ).toStringList();
585 const QString gridUrl = missingGridUrls.value( 0 );
587 QString downloadMessage;
588 if ( !packageName.isEmpty() )
590 downloadMessage = tr(
"This grid is part of the “<i>%1</i>” package, available for download from <a href=\"%2\">%2</a>." ).arg( packageName, gridUrl );
592 else if ( !gridUrl.isEmpty() )
594 downloadMessage = tr(
"This grid is available for download from <a href=\"%1\">%1</a>." ).arg( gridUrl );
597 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 ) );
599 QgsInstallGridShiftFileDialog *dlg =
new QgsInstallGridShiftFileDialog( missingGrids.at( 0 ),
this );
600 dlg->setAttribute( Qt::WA_DeleteOnClose );
601 dlg->setWindowTitle( tr(
"Install Grid File" ) );
602 dlg->setDescription( longMessage );
603 dlg->setDownloadMessage( downloadMessage );
Represents a coordinate reference system (CRS).
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
bool isEarthCrs() const
Returns true if the CRS is associated with the Earth.
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.
QgsCoordinateReferenceSystem crs
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.