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