QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsdatumtransform.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdatumtransform.cpp
3  ------------------------
4  begin : Dec 2017
5  copyright : (C) 2017 Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include "qgsdatumtransform.h"
19 #include "qgsapplication.h"
20 #include "qgssqliteutils.h"
21 #include <sqlite3.h>
22 
23 #if PROJ_VERSION_MAJOR>=6
24 #include "qgsprojutils.h"
25 #include <proj.h>
26 #endif
27 
28 QList<QgsDatumTransform::TransformDetails> QgsDatumTransform::operations( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination )
29 {
30  QList< QgsDatumTransform::TransformDetails > res;
31 #if PROJ_VERSION_MAJOR<6
32  Q_UNUSED( source )
33  Q_UNUSED( destination )
34 #else
35  if ( !source.projObject() || !destination.projObject() )
36  return res;
37 
38  PJ_CONTEXT *pjContext = QgsProjContext::get();
39 
40  PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( pjContext, nullptr );
41 
42  // We want to return ALL grids, not just those available for use
43  proj_operation_factory_context_set_grid_availability_use( pjContext, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
44 
45  // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
46  proj_operation_factory_context_set_spatial_criterion( pjContext, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
47 
48  if ( PJ_OBJ_LIST *ops = proj_create_operations( pjContext, source.projObject(), destination.projObject(), operationContext ) )
49  {
50  int count = proj_list_get_count( ops );
51  for ( int i = 0; i < count; ++i )
52  {
53  QgsProjUtils::proj_pj_unique_ptr op( proj_list_get( pjContext, ops, i ) );
54  if ( !op )
55  continue;
56 
57  res.push_back( transformDetailsFromPj( op.get() ) );
58  }
59  proj_list_destroy( ops );
60  }
61  proj_operation_factory_context_destroy( operationContext );
62 #endif
63  return res;
64 }
65 
66 QList< QgsDatumTransform::TransformPair > QgsDatumTransform::datumTransformations( const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS )
67 {
68  QList< QgsDatumTransform::TransformPair > transformations;
69 
70  QString srcGeoId = srcCRS.geographicCrsAuthId();
71  QString destGeoId = destCRS.geographicCrsAuthId();
72 
73  if ( srcGeoId.isEmpty() || destGeoId.isEmpty() )
74  {
75  return transformations;
76  }
77 
78  QStringList srcSplit = srcGeoId.split( ':' );
79  QStringList destSplit = destGeoId.split( ':' );
80 
81  if ( srcSplit.size() < 2 || destSplit.size() < 2 )
82  {
83  return transformations;
84  }
85 
86  int srcAuthCode = srcSplit.at( 1 ).toInt();
87  int destAuthCode = destSplit.at( 1 ).toInt();
88 
89  if ( srcAuthCode == destAuthCode )
90  {
91  return transformations; //crs have the same datum
92  }
93 
94  QList<int> directTransforms;
95  searchDatumTransform( QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE source_crs_code=%1 AND target_crs_code=%2 ORDER BY deprecated ASC,preferred DESC" ).arg( srcAuthCode ).arg( destAuthCode ),
96  directTransforms );
97  QList<int> reverseDirectTransforms;
98  searchDatumTransform( QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE source_crs_code = %1 AND target_crs_code=%2 ORDER BY deprecated ASC,preferred DESC" ).arg( destAuthCode ).arg( srcAuthCode ),
99  reverseDirectTransforms );
100  QList<int> srcToWgs84;
101  searchDatumTransform( QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE (source_crs_code=%1 AND target_crs_code=%2) OR (source_crs_code=%2 AND target_crs_code=%1) ORDER BY deprecated ASC,preferred DESC" ).arg( srcAuthCode ).arg( 4326 ),
102  srcToWgs84 );
103  QList<int> destToWgs84;
104  searchDatumTransform( QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE (source_crs_code=%1 AND target_crs_code=%2) OR (source_crs_code=%2 AND target_crs_code=%1) ORDER BY deprecated ASC,preferred DESC" ).arg( destAuthCode ).arg( 4326 ),
105  destToWgs84 );
106 
107  //add direct datum transformations
108  for ( int transform : qgis::as_const( directTransforms ) )
109  {
110  transformations.push_back( QgsDatumTransform::TransformPair( transform, -1 ) );
111  }
112 
113  //add direct datum transformations
114  for ( int transform : qgis::as_const( reverseDirectTransforms ) )
115  {
116  transformations.push_back( QgsDatumTransform::TransformPair( -1, transform ) );
117  }
118 
119  for ( int srcTransform : qgis::as_const( srcToWgs84 ) )
120  {
121  for ( int destTransform : qgis::as_const( destToWgs84 ) )
122  {
123  transformations.push_back( QgsDatumTransform::TransformPair( srcTransform, destTransform ) );
124  }
125  }
126 
127  return transformations;
128 }
129 
130 void QgsDatumTransform::searchDatumTransform( const QString &sql, QList< int > &transforms )
131 {
133  int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
134  if ( openResult != SQLITE_OK )
135  {
136  return;
137  }
138 
140  int prepareRes;
141  statement = database.prepare( sql, prepareRes );
142  if ( prepareRes != SQLITE_OK )
143  {
144  return;
145  }
146 
147  QString cOpCode;
148  while ( statement.step() == SQLITE_ROW )
149  {
150  cOpCode = statement.columnAsText( 0 );
151  transforms.push_back( cOpCode.toInt() );
152  }
153 }
154 
155 QString QgsDatumTransform::datumTransformToProj( int datumTransform )
156 {
157  QString transformString;
158 
160  int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
161  if ( openResult != SQLITE_OK )
162  {
163  return transformString;
164  }
165 
167  QString sql = QStringLiteral( "SELECT coord_op_method_code,p1,p2,p3,p4,p5,p6,p7 FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( datumTransform );
168  int prepareRes;
169  statement = database.prepare( sql, prepareRes );
170  if ( prepareRes != SQLITE_OK )
171  {
172  return transformString;
173  }
174 
175  if ( statement.step() == SQLITE_ROW )
176  {
177  //coord_op_methode_code
178  int methodCode = statement.columnAsInt64( 0 );
179  if ( methodCode == 9615 ) //ntv2
180  {
181  transformString = "+nadgrids=" + statement.columnAsText( 1 );
182  }
183  else if ( methodCode == 9603 || methodCode == 9606 || methodCode == 9607 )
184  {
185  transformString += QLatin1String( "+towgs84=" );
186  double p1 = statement.columnAsDouble( 1 );
187  double p2 = statement.columnAsDouble( 2 );
188  double p3 = statement.columnAsDouble( 3 );
189  double p4 = statement.columnAsDouble( 4 );
190  double p5 = statement.columnAsDouble( 5 );
191  double p6 = statement.columnAsDouble( 6 );
192  double p7 = statement.columnAsDouble( 7 );
193  if ( methodCode == 9603 ) //3 parameter transformation
194  {
195  transformString += QStringLiteral( "%1,%2,%3" ).arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ) );
196  }
197  else //7 parameter transformation
198  {
199  transformString += QStringLiteral( "%1,%2,%3,%4,%5,%6,%7" ).arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ), QString::number( p4 ), QString::number( p5 ), QString::number( p6 ), QString::number( p7 ) );
200  }
201  }
202  }
203 
204  return transformString;
205 }
206 
208 {
210  int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
211  if ( openResult != SQLITE_OK )
212  {
213  return -1;
214  }
215 
217  QString sql = QStringLiteral( "SELECT coord_op_method_code,p1,p2,p3,p4,p5,p6,p7,coord_op_code FROM tbl_datum_transform" );
218  int prepareRes;
219  statement = database.prepare( sql, prepareRes );
220  if ( prepareRes != SQLITE_OK )
221  {
222  return -1;
223  }
224 
225  while ( statement.step() == SQLITE_ROW )
226  {
227  QString transformString;
228  //coord_op_methode_code
229  int methodCode = statement.columnAsInt64( 0 );
230  if ( methodCode == 9615 ) //ntv2
231  {
232  transformString = "+nadgrids=" + statement.columnAsText( 1 );
233  }
234  else if ( methodCode == 9603 || methodCode == 9606 || methodCode == 9607 )
235  {
236  transformString += QLatin1String( "+towgs84=" );
237  double p1 = statement.columnAsDouble( 1 );
238  double p2 = statement.columnAsDouble( 2 );
239  double p3 = statement.columnAsDouble( 3 );
240  double p4 = statement.columnAsDouble( 4 );
241  double p5 = statement.columnAsDouble( 5 );
242  double p6 = statement.columnAsDouble( 6 );
243  double p7 = statement.columnAsDouble( 7 );
244  if ( methodCode == 9603 ) //3 parameter transformation
245  {
246  transformString += QStringLiteral( "%1,%2,%3" ).arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ) );
247  }
248  else //7 parameter transformation
249  {
250  transformString += QStringLiteral( "%1,%2,%3,%4,%5,%6,%7" ).arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ), QString::number( p4 ), QString::number( p5 ), QString::number( p6 ), QString::number( p7 ) );
251  }
252  }
253 
254  if ( transformString.compare( string, Qt::CaseInsensitive ) == 0 )
255  {
256  return statement.columnAsInt64( 8 );
257  }
258  }
259 
260  return -1;
261 }
262 
264 {
266 
268  int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
269  if ( openResult != SQLITE_OK )
270  {
271  return info;
272  }
273 
275  QString sql = QStringLiteral( "SELECT epsg_nr,source_crs_code,target_crs_code,remarks,scope,preferred,deprecated FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( datumTransform );
276  int prepareRes;
277  statement = database.prepare( sql, prepareRes );
278  if ( prepareRes != SQLITE_OK )
279  {
280  return info;
281  }
282 
283  int srcCrsId, destCrsId;
284  if ( statement.step() != SQLITE_ROW )
285  {
286  return info;
287  }
288 
289  info.datumTransformId = datumTransform;
290  info.epsgCode = statement.columnAsInt64( 0 );
291  srcCrsId = statement.columnAsInt64( 1 );
292  destCrsId = statement.columnAsInt64( 2 );
293  info.remarks = statement.columnAsText( 3 );
294  info.scope = statement.columnAsText( 4 );
295  info.preferred = statement.columnAsInt64( 5 ) != 0;
296  info.deprecated = statement.columnAsInt64( 6 ) != 0;
297 
298  QgsCoordinateReferenceSystem srcCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( srcCrsId ) );
299  info.sourceCrsDescription = srcCrs.description();
300  info.sourceCrsAuthId = srcCrs.authid();
301  QgsCoordinateReferenceSystem destCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( destCrsId ) );
302  info.destinationCrsDescription = destCrs.description();
303  info.destinationCrsAuthId = destCrs.authid();
304 
305  return info;
306 }
307 
308 #if PROJ_VERSION_MAJOR >= 6
309 QgsDatumTransform::TransformDetails QgsDatumTransform::transformDetailsFromPj( PJ *op )
310 {
311  PJ_CONTEXT *pjContext = QgsProjContext::get();
312  TransformDetails details;
313  if ( !op )
314  return details;
315 
316  QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, op ) );
317  if ( normalized )
318  details.proj = QString( proj_as_proj_string( pjContext, normalized.get(), PJ_PROJ_5, nullptr ) );
319 
320  if ( details.proj.isEmpty() )
321  details.proj = QString( proj_as_proj_string( pjContext, op, PJ_PROJ_5, nullptr ) );
322  details.name = QString( proj_get_name( op ) );
323  details.accuracy = proj_coordoperation_get_accuracy( pjContext, op );
324  details.isAvailable = proj_coordoperation_is_instantiable( pjContext, op );
325 
326  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( pjContext, op ); ++j )
327  {
328  const char *shortName = nullptr;
329  const char *fullName = nullptr;
330  const char *packageName = nullptr;
331  const char *url = nullptr;
332  int directDownload = 0;
333  int openLicense = 0;
334  int isAvailable = 0;
335  proj_coordoperation_get_grid_used( pjContext, op, j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
336  GridDetails gridDetails;
337  gridDetails.shortName = QString( shortName );
338  gridDetails.fullName = QString( fullName );
339  gridDetails.packageName = QString( packageName );
340  gridDetails.url = QString( url );
341  gridDetails.directDownload = directDownload;
342  gridDetails.openLicense = openLicense;
343  gridDetails.isAvailable = isAvailable;
344 
345  details.grids.append( gridDetails );
346  }
347  return details;
348 }
349 #endif
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
QString name
Display name of transform operation.
QString destinationCrsDescription
Destination CRS description.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
static QList< QgsDatumTransform::TransformDetails > operations(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination)
Returns a list of coordinate operations available for transforming coordinates from the source to des...
Contains information about a projection transformation grid file.
int datumTransformId
Datum transform ID.
QString packageName
Name of package the grid is included within.
bool deprecated
True if transform is deprecated.
QString fullName
Full name of transform grid.
static Q_DECL_DEPRECATED QList< QgsDatumTransform::TransformPair > datumTransformations(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination)
Returns a list of datum transformations which are available for the given source and destination CRS...
bool openLicense
true if grid is available under an open license
QString shortName
Short name of transform grid.
QString sourceCrsDescription
Source CRS description.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
bool isAvailable
true if grid is currently available for use
QString url
Url to download grid from.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
Contains datum transform information.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
QString destinationCrsAuthId
Destination CRS auth ID.
bool preferred
True if transform is the preferred transform to use for the source/destination CRS combination...
int epsgCode
EPSG code for the transform, or 0 if not found in EPSG database.
QString sourceCrsAuthId
Source CRS auth ID.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
Contains datum transform information.
void PJ_CONTEXT
Definition: qgsprojutils.h:135
QList< QgsDatumTransform::GridDetails > grids
Contains a list of transform grids used by the operation.
This class represents a coordinate reference system (CRS).
bool isAvailable
true if operation is available.
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID...
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
double accuracy
Transformation accuracy (in meters)
static Q_DECL_DEPRECATED int projStringToDatumTransformId(const QString &string)
Returns the datum transform ID corresponding to a specified proj string.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
bool directDownload
true if direct download of grid is possible
QString remarks
Transform remarks.
Contains information about a coordinate transformation operation.
QString proj
Proj representation of transform operation.
static Q_DECL_DEPRECATED QgsDatumTransform::TransformInfo datumTransformInfo(int datumTransformId)
Returns detailed information about the specified datumTransformId.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
QString scope
Scope of transform.
QString authid() const
Returns the authority identifier for the CRS.