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, [=] {
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, [=] { 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 std::unique_ptr<QTableWidgetItem> 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 );
This class 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.
This class is a composition of two QSettings instances:
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
const QgsCoordinateReferenceSystem & crs