QGIS API Documentation 3.99.0-Master (d270888f95f)
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
27#include <QString>
28
29using namespace Qt::StringLiterals;
30
31QList<QgsDatumTransform::TransformDetails> QgsDatumTransform::operations( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, bool includeSuperseded )
32{
33 QList< QgsDatumTransform::TransformDetails > res;
34 if ( !source.projObject() || !destination.projObject() )
35 return res;
36
37 PJ_CONTEXT *pjContext = QgsProjContext::get();
38
39 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( pjContext, nullptr );
40
41 // We want to return ALL grids, not just those available for use
42 proj_operation_factory_context_set_grid_availability_use( pjContext, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
43
44 // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
45 proj_operation_factory_context_set_spatial_criterion( pjContext, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
46
47 if ( includeSuperseded )
48 proj_operation_factory_context_set_discard_superseded( pjContext, operationContext, false );
49
50 if ( PJ_OBJ_LIST *ops = proj_create_operations( pjContext, source.projObject(), destination.projObject(), operationContext ) )
51 {
52 int count = proj_list_get_count( ops );
53 for ( int i = 0; i < count; ++i )
54 {
55 QgsProjUtils::proj_pj_unique_ptr op( proj_list_get( pjContext, ops, i ) );
56 if ( !op )
57 continue;
58
60 if ( !details.proj.isEmpty() )
61 res.push_back( details );
62
63 }
64 proj_list_destroy( ops );
65 }
66 proj_operation_factory_context_destroy( operationContext );
67 return res;
68}
69
70QList< QgsDatumTransform::TransformPair > QgsDatumTransform::datumTransformations( const QgsCoordinateReferenceSystem &srcCRS, const QgsCoordinateReferenceSystem &destCRS )
71{
72 QList< QgsDatumTransform::TransformPair > transformations;
73
74 QString srcGeoId = srcCRS.geographicCrsAuthId();
75 QString destGeoId = destCRS.geographicCrsAuthId();
76
77 if ( srcGeoId.isEmpty() || destGeoId.isEmpty() )
78 {
79 return transformations;
80 }
81
82 QStringList srcSplit = srcGeoId.split( ':' );
83 QStringList destSplit = destGeoId.split( ':' );
84
85 if ( srcSplit.size() < 2 || destSplit.size() < 2 )
86 {
87 return transformations;
88 }
89
90 int srcAuthCode = srcSplit.at( 1 ).toInt();
91 int destAuthCode = destSplit.at( 1 ).toInt();
92
93 if ( srcAuthCode == destAuthCode )
94 {
95 return transformations; //crs have the same datum
96 }
97
98 QList<int> directTransforms;
99 searchDatumTransform( u"SELECT coord_op_code FROM tbl_datum_transform WHERE source_crs_code=%1 AND target_crs_code=%2 ORDER BY deprecated ASC,preferred DESC"_s.arg( srcAuthCode ).arg( destAuthCode ),
100 directTransforms );
101 QList<int> reverseDirectTransforms;
102 searchDatumTransform( u"SELECT coord_op_code FROM tbl_datum_transform WHERE source_crs_code = %1 AND target_crs_code=%2 ORDER BY deprecated ASC,preferred DESC"_s.arg( destAuthCode ).arg( srcAuthCode ),
103 reverseDirectTransforms );
104 QList<int> srcToWgs84;
105 searchDatumTransform( u"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"_s.arg( srcAuthCode ).arg( 4326 ),
106 srcToWgs84 );
107 QList<int> destToWgs84;
108 searchDatumTransform( u"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"_s.arg( destAuthCode ).arg( 4326 ),
109 destToWgs84 );
110
111 //add direct datum transformations
112 for ( int transform : std::as_const( directTransforms ) )
113 {
114 transformations.push_back( QgsDatumTransform::TransformPair( transform, -1 ) );
115 }
116
117 //add direct datum transformations
118 for ( int transform : std::as_const( reverseDirectTransforms ) )
119 {
120 transformations.push_back( QgsDatumTransform::TransformPair( -1, transform ) );
121 }
122
123 for ( int srcTransform : std::as_const( srcToWgs84 ) )
124 {
125 for ( int destTransform : std::as_const( destToWgs84 ) )
126 {
127 transformations.push_back( QgsDatumTransform::TransformPair( srcTransform, destTransform ) );
128 }
129 }
130
131 return transformations;
132}
133
134void QgsDatumTransform::searchDatumTransform( const QString &sql, QList< int > &transforms )
135{
137 int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
138 if ( openResult != SQLITE_OK )
139 {
140 return;
141 }
142
143 sqlite3_statement_unique_ptr statement;
144 int prepareRes;
145 statement = database.prepare( sql, prepareRes );
146 if ( prepareRes != SQLITE_OK )
147 {
148 return;
149 }
150
151 QString cOpCode;
152 while ( statement.step() == SQLITE_ROW )
153 {
154 cOpCode = statement.columnAsText( 0 );
155 transforms.push_back( cOpCode.toInt() );
156 }
157}
158
159QString QgsDatumTransform::datumTransformToProj( int datumTransform )
160{
161 QString transformString;
162
164 int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
165 if ( openResult != SQLITE_OK )
166 {
167 return transformString;
168 }
169
171 QString sql = u"SELECT coord_op_method_code,p1,p2,p3,p4,p5,p6,p7 FROM tbl_datum_transform WHERE coord_op_code=%1"_s.arg( datumTransform );
172 int prepareRes;
173 statement = database.prepare( sql, prepareRes );
174 if ( prepareRes != SQLITE_OK )
175 {
176 return transformString;
177 }
178
179 if ( statement.step() == SQLITE_ROW )
180 {
181 //coord_op_methode_code
182 int methodCode = statement.columnAsInt64( 0 );
183 if ( methodCode == 9615 ) //ntv2
184 {
185 transformString = "+nadgrids=" + statement.columnAsText( 1 );
186 }
187 else if ( methodCode == 9603 || methodCode == 9606 || methodCode == 9607 )
188 {
189 transformString += "+towgs84="_L1;
190 double p1 = statement.columnAsDouble( 1 );
191 double p2 = statement.columnAsDouble( 2 );
192 double p3 = statement.columnAsDouble( 3 );
193 double p4 = statement.columnAsDouble( 4 );
194 double p5 = statement.columnAsDouble( 5 );
195 double p6 = statement.columnAsDouble( 6 );
196 double p7 = statement.columnAsDouble( 7 );
197 if ( methodCode == 9603 ) //3 parameter transformation
198 {
199 transformString += u"%1,%2,%3"_s.arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ) );
200 }
201 else //7 parameter transformation
202 {
203 transformString += u"%1,%2,%3,%4,%5,%6,%7"_s.arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ), QString::number( p4 ), QString::number( p5 ), QString::number( p6 ), QString::number( p7 ) );
204 }
205 }
206 }
207
208 return transformString;
209}
210
212{
214 int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
215 if ( openResult != SQLITE_OK )
216 {
217 return -1;
218 }
219
221 QString sql = u"SELECT coord_op_method_code,p1,p2,p3,p4,p5,p6,p7,coord_op_code FROM tbl_datum_transform"_s;
222 int prepareRes;
223 statement = database.prepare( sql, prepareRes );
224 if ( prepareRes != SQLITE_OK )
225 {
226 return -1;
227 }
228
229 while ( statement.step() == SQLITE_ROW )
230 {
231 QString transformString;
232 //coord_op_methode_code
233 int methodCode = statement.columnAsInt64( 0 );
234 if ( methodCode == 9615 ) //ntv2
235 {
236 transformString = "+nadgrids=" + statement.columnAsText( 1 );
237 }
238 else if ( methodCode == 9603 || methodCode == 9606 || methodCode == 9607 )
239 {
240 transformString += "+towgs84="_L1;
241 double p1 = statement.columnAsDouble( 1 );
242 double p2 = statement.columnAsDouble( 2 );
243 double p3 = statement.columnAsDouble( 3 );
244 double p4 = statement.columnAsDouble( 4 );
245 double p5 = statement.columnAsDouble( 5 );
246 double p6 = statement.columnAsDouble( 6 );
247 double p7 = statement.columnAsDouble( 7 );
248 if ( methodCode == 9603 ) //3 parameter transformation
249 {
250 transformString += u"%1,%2,%3"_s.arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ) );
251 }
252 else //7 parameter transformation
253 {
254 transformString += u"%1,%2,%3,%4,%5,%6,%7"_s.arg( QString::number( p1 ), QString::number( p2 ), QString::number( p3 ), QString::number( p4 ), QString::number( p5 ), QString::number( p6 ), QString::number( p7 ) );
255 }
256 }
257
258 if ( transformString.compare( string, Qt::CaseInsensitive ) == 0 )
259 {
260 return statement.columnAsInt64( 8 );
261 }
262 }
263
264 return -1;
265}
266
268{
270
272 int openResult = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
273 if ( openResult != SQLITE_OK )
274 {
275 return info;
276 }
277
279 QString sql = u"SELECT epsg_nr,source_crs_code,target_crs_code,remarks,scope,preferred,deprecated FROM tbl_datum_transform WHERE coord_op_code=%1"_s.arg( datumTransform );
280 int prepareRes;
281 statement = database.prepare( sql, prepareRes );
282 if ( prepareRes != SQLITE_OK )
283 {
284 return info;
285 }
286
287 int srcCrsId, destCrsId;
288 if ( statement.step() != SQLITE_ROW )
289 {
290 return info;
291 }
292
293 info.datumTransformId = datumTransform;
294 info.epsgCode = statement.columnAsInt64( 0 );
295 srcCrsId = statement.columnAsInt64( 1 );
296 destCrsId = statement.columnAsInt64( 2 );
297 info.remarks = statement.columnAsText( 3 );
298 info.scope = statement.columnAsText( 4 );
299 info.preferred = statement.columnAsInt64( 5 ) != 0;
300 info.deprecated = statement.columnAsInt64( 6 ) != 0;
301
302 QgsCoordinateReferenceSystem srcCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( u"EPSG:%1"_s.arg( srcCrsId ) );
303 info.sourceCrsDescription = srcCrs.description();
304 info.sourceCrsAuthId = srcCrs.authid();
305 QgsCoordinateReferenceSystem destCrs = QgsCoordinateReferenceSystem::fromOgcWmsCrs( u"EPSG:%1"_s.arg( destCrsId ) );
306 info.destinationCrsDescription = destCrs.description();
307 info.destinationCrsAuthId = destCrs.authid();
308
309 return info;
310}
311
313{
314 PJ_CONTEXT *pjContext = QgsProjContext::get();
315 TransformDetails details;
316 if ( !op )
317 return details;
318
319 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, op ) );
320 if ( normalized )
321 details.proj = QString( proj_as_proj_string( pjContext, normalized.get(), PJ_PROJ_5, nullptr ) );
322
323 if ( details.proj.isEmpty() )
324 details.proj = QString( proj_as_proj_string( pjContext, op, PJ_PROJ_5, nullptr ) );
325
326 if ( details.proj.isEmpty() )
327 return details;
328
329 details.name = QString( proj_get_name( op ) );
330 details.accuracy = proj_coordoperation_get_accuracy( pjContext, op );
331 details.isAvailable = proj_coordoperation_is_instantiable( pjContext, op );
332
333 details.authority = QString( proj_get_id_auth_name( op, 0 ) );
334 details.code = QString( proj_get_id_code( op, 0 ) );
335
336 const char *areaOfUseName = nullptr;
337 double westLon = 0;
338 double southLat = 0;
339 double eastLon = 0;
340 double northLat = 0;
341 if ( proj_get_area_of_use( pjContext, op, &westLon, &southLat, &eastLon, &northLat, &areaOfUseName ) )
342 {
343 details.areaOfUse = QString( areaOfUseName );
344 // don't use the constructor which normalizes!
345 details.bounds.setXMinimum( westLon );
346 details.bounds.setYMinimum( southLat );
347 details.bounds.setXMaximum( eastLon );
348 details.bounds.setYMaximum( northLat );
349 }
350
351 details.remarks = QString( proj_get_remarks( op ) );
352 details.scope = QString( proj_get_scope( op ) );
353
354 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( pjContext, op ); ++j )
355 {
356 const char *shortName = nullptr;
357 const char *fullName = nullptr;
358 const char *packageName = nullptr;
359 const char *url = nullptr;
360 int directDownload = 0;
361 int openLicense = 0;
362 int isAvailable = 0;
363 proj_coordoperation_get_grid_used( pjContext, op, j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
364 GridDetails gridDetails;
365 gridDetails.shortName = QString( shortName );
366 gridDetails.fullName = QString( fullName );
367 gridDetails.packageName = QString( packageName );
368 gridDetails.url = QString( url );
369 gridDetails.directDownload = directDownload;
370 gridDetails.openLicense = openLicense;
371 gridDetails.isAvailable = isAvailable;
372
373 details.grids.append( gridDetails );
374 }
375
376 if ( proj_get_type( op ) == PJ_TYPE_CONCATENATED_OPERATION )
377 {
378 for ( int j = 0; j < proj_concatoperation_get_step_count( pjContext, op ); ++j )
379 {
380 QgsProjUtils::proj_pj_unique_ptr step( proj_concatoperation_get_step( pjContext, op, j ) );
381 if ( step )
382 {
383 SingleOperationDetails singleOpDetails;
384 singleOpDetails.remarks = QString( proj_get_remarks( step.get() ) );
385 singleOpDetails.scope = QString( proj_get_scope( step.get() ) );
386 singleOpDetails.authority = QString( proj_get_id_auth_name( step.get(), 0 ) );
387 singleOpDetails.code = QString( proj_get_id_code( step.get(), 0 ) );
388
389 const char *areaOfUseName = nullptr;
390 if ( proj_get_area_of_use( pjContext, step.get(), nullptr, nullptr, nullptr, nullptr, &areaOfUseName ) )
391 {
392 singleOpDetails.areaOfUse = QString( areaOfUseName );
393 }
394 details.operationDetails.append( singleOpDetails );
395 }
396 }
397 }
398 else
399 {
400 SingleOperationDetails singleOpDetails;
401 singleOpDetails.remarks = QString( proj_get_remarks( op ) );
402 singleOpDetails.scope = QString( proj_get_scope( op ) );
403 singleOpDetails.authority = QString( proj_get_id_auth_name( op, 0 ) );
404 singleOpDetails.code = QString( proj_get_id_code( op, 0 ) );
405
406 const char *areaOfUseName = nullptr;
407 if ( proj_get_area_of_use( pjContext, op, nullptr, nullptr, nullptr, nullptr, &areaOfUseName ) )
408 {
409 singleOpDetails.areaOfUse = QString( areaOfUseName );
410 }
411 details.operationDetails.append( singleOpDetails );
412 }
413
414 return details;
415}
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.