QGIS API Documentation  3.27.0-Master (aef1b1ec20)
qgscrsdefinitionwidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscrsdefinitionwidget.cpp
3  ---------------------
4  begin : December 2021
5  copyright : (C) 2021 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgscrsdefinitionwidget.h"
18 #include "qgsprojutils.h"
19 
20 #include <QMessageBox>
21 #include <QRegularExpression>
22 #include <QRegularExpressionMatch>
23 #include <proj.h>
24 
26  : QWidget( parent )
27 {
28  setupUi( this );
29 
30  connect( mButtonCalculate, &QPushButton::clicked, this, &QgsCrsDefinitionWidget::pbnCalculate_clicked );
31  connect( mButtonCopyCRS, &QPushButton::clicked, this, &QgsCrsDefinitionWidget::pbnCopyCRS_clicked );
32  connect( mButtonValidate, &QPushButton::clicked, this, &QgsCrsDefinitionWidget::validateCurrent );
33 
34  mFormatComboBox->addItem( tr( "WKT (Recommended)" ), static_cast< int >( Qgis::CrsDefinitionFormat::Wkt ) );
35  mFormatComboBox->addItem( tr( "Proj String (Legacy — Not Recommended)" ), static_cast< int >( Qgis::CrsDefinitionFormat::Proj ) );
36  mFormatComboBox->setCurrentIndex( mFormatComboBox->findData( static_cast< int >( Qgis::CrsDefinitionFormat::Wkt ) ) );
37 
38  connect( mFormatComboBox, qOverload<int>( &QComboBox::currentIndexChanged ), this, &QgsCrsDefinitionWidget::formatChanged );
39  connect( mTextEditParameters, &QPlainTextEdit::textChanged, this, &QgsCrsDefinitionWidget::crsChanged );
40 }
41 
43 {
45  switch ( static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() ) )
46  {
47  case Qgis::CrsDefinitionFormat::Wkt:
48  crs = QgsCoordinateReferenceSystem::fromWkt( mTextEditParameters->toPlainText() );
49  break;
50 
51  case Qgis::CrsDefinitionFormat::Proj:
52  crs = QgsCoordinateReferenceSystem::fromProj( mTextEditParameters->toPlainText() );
53  break;
54  }
55 
57  return crs;
58 }
59 
61 {
62  setCrs( crs, crs.nativeFormat() );
63 }
64 
66 {
67  switch ( nativeFormat )
68  {
69  case Qgis::CrsDefinitionFormat::Wkt:
70  whileBlocking( mTextEditParameters )->setPlainText( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false ) );
71  break;
72  case Qgis::CrsDefinitionFormat::Proj:
73  whileBlocking( mTextEditParameters )->setPlainText( crs.toProj() );
74  break;
75  }
76 
77  whileBlocking( mFormatComboBox )->setCurrentIndex( mFormatComboBox->findData( static_cast< int >( nativeFormat ) ) );
78  emit crsChanged();
79 }
80 
82 {
83  return static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() );
84 }
85 
87 {
88  mFormatComboBox->setCurrentIndex( mFormatComboBox->findData( static_cast< int >( format ) ) );
89 }
90 
92 {
93  return mTextEditParameters->toPlainText();
94 }
95 
96 void QgsCrsDefinitionWidget::setDefinitionString( const QString &definition )
97 {
98  mTextEditParameters->setPlainText( definition );
99 }
100 
101 void QgsCrsDefinitionWidget::pbnCopyCRS_clicked()
102 {
103  std::unique_ptr< QgsProjectionSelectionDialog > selector = std::make_unique< QgsProjectionSelectionDialog >( this );
104  if ( selector->exec() )
105  {
106  const QgsCoordinateReferenceSystem srs = selector->crs();
107 
108  whileBlocking( mFormatComboBox )->setCurrentIndex( mFormatComboBox->findData( static_cast< int >( Qgis::CrsDefinitionFormat::Wkt ) ) );
109  mTextEditParameters->setPlainText( srs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true ) );
110  }
111 }
112 
113 static void proj_collecting_logger( void *user_data, int /*level*/, const char *message )
114 {
115  QStringList *dest = reinterpret_cast< QStringList * >( user_data );
116  QString messageString( message );
117  messageString.replace( QLatin1String( "internal_proj_create: " ), QString() );
118  dest->append( messageString );
119 }
120 
121 void QgsCrsDefinitionWidget::validateCurrent()
122 {
123  const QString projDef = mTextEditParameters->toPlainText();
124 
125  PJ_CONTEXT *context = proj_context_create();
126 
127  QStringList projErrors;
128  proj_log_func( context, &projErrors, proj_collecting_logger );
130 
131  switch ( static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() ) )
132  {
133  case Qgis::CrsDefinitionFormat::Wkt:
134  {
135  PROJ_STRING_LIST warnings = nullptr;
136  PROJ_STRING_LIST grammerErrors = nullptr;
137  crs.reset( proj_create_from_wkt( context, projDef.toUtf8().constData(), nullptr, &warnings, &grammerErrors ) );
138  QStringList warningStrings;
139  QStringList grammerStrings;
140  for ( auto iter = warnings; iter && *iter; ++iter )
141  warningStrings << QString( *iter );
142  for ( auto iter = grammerErrors; iter && *iter; ++iter )
143  grammerStrings << QString( *iter );
144  proj_string_list_destroy( warnings );
145  proj_string_list_destroy( grammerErrors );
146 
147  if ( crs )
148  {
149  QMessageBox::information( this, tr( "Custom Coordinate Reference System" ),
150  tr( "This WKT projection definition is valid." ) );
151  }
152  else
153  {
154  QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
155  tr( "This WKT projection definition is not valid:" ) + QStringLiteral( "\n\n" ) + warningStrings.join( '\n' ) + grammerStrings.join( '\n' ) );
156  }
157  break;
158  }
159 
160  case Qgis::CrsDefinitionFormat::Proj:
161  {
162  const QString projCrsString = projDef + ( projDef.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
163  crs.reset( proj_create( context, projCrsString.toUtf8().constData() ) );
164  if ( crs )
165  {
166  QMessageBox::information( this, tr( "Custom Coordinate Reference System" ),
167  tr( "This proj projection definition is valid." ) );
168  }
169  else
170  {
171  QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
172  tr( "This proj projection definition is not valid:" ) + QStringLiteral( "\n\n" ) + projErrors.join( '\n' ) );
173  }
174  break;
175  }
176  }
177 
178  // reset logger to terminal output
179  proj_log_func( context, nullptr, nullptr );
180  proj_context_destroy( context );
181  context = nullptr;
182 }
183 
184 void QgsCrsDefinitionWidget::formatChanged()
185 {
187  QString newFormatString;
188  switch ( static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() ) )
189  {
190  case Qgis::CrsDefinitionFormat::Proj:
191  {
192  crs.createFromWkt( multiLineWktToSingleLine( mTextEditParameters->toPlainText() ) );
193  if ( crs.isValid() )
194  newFormatString = crs.toProj();
195  break;
196  }
197 
198  case Qgis::CrsDefinitionFormat::Wkt:
199  {
200  PJ_CONTEXT *pjContext = QgsProjContext::get();
201  QString proj = mTextEditParameters->toPlainText();
202  proj.replace( QLatin1String( "+type=crs" ), QString() );
203  proj += QLatin1String( " +type=crs" );
204  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), proj.toUtf8().constData() ) );
205  if ( crs )
206  {
207  const QByteArray multiLineOption = QStringLiteral( "MULTILINE=YES" ).toLocal8Bit();
208  const char *const options[] = {multiLineOption.constData(), nullptr};
209  newFormatString = QString( proj_as_wkt( pjContext, crs.get(), PJ_WKT2_2019, options ) );
210  }
211  break;
212  }
213  }
214  if ( !newFormatString.isEmpty() )
215  mTextEditParameters->setPlainText( newFormatString );
216 }
217 
218 void QgsCrsDefinitionWidget::pbnCalculate_clicked()
219 {
220  // We must check the prj def is valid!
221  QString projDef = mTextEditParameters->toPlainText();
222 
223  // Get the WGS84 coordinates
224  bool okN, okE;
225  double latitude = mNorthWGS84Edit->text().toDouble( &okN );
226  double longitude = mEastWGS84Edit->text().toDouble( &okE );
227 
228  if ( !okN || !okE )
229  {
230  QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
231  tr( "Latitude and Longitude must be in decimal form." ) );
232  mProjectedXLabel->clear();
233  mProjectedYLabel->clear();
234  return;
235  }
236 
238  if ( static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() ) == Qgis::CrsDefinitionFormat::Proj )
239  {
240  projDef = projDef + ( projDef.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
241  target = QgsCoordinateReferenceSystem::fromProj( projDef );
242  }
243  else
244  {
245  target = QgsCoordinateReferenceSystem::fromWkt( projDef );
246  }
247 
248  if ( !target.isValid() )
249  {
250  QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
251  tr( "This CRS projection definition is not valid." ) );
252  mProjectedXLabel->clear();
253  mProjectedYLabel->clear();
254  return;
255  }
256 
257  const QgsCoordinateTransform transform( target.toGeographicCrs(), target, QgsCoordinateTransformContext() );
258  try
259  {
260  const QgsPointXY res = transform.transform( QgsPointXY( longitude, latitude ) );
261  const int precision = target.isGeographic() ? 7 : 4;
262  mProjectedXLabel->setText( QLocale().toString( res.x(), 'f', precision ) );
263  mProjectedYLabel->setText( QLocale().toString( res.y(), 'f', precision ) );
264  }
265  catch ( QgsCsException &e )
266  {
267  mProjectedXLabel->setText( tr( "Error" ) );
268  mProjectedYLabel->setText( tr( "Error" ) );
269  QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
270  e.what() );
271  }
272 }
273 
274 QString QgsCrsDefinitionWidget::multiLineWktToSingleLine( const QString &wkt )
275 {
276  QString res = wkt;
277  QRegularExpression re( QStringLiteral( "\\s*\\n\\s*" ) );
278  re.setPatternOptions( QRegularExpression::MultilineOption );
279  res.replace( re, QString() );
280  return res;
281 }
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:1706
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromWkt(const QString &wkt)
Sets this CRS using a WKT definition.
QString toProj() const
Returns a Proj string representation of this CRS.
Qgis::CrsDefinitionFormat nativeFormat() const
Returns the native format for the CRS definition.
void setNativeFormat(Qgis::CrsDefinitionFormat format)
Sets the native format for the CRS definition.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
Contains information about the context in which a coordinate transform is executed.
Class for doing transforms between two map coordinate systems.
void setDefinitionString(const QString &definition)
Sets the current definition string.
QString definitionString() const
Returns the current definition string.
void setFormat(Qgis::CrsDefinitionFormat format)
Sets the CRS format.
void crsChanged()
Emitted when the CRS defined in the widget is changed.
QgsCrsDefinitionWidget(QWidget *parent=nullptr)
Constructor for QgsCrsDefinitionWidget, with the specified parent widget.
Qgis::CrsDefinitionFormat format() const
Returns the selected CRS format.
QgsCoordinateReferenceSystem crs() const
Returns the current CRS as defined in the widget.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the current crs to display in the widget.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
QString what() const
Definition: qgsexception.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:59
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Definition: qgsprojutils.h:141
QgsSignalBlocker< Object > whileBlocking(Object *object)
Temporarily blocks signals from a QObject while calling a single method from the object.
Definition: qgis.h:2186
struct projCtx_t PJ_CONTEXT
const QgsCoordinateReferenceSystem & crs
int precision