QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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
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 {
48 crs = QgsCoordinateReferenceSystem::fromWkt( mTextEditParameters->toPlainText() );
49 break;
50
52 crs = QgsCoordinateReferenceSystem::fromProj( mTextEditParameters->toPlainText() );
53 break;
54 }
55
57 return crs;
58}
59
61{
63}
64
66{
67 switch ( nativeFormat )
68 {
70 whileBlocking( mTextEditParameters )->setPlainText( crs.toWkt( Qgis::CrsWktVariant::Preferred, false ) );
71 break;
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
96void QgsCrsDefinitionWidget::setDefinitionString( const QString &definition )
97{
98 mTextEditParameters->setPlainText( definition );
99}
100
101void 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( Qgis::CrsWktVariant::Preferred, true ) );
110 }
111}
112
113static 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
121void 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 {
134 {
135 PROJ_STRING_LIST warnings = nullptr;
136 PROJ_STRING_LIST grammarErrors = nullptr;
137 crs.reset( proj_create_from_wkt( context, projDef.toUtf8().constData(), nullptr, &warnings, &grammarErrors ) );
138 QStringList warningStrings;
139 QStringList grammarStrings;
140 for ( auto iter = warnings; iter && *iter; ++iter )
141 warningStrings << QString( *iter );
142 for ( auto iter = grammarErrors; iter && *iter; ++iter )
143 grammarStrings << QString( *iter );
144 proj_string_list_destroy( warnings );
145 proj_string_list_destroy( grammarErrors );
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' ) + grammarStrings.join( '\n' ) );
156 }
157 break;
158 }
159
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
184void QgsCrsDefinitionWidget::formatChanged()
185{
187 QString newFormatString;
188 switch ( static_cast< Qgis::CrsDefinitionFormat >( mFormatComboBox->currentData().toInt() ) )
189 {
191 {
192 crs.createFromWkt( multiLineWktToSingleLine( mTextEditParameters->toPlainText() ) );
193 if ( crs.isValid() )
194 newFormatString = crs.toProj();
195 break;
196 }
197
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
218void 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
258 try
259 {
260 if ( target.celestialBodyName() == QLatin1String( "Earth" ) )
261 {
262 source = QgsCoordinateReferenceSystem( "EPSG:4326" );
263 }
264 else
265 {
266 source = target.toGeographicCrs();
267 }
268 }
269 catch ( QgsNotSupportedException & )
270 {
271 source = target.toGeographicCrs();
272 }
273
274 const QgsCoordinateTransform transform( source, target, QgsCoordinateTransformContext() );
275 try
276 {
277 const QgsPointXY res = transform.transform( QgsPointXY( longitude, latitude ) );
278 const int precision = target.isGeographic() ? 7 : 4;
279 mProjectedXLabel->setText( QLocale().toString( res.x(), 'f', precision ) );
280 mProjectedYLabel->setText( QLocale().toString( res.y(), 'f', precision ) );
281 }
282 catch ( QgsCsException &e )
283 {
284 mProjectedXLabel->setText( tr( "Error" ) );
285 mProjectedYLabel->setText( tr( "Error" ) );
286 QMessageBox::warning( this, tr( "Custom Coordinate Reference System" ),
287 e.what() );
288 }
289}
290
291QString QgsCrsDefinitionWidget::multiLineWktToSingleLine( const QString &wkt )
292{
293 QString res = wkt;
294 const thread_local QRegularExpression re( QStringLiteral( "\\s*\\n\\s*" ), QRegularExpression::MultilineOption );
295 res.replace( re, QString() );
296 return res;
297}
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:3167
@ Wkt
WKT format (always recommended over proj string format)
@ Proj
Proj string format.
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
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.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QString celestialBodyName() const
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
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:67
QString what() const
Definition: qgsexception.h:49
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:118
A class to represent a 2D point.
Definition: qgspointxy.h:60
double y
Definition: qgspointxy.h:64
Q_GADGET double x
Definition: qgspointxy.h:63
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:5111
struct projCtx_t PJ_CONTEXT
const QgsCoordinateReferenceSystem & crs
int precision