QGIS API Documentation 3.99.0-Master (2fe06baccd8)
Loading...
Searching...
No Matches
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"
18
19#include <proj.h>
20#include <sqlite3.h>
21
22#include "qgsapplication.h"
24#include "qgsprojutils.h"
25#include "qgssqliteutils.h"
26
27QList<QgsDatumTransform::TransformDetails> QgsDatumTransform::operations( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, bool includeSuperseded )
28{
29 QList< QgsDatumTransform::TransformDetails > res;
30 if ( !source.projObject() || !destination.projObject() )
31 return res;
32
33 PJ_CONTEXT *pjContext = QgsProjContext::get();
34
35 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( pjContext, nullptr );
36
37 // We want to return ALL grids, not just those available for use
38 proj_operation_factory_context_set_grid_availability_use( pjContext, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
39
40 // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
41 proj_operation_factory_context_set_spatial_criterion( pjContext, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
42
43 if ( includeSuperseded )
44 proj_operation_factory_context_set_discard_superseded( pjContext, operationContext, false );
45
46 if ( PJ_OBJ_LIST *ops = proj_create_operations( pjContext, source.projObject(), destination.projObject(), operationContext ) )
47 {
48 int count = proj_list_get_count( ops );
49 for ( int i = 0; i < count; ++i )
50 {
51 QgsProjUtils::proj_pj_unique_ptr op( proj_list_get( pjContext, ops, i ) );
52 if ( !op )
53 continue;
54
56 if ( !details.proj.isEmpty() )
57 res.push_back( details );
58
59 }
60 proj_list_destroy( ops );
61 }
62 proj_operation_factory_context_destroy( operationContext );
63 return res;
64}
65
66QList< 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 : std::as_const( directTransforms ) )
109 {
110 transformations.push_back( QgsDatumTransform::TransformPair( transform, -1 ) );
111 }
112
113 //add direct datum transformations
114 for ( int transform : std::as_const( reverseDirectTransforms ) )
115 {
116 transformations.push_back( QgsDatumTransform::TransformPair( -1, transform ) );
117 }
118
119 for ( int srcTransform : std::as_const( srcToWgs84 ) )
120 {
121 for ( int destTransform : std::as_const( destToWgs84 ) )
122 {
123 transformations.push_back( QgsDatumTransform::TransformPair( srcTransform, destTransform ) );
124 }
125 }
126
127 return transformations;
128}
129
130void 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
139 sqlite3_statement_unique_ptr statement;
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
155QString 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
309{
310 PJ_CONTEXT *pjContext = QgsProjContext::get();
311 TransformDetails details;
312 if ( !op )
313 return details;
314
315 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, op ) );
316 if ( normalized )
317 details.proj = QString( proj_as_proj_string( pjContext, normalized.get(), PJ_PROJ_5, nullptr ) );
318
319 if ( details.proj.isEmpty() )
320 details.proj = QString( proj_as_proj_string( pjContext, op, PJ_PROJ_5, nullptr ) );
321
322 if ( details.proj.isEmpty() )
323 return details;
324
325 details.name = QString( proj_get_name( op ) );
326 details.accuracy = proj_coordoperation_get_accuracy( pjContext, op );
327 details.isAvailable = proj_coordoperation_is_instantiable( pjContext, op );
328
329 details.authority = QString( proj_get_id_auth_name( op, 0 ) );
330 details.code = QString( proj_get_id_code( op, 0 ) );
331
332 const char *areaOfUseName = nullptr;
333 double westLon = 0;
334 double southLat = 0;
335 double eastLon = 0;
336 double northLat = 0;
337 if ( proj_get_area_of_use( pjContext, op, &westLon, &southLat, &eastLon, &northLat, &areaOfUseName ) )
338 {
339 details.areaOfUse = QString( areaOfUseName );
340 // don't use the constructor which normalizes!
341 details.bounds.setXMinimum( westLon );
342 details.bounds.setYMinimum( southLat );
343 details.bounds.setXMaximum( eastLon );
344 details.bounds.setYMaximum( northLat );
345 }
346
347 details.remarks = QString( proj_get_remarks( op ) );
348 details.scope = QString( proj_get_scope( op ) );
349
350 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( pjContext, op ); ++j )
351 {
352 const char *shortName = nullptr;
353 const char *fullName = nullptr;
354 const char *packageName = nullptr;
355 const char *url = nullptr;
356 int directDownload = 0;
357 int openLicense = 0;
358 int isAvailable = 0;
359 proj_coordoperation_get_grid_used( pjContext, op, j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
360 GridDetails gridDetails;
361 gridDetails.shortName = QString( shortName );
362 gridDetails.fullName = QString( fullName );
363 gridDetails.packageName = QString( packageName );
364 gridDetails.url = QString( url );
365 gridDetails.directDownload = directDownload;
366 gridDetails.openLicense = openLicense;
367 gridDetails.isAvailable = isAvailable;
368
369 details.grids.append( gridDetails );
370 }
371
372 if ( proj_get_type( op ) == PJ_TYPE_CONCATENATED_OPERATION )
373 {
374 for ( int j = 0; j < proj_concatoperation_get_step_count( pjContext, op ); ++j )
375 {
376 QgsProjUtils::proj_pj_unique_ptr step( proj_concatoperation_get_step( pjContext, op, j ) );
377 if ( step )
378 {
379 SingleOperationDetails singleOpDetails;
380 singleOpDetails.remarks = QString( proj_get_remarks( step.get() ) );
381 singleOpDetails.scope = QString( proj_get_scope( step.get() ) );
382 singleOpDetails.authority = QString( proj_get_id_auth_name( step.get(), 0 ) );
383 singleOpDetails.code = QString( proj_get_id_code( step.get(), 0 ) );
384
385 const char *areaOfUseName = nullptr;
386 if ( proj_get_area_of_use( pjContext, step.get(), nullptr, nullptr, nullptr, nullptr, &areaOfUseName ) )
387 {
388 singleOpDetails.areaOfUse = QString( areaOfUseName );
389 }
390 details.operationDetails.append( singleOpDetails );
391 }
392 }
393 }
394 else
395 {
396 SingleOperationDetails singleOpDetails;
397 singleOpDetails.remarks = QString( proj_get_remarks( op ) );
398 singleOpDetails.scope = QString( proj_get_scope( op ) );
399 singleOpDetails.authority = QString( proj_get_id_auth_name( op, 0 ) );
400 singleOpDetails.code = QString( proj_get_id_code( op, 0 ) );
401
402 const char *areaOfUseName = nullptr;
403 if ( proj_get_area_of_use( pjContext, op, nullptr, nullptr, nullptr, nullptr, &areaOfUseName ) )
404 {
405 singleOpDetails.areaOfUse = QString( areaOfUseName );
406 }
407 details.operationDetails.append( singleOpDetails );
408 }
409
410 return details;
411}
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
Represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
PJ * projObject() const
Returns the underlying PROJ PJ object corresponding to the CRS, or nullptr if the CRS is invalid.
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID.
static QList< QgsDatumTransform::TransformDetails > operations(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, bool includeSuperseded=false)
Returns a list of coordinate operations available for transforming coordinates from the source to des...
static QgsDatumTransform::TransformDetails transformDetailsFromPj(PJ *op)
Returns the transform details for a Proj coordinate operation op.
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.
static Q_DECL_DEPRECATED int projStringToDatumTransformId(const QString &string)
Returns the datum transform ID corresponding to a specified proj string.
static Q_DECL_DEPRECATED QgsDatumTransform::TransformInfo datumTransformInfo(int datumTransformId)
Returns detailed information about the specified datumTransformId.
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.
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
struct pj_ctx PJ_CONTEXT
struct PJconsts PJ
Contains information about a projection transformation grid file.
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
QString fullName
Full name of transform grid.
bool directDownload
true if direct download of grid is possible
QString packageName
Name of package the grid is included within.
QString url
Url to download grid from.
bool openLicense
true if grid is available under an open license
Contains information about a single coordinate operation.
QString authority
Authority name, e.g. EPSG.
QString code
Authority code, e.g. "8447" (for EPSG:8447).
QString remarks
Remarks for operation, from EPSG registry database.
QString areaOfUse
Area of use, from EPSG registry database.
QString scope
Scope of operation, from EPSG registry database.
Contains information about a coordinate transformation operation.
double accuracy
Transformation accuracy (in meters).
QgsRectangle bounds
Valid bounds for the coordinate operation.
QString name
Display name of transform operation.
QString areaOfUse
Area of use string.
QList< QgsDatumTransform::SingleOperationDetails > operationDetails
Contains information about the single operation steps used in the transform operation.
bool isAvailable
true if operation is available.
QString proj
Proj representation of transform operation.
QList< QgsDatumTransform::GridDetails > grids
Contains a list of transform grids used by the operation.
QString scope
Scope of operation, from EPSG registry database.
QString remarks
Remarks for operation, from EPSG registry database.
QString authority
Authority name, e.g.
QString code
Identification code, e.g.
Contains datum transform information.
QString sourceCrsDescription
Source CRS description.
QString destinationCrsAuthId
Destination CRS auth ID.
int epsgCode
EPSG code for the transform, or 0 if not found in EPSG database.
QString destinationCrsDescription
Destination CRS description.
QString sourceCrsAuthId
Source CRS auth ID.
QString scope
Scope of transform.
QString remarks
Transform remarks.
bool preferred
True if transform is the preferred transform to use for the source/destination CRS combination.
bool deprecated
True if transform is deprecated.
int datumTransformId
Datum transform ID.
Contains datum transform information.