Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsprojutils.h
3 : : -------------------
4 : : begin : March 2019
5 : : copyright : (C) 2019 by 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 "qgsprojutils.h"
18 : : #include "qgis.h"
19 : : #include "qgscoordinatetransform.h"
20 : :
21 : : #include <QString>
22 : : #include <QSet>
23 : : #include <QRegularExpression>
24 : :
25 : : #include <proj.h>
26 : :
27 : : #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
28 : 3 : thread_local QgsProjContext QgsProjContext::sProjContext;
29 : : #else
30 : : QThreadStorage< QgsProjContext * > QgsProjContext::sProjContext;
31 : : #endif
32 : :
33 : 3 : QgsProjContext::QgsProjContext()
34 : : {
35 : 3 : mContext = proj_context_create();
36 : 3 : }
37 : :
38 : 3 : QgsProjContext::~QgsProjContext()
39 : : {
40 : : // Call removeFromCacheObjectsBelongingToCurrentThread() before
41 : : // destroying the context
42 : 3 : QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( mContext );
43 : 3 : QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( mContext );
44 : 3 : proj_context_destroy( mContext );
45 : 3 : }
46 : :
47 : 125 : PJ_CONTEXT *QgsProjContext::get()
48 : : {
49 : : #if defined(USE_THREAD_LOCAL) && !defined(Q_OS_WIN)
50 : 125 : return sProjContext.mContext;
51 : : #else
52 : : PJ_CONTEXT *pContext = nullptr;
53 : : if ( sProjContext.hasLocalData() )
54 : : {
55 : : pContext = sProjContext.localData()->mContext;
56 : : }
57 : : else
58 : : {
59 : : sProjContext.setLocalData( new QgsProjContext() );
60 : : pContext = sProjContext.localData()->mContext;
61 : : }
62 : : return pContext;
63 : : #endif
64 : : }
65 : :
66 : 60 : void QgsProjUtils::ProjPJDeleter::operator()( PJ *object )
67 : : {
68 : 60 : proj_destroy( object );
69 : 60 : }
70 : :
71 : 0 : bool QgsProjUtils::usesAngularUnit( const QString &projDef )
72 : : {
73 : 0 : const QString crsDef = QStringLiteral( "%1 +type=crs" ).arg( projDef );
74 : 0 : PJ_CONTEXT *context = QgsProjContext::get();
75 : 0 : QgsProjUtils::proj_pj_unique_ptr projSingleOperation( proj_create( context, crsDef.toUtf8().constData() ) );
76 : 0 : if ( !projSingleOperation )
77 : 0 : return false;
78 : :
79 : 0 : QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, projSingleOperation.get() ) );
80 : 0 : if ( !coordinateSystem )
81 : 0 : return false;
82 : :
83 : 0 : const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
84 : 0 : if ( axisCount > 0 )
85 : : {
86 : 0 : const char *outUnitAuthName = nullptr;
87 : 0 : const char *outUnitAuthCode = nullptr;
88 : : // Read only first axis
89 : 0 : proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
90 : : nullptr,
91 : : nullptr,
92 : : nullptr,
93 : : nullptr,
94 : : nullptr,
95 : : &outUnitAuthName,
96 : : &outUnitAuthCode );
97 : :
98 : 0 : if ( outUnitAuthName && outUnitAuthCode )
99 : : {
100 : 0 : const char *unitCategory = nullptr;
101 : 0 : if ( proj_uom_get_info_from_database( context, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
102 : : {
103 : 0 : return QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
104 : : }
105 : 0 : }
106 : 0 : }
107 : 0 : return false;
108 : 0 : }
109 : :
110 : 0 : bool QgsProjUtils::axisOrderIsSwapped( const PJ *crs )
111 : : {
112 : : //ported from https://github.com/pramsey/postgis/blob/7ecf6839c57a838e2c8540001a3cd35b78a730db/liblwgeom/lwgeom_transform.c#L299
113 : 0 : if ( !crs )
114 : 0 : return false;
115 : :
116 : 0 : PJ_CONTEXT *context = QgsProjContext::get();
117 : 0 : QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, crs ) );
118 : 0 : if ( !pjCs )
119 : 0 : return false;
120 : :
121 : 0 : const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
122 : 0 : if ( axisCount > 0 )
123 : : {
124 : 0 : const char *outDirection = nullptr;
125 : : // Read only first axis, see if it is degrees / north
126 : :
127 : 0 : proj_cs_get_axis_info( context, pjCs.get(), 0,
128 : : nullptr,
129 : : nullptr,
130 : : &outDirection,
131 : : nullptr,
132 : : nullptr,
133 : : nullptr,
134 : : nullptr
135 : : );
136 : 0 : return QString( outDirection ).compare( QLatin1String( "north" ), Qt::CaseInsensitive ) == 0;
137 : : }
138 : 0 : return false;
139 : 0 : }
140 : :
141 : :
142 : 20 : QgsProjUtils::proj_pj_unique_ptr QgsProjUtils::crsToSingleCrs( const PJ *crs )
143 : : {
144 : 20 : if ( !crs )
145 : 0 : return nullptr;
146 : :
147 : 20 : PJ_CONTEXT *context = QgsProjContext::get();
148 : 20 : switch ( proj_get_type( crs ) )
149 : : {
150 : : case PJ_TYPE_BOUND_CRS:
151 : 0 : return QgsProjUtils::proj_pj_unique_ptr( proj_get_source_crs( context, crs ) );
152 : :
153 : : case PJ_TYPE_COMPOUND_CRS:
154 : : {
155 : 0 : int i = 0;
156 : 0 : QgsProjUtils::proj_pj_unique_ptr res( proj_crs_get_sub_crs( context, crs, i ) );
157 : 0 : while ( res && ( proj_get_type( res.get() ) == PJ_TYPE_VERTICAL_CRS || proj_get_type( res.get() ) == PJ_TYPE_TEMPORAL_CRS ) )
158 : : {
159 : 0 : i++;
160 : 0 : res.reset( proj_crs_get_sub_crs( context, crs, i ) );
161 : : }
162 : 0 : return res;
163 : 0 : }
164 : :
165 : : // maybe other types to handle??
166 : :
167 : 3 : default:
168 : 20 : return QgsProjUtils::proj_pj_unique_ptr( proj_clone( context, crs ) );
169 : : }
170 : :
171 : : #ifndef _MSC_VER // unreachable
172 : : return nullptr;
173 : : #endif
174 : 20 : }
175 : :
176 : 0 : bool QgsProjUtils::identifyCrs( const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags )
177 : : {
178 : 0 : authName.clear();
179 : 0 : authCode.clear();
180 : :
181 : 0 : if ( !crs )
182 : 0 : return false;
183 : :
184 : 0 : int *confidence = nullptr;
185 : 0 : if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs, nullptr, nullptr, &confidence ) )
186 : : {
187 : 0 : const int count = proj_list_get_count( crsList );
188 : 0 : int bestConfidence = 0;
189 : 0 : QgsProjUtils::proj_pj_unique_ptr matchedCrs;
190 : 0 : for ( int i = 0; i < count; ++i )
191 : : {
192 : 0 : if ( confidence[i] >= bestConfidence )
193 : : {
194 : 0 : QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
195 : 0 : switch ( proj_get_type( candidateCrs.get() ) )
196 : : {
197 : : case PJ_TYPE_BOUND_CRS:
198 : : // proj_identify also matches bound CRSes to the source CRS. But they are not the same as the source CRS, so we don't
199 : : // consider them a candidate for a match here (depending on the identify flags, that is!)
200 : 0 : if ( flags & FlagMatchBoundCrsToUnderlyingSourceCrs )
201 : 0 : break;
202 : : else
203 : 0 : continue;
204 : :
205 : : default:
206 : 0 : break;
207 : : }
208 : :
209 : 0 : candidateCrs = QgsProjUtils::crsToSingleCrs( candidateCrs.get() );
210 : 0 : const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
211 : : // if a match is identical confidence, we prefer EPSG codes for compatibility with earlier qgis conversions
212 : 0 : if ( confidence[i] > bestConfidence || ( confidence[i] == bestConfidence && authName == QLatin1String( "EPSG" ) ) )
213 : : {
214 : 0 : bestConfidence = confidence[i];
215 : 0 : matchedCrs = std::move( candidateCrs );
216 : 0 : }
217 : 0 : }
218 : 0 : }
219 : 0 : proj_list_destroy( crsList );
220 : 0 : proj_int_list_destroy( confidence );
221 : 0 : if ( matchedCrs && bestConfidence >= 70 )
222 : : {
223 : 0 : authName = QString( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
224 : 0 : authCode = QString( proj_get_id_code( matchedCrs.get(), 0 ) );
225 : 0 : }
226 : 0 : }
227 : 0 : return !authName.isEmpty() && !authCode.isEmpty();
228 : 0 : }
229 : :
230 : 0 : bool QgsProjUtils::coordinateOperationIsAvailable( const QString &projDef )
231 : : {
232 : 0 : if ( projDef.isEmpty() )
233 : 0 : return true;
234 : :
235 : 0 : PJ_CONTEXT *context = QgsProjContext::get();
236 : 0 : QgsProjUtils::proj_pj_unique_ptr coordinateOperation( proj_create( context, projDef.toUtf8().constData() ) );
237 : 0 : if ( !coordinateOperation )
238 : 0 : return false;
239 : :
240 : 0 : return static_cast< bool >( proj_coordoperation_is_instantiable( context, coordinateOperation.get() ) );
241 : 0 : }
242 : :
243 : 0 : QList<QgsDatumTransform::GridDetails> QgsProjUtils::gridsUsed( const QString &proj )
244 : : {
245 : 0 : const thread_local QRegularExpression regex( QStringLiteral( "\\+(?:nad)?grids=(.*?)\\s" ) );
246 : :
247 : 0 : QList< QgsDatumTransform::GridDetails > grids;
248 : 0 : QRegularExpressionMatchIterator matches = regex.globalMatch( proj );
249 : 0 : while ( matches.hasNext() )
250 : : {
251 : 0 : const QRegularExpressionMatch match = matches.next();
252 : 0 : const QString gridName = match.captured( 1 );
253 : 0 : QgsDatumTransform::GridDetails grid;
254 : 0 : grid.shortName = gridName;
255 : 0 : const char *fullName = nullptr;
256 : 0 : const char *packageName = nullptr;
257 : 0 : const char *url = nullptr;
258 : 0 : int directDownload = 0;
259 : 0 : int openLicense = 0;
260 : 0 : int available = 0;
261 : 0 : proj_grid_get_info_from_database( QgsProjContext::get(), gridName.toUtf8().constData(), &fullName, &packageName, &url, &directDownload, &openLicense, &available );
262 : 0 : grid.fullName = QString( fullName );
263 : 0 : grid.packageName = QString( packageName );
264 : 0 : grid.url = QString( url );
265 : 0 : grid.directDownload = directDownload;
266 : 0 : grid.openLicense = openLicense;
267 : 0 : grid.isAvailable = available;
268 : 0 : grids.append( grid );
269 : 0 : }
270 : 0 : return grids;
271 : 0 : }
272 : :
273 : : #if 0
274 : : QStringList QgsProjUtils::nonAvailableGrids( const QString &projDef )
275 : : {
276 : : if ( projDef.isEmpty() )
277 : : return QStringList();
278 : :
279 : : PJ_CONTEXT *context = QgsProjContext::get();
280 : : QgsProjUtils::proj_pj_unique_ptr op( proj_create( context, projDef.toUtf8().constData() ) ); < ---- - this always fails if grids are missing
281 : : if ( !op )
282 : : return QStringList();
283 : :
284 : : QStringList res;
285 : : for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, op.get() ); ++j )
286 : : {
287 : : const char *shortName = nullptr;
288 : : int isAvailable = 0;
289 : : proj_coordoperation_get_grid_used( context, op.get(), j, &shortName, nullptr, nullptr, nullptr, nullptr, nullptr, &isAvailable );
290 : : if ( !isAvailable )
291 : : res << QString( shortName );
292 : : }
293 : : return res;
294 : : }
295 : : #endif
296 : :
297 : 0 : int QgsProjUtils::projVersionMajor()
298 : : {
299 : 0 : return PROJ_VERSION_MAJOR;
300 : : }
301 : :
302 : 7 : QStringList QgsProjUtils::searchPaths()
303 : : {
304 : 7 : const QString path( proj_info().searchpath );
305 : 7 : QStringList paths;
306 : : #ifdef Q_OS_WIN
307 : : paths = path.split( ';' );
308 : : #else
309 : 7 : paths = path.split( ':' );
310 : : #endif
311 : :
312 : 7 : QSet<QString> existing;
313 : : // thin out duplicates from paths -- see https://github.com/OSGeo/proj.4/pull/1498
314 : 7 : QStringList res;
315 : 7 : res.reserve( paths.count() );
316 : 28 : for ( const QString &p : std::as_const( paths ) )
317 : : {
318 : 21 : if ( existing.contains( p ) )
319 : 5 : continue;
320 : :
321 : 16 : existing.insert( p );
322 : 16 : res << p;
323 : : }
324 : 7 : return res;
325 : 7 : }
|