Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgscoordinatetransformcontext.cpp
3 : : ---------------------------------
4 : : begin : November 2017
5 : : copyright : (C) 2017 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 : :
18 : : #include "qgscoordinatetransformcontext.h"
19 : : #include "qgscoordinatetransformcontext_p.h"
20 : : #include "qgscoordinatetransform.h"
21 : : #include "qgssettings.h"
22 : : #include "qgsprojutils.h"
23 : :
24 : 0 : QString crsToKey( const QgsCoordinateReferenceSystem &crs )
25 : : {
26 : 0 : return crs.authid().isEmpty() ? crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : crs.authid();
27 : 0 : }
28 : :
29 : : #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
30 : : template<>
31 : 0 : bool qMapLessThanKey<QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem>>( const QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem> &key1,
32 : : const QPair<QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem> &key2 )
33 : : {
34 : 0 : const QPair< QString, QString > key1String = qMakePair( crsToKey( key1.first ), crsToKey( key1.second ) );
35 : 0 : const QPair< QString, QString > key2String = qMakePair( crsToKey( key2.first ), crsToKey( key2.second ) );
36 : 0 : return key1String < key2String;
37 : 0 : }
38 : : #endif
39 : :
40 : 3385 : QgsCoordinateTransformContext::QgsCoordinateTransformContext()
41 : 3385 : : d( new QgsCoordinateTransformContextPrivate() )
42 : 3385 : {}
43 : :
44 : 3620 : QgsCoordinateTransformContext::~QgsCoordinateTransformContext() = default;
45 : :
46 : 256 : QgsCoordinateTransformContext::QgsCoordinateTransformContext( const QgsCoordinateTransformContext &rhs ) //NOLINT
47 : 256 : : d( rhs.d )
48 : 256 : {}
49 : :
50 : 2094 : QgsCoordinateTransformContext &QgsCoordinateTransformContext::operator=( const QgsCoordinateTransformContext &rhs ) //NOLINT
51 : : {
52 : 2094 : d = rhs.d;
53 : 2094 : return *this;
54 : : }
55 : :
56 : 8 : bool QgsCoordinateTransformContext::operator==( const QgsCoordinateTransformContext &rhs ) const
57 : : {
58 : 8 : if ( d == rhs.d )
59 : 0 : return true;
60 : :
61 : 8 : d->mLock.lockForRead();
62 : 8 : rhs.d->mLock.lockForRead();
63 : 8 : bool equal = d->mSourceDestDatumTransforms == rhs.d->mSourceDestDatumTransforms;
64 : 8 : d->mLock.unlock();
65 : 8 : rhs.d->mLock.unlock();
66 : 8 : return equal;
67 : 8 : }
68 : :
69 : 0 : void QgsCoordinateTransformContext::clear()
70 : : {
71 : 0 : d.detach();
72 : : // play it safe
73 : 0 : d->mLock.lockForWrite();
74 : 0 : d->mSourceDestDatumTransforms.clear();
75 : 0 : d->mLock.unlock();
76 : 0 : }
77 : :
78 : 0 : QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair> QgsCoordinateTransformContext::sourceDestinationDatumTransforms() const
79 : : {
80 : 0 : return QMap<QPair<QString, QString>, QgsDatumTransform::TransformPair>();
81 : : }
82 : :
83 : 0 : QMap<QPair<QString, QString>, QString> QgsCoordinateTransformContext::coordinateOperations() const
84 : : {
85 : 0 : d->mLock.lockForRead();
86 : 0 : auto res = d->mSourceDestDatumTransforms;
87 : 0 : res.detach();
88 : 0 : d->mLock.unlock();
89 : 0 : QMap<QPair<QString, QString>, QString> results;
90 : 0 : for ( auto it = res.constBegin(); it != res.constEnd(); ++it )
91 : 0 : results.insert( qMakePair( it.key().first.authid(), it.key().second.authid() ), it.value().operation );
92 : :
93 : 0 : return results;
94 : 0 : }
95 : :
96 : 0 : bool QgsCoordinateTransformContext::addSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, int sourceTransform, int destinationTransform )
97 : : {
98 : 0 : if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
99 : 0 : return false;
100 : : Q_UNUSED( sourceTransform )
101 : : Q_UNUSED( destinationTransform )
102 : 0 : return false;
103 : 0 : }
104 : :
105 : 0 : bool QgsCoordinateTransformContext::addCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &coordinateOperationProjString, bool allowFallback )
106 : : {
107 : 0 : if ( !sourceCrs.isValid() || !destinationCrs.isValid() )
108 : 0 : return false;
109 : 0 : d.detach();
110 : 0 : d->mLock.lockForWrite();
111 : 0 : QgsCoordinateTransformContextPrivate::OperationDetails details;
112 : 0 : details.operation = coordinateOperationProjString;
113 : 0 : details.allowFallback = allowFallback;
114 : 0 : d->mSourceDestDatumTransforms.insert( qMakePair( sourceCrs, destinationCrs ), details );
115 : 0 : d->mLock.unlock();
116 : 0 : return true;
117 : 0 : }
118 : :
119 : 0 : void QgsCoordinateTransformContext::removeSourceDestinationDatumTransform( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs )
120 : : {
121 : 0 : removeCoordinateOperation( sourceCrs, destinationCrs );
122 : 0 : }
123 : :
124 : 0 : void QgsCoordinateTransformContext::removeCoordinateOperation( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs )
125 : : {
126 : 0 : d->mSourceDestDatumTransforms.remove( qMakePair( sourceCrs, destinationCrs ) );
127 : 0 : }
128 : :
129 : 0 : bool QgsCoordinateTransformContext::hasTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
130 : : {
131 : 0 : const QString t = calculateCoordinateOperation( source, destination );
132 : 0 : return !t.isEmpty();
133 : 0 : }
134 : :
135 : 0 : QgsDatumTransform::TransformPair QgsCoordinateTransformContext::calculateDatumTransforms( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
136 : : {
137 : 0 : Q_UNUSED( source )
138 : 0 : Q_UNUSED( destination )
139 : 0 : return QgsDatumTransform::TransformPair( -1, -1 );
140 : : }
141 : :
142 : 6 : QString QgsCoordinateTransformContext::calculateCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
143 : : {
144 : 6 : if ( !source.isValid() || !destination.isValid() )
145 : 0 : return QString();
146 : :
147 : 6 : d->mLock.lockForRead();
148 : 6 : QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
149 : 6 : if ( res.operation.isEmpty() )
150 : : {
151 : : // try to reverse
152 : 6 : res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
153 : 6 : }
154 : 6 : d->mLock.unlock();
155 : 6 : return res.operation;
156 : 6 : }
157 : :
158 : 6 : bool QgsCoordinateTransformContext::allowFallbackTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
159 : : {
160 : 6 : if ( !source.isValid() || !destination.isValid() )
161 : 0 : return false;
162 : :
163 : 6 : d->mLock.lockForRead();
164 : 6 : QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
165 : 6 : if ( res.operation.isEmpty() )
166 : : {
167 : : // try to reverse
168 : 6 : res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
169 : 6 : }
170 : 6 : d->mLock.unlock();
171 : 6 : return res.allowFallback;
172 : 6 : }
173 : :
174 : 6 : bool QgsCoordinateTransformContext::mustReverseCoordinateOperation( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination ) const
175 : : {
176 : 6 : if ( !source.isValid() || !destination.isValid() )
177 : 0 : return false;
178 : :
179 : 6 : d->mLock.lockForRead();
180 : 6 : QgsCoordinateTransformContextPrivate::OperationDetails res = d->mSourceDestDatumTransforms.value( qMakePair( source, destination ), QgsCoordinateTransformContextPrivate::OperationDetails() );
181 : 6 : if ( !res.operation.isEmpty() )
182 : : {
183 : 0 : d->mLock.unlock();
184 : 0 : return false;
185 : : }
186 : : // see if the reverse operation is present
187 : 6 : res = d->mSourceDestDatumTransforms.value( qMakePair( destination, source ), QgsCoordinateTransformContextPrivate::OperationDetails() );
188 : 6 : if ( !res.operation.isEmpty() )
189 : : {
190 : 0 : d->mLock.unlock();
191 : 0 : return true;
192 : : }
193 : :
194 : 6 : d->mLock.unlock();
195 : 6 : return false;
196 : 6 : }
197 : :
198 : 0 : bool QgsCoordinateTransformContext::readXml( const QDomElement &element, const QgsReadWriteContext &, QStringList &missingTransforms )
199 : : {
200 : 0 : d.detach();
201 : 0 : d->mLock.lockForWrite();
202 : :
203 : 0 : d->mSourceDestDatumTransforms.clear();
204 : :
205 : 0 : const QDomNodeList contextNodes = element.elementsByTagName( QStringLiteral( "transformContext" ) );
206 : 0 : if ( contextNodes.count() < 1 )
207 : : {
208 : 0 : d->mLock.unlock();
209 : 0 : return true;
210 : : }
211 : :
212 : 0 : missingTransforms.clear();
213 : 0 : bool result = true;
214 : :
215 : 0 : const QDomElement contextElem = contextNodes.at( 0 ).toElement();
216 : :
217 : : // src/dest transforms
218 : 0 : const QDomNodeList srcDestNodes = contextElem.elementsByTagName( QStringLiteral( "srcDest" ) );
219 : 0 : for ( int i = 0; i < srcDestNodes.size(); ++i )
220 : : {
221 : 0 : const QDomElement transformElem = srcDestNodes.at( i ).toElement();
222 : :
223 : 0 : const QDomElement srcElem = transformElem.firstChildElement( QStringLiteral( "src" ) );
224 : 0 : const QDomElement destElem = transformElem.firstChildElement( QStringLiteral( "dest" ) );
225 : :
226 : 0 : QgsCoordinateReferenceSystem srcCrs;
227 : 0 : QgsCoordinateReferenceSystem destCrs;
228 : 0 : if ( !srcElem.isNull() && !destElem.isNull() )
229 : : {
230 : 0 : srcCrs.readXml( srcElem );
231 : 0 : destCrs.readXml( destElem );
232 : 0 : }
233 : : else
234 : : {
235 : : // for older project compatibility
236 : 0 : const QString key1 = transformElem.attribute( QStringLiteral( "source" ) );
237 : 0 : const QString key2 = transformElem.attribute( QStringLiteral( "dest" ) );
238 : 0 : srcCrs = QgsCoordinateReferenceSystem( key1 );
239 : 0 : destCrs = QgsCoordinateReferenceSystem( key2 );
240 : 0 : }
241 : :
242 : 0 : if ( !srcCrs.isValid() || !destCrs.isValid() )
243 : 0 : continue;
244 : :
245 : 0 : const QString coordinateOp = transformElem.attribute( QStringLiteral( "coordinateOp" ) );
246 : 0 : const bool allowFallback = transformElem.attribute( QStringLiteral( "allowFallback" ), QStringLiteral( "1" ) ).toInt();
247 : :
248 : : // try to instantiate operation, and check for missing grids
249 : 0 : if ( !QgsProjUtils::coordinateOperationIsAvailable( coordinateOp ) )
250 : : {
251 : : // not possible in current Proj 6 api!
252 : : // QgsCoordinateTransform will alert users to this, we don't need to use missingTransforms here
253 : 0 : result = false;
254 : 0 : }
255 : :
256 : 0 : QgsCoordinateTransformContextPrivate::OperationDetails deets;
257 : 0 : deets.operation = coordinateOp;
258 : 0 : deets.allowFallback = allowFallback;
259 : 0 : d->mSourceDestDatumTransforms.insert( qMakePair( srcCrs, destCrs ), deets );
260 : 0 : }
261 : :
262 : 0 : d->mLock.unlock();
263 : 0 : return result;
264 : 0 : }
265 : :
266 : 0 : void QgsCoordinateTransformContext::writeXml( QDomElement &element, const QgsReadWriteContext & ) const
267 : : {
268 : 0 : d->mLock.lockForRead();
269 : :
270 : 0 : QDomDocument doc = element.ownerDocument();
271 : :
272 : 0 : QDomElement contextElem = doc.createElement( QStringLiteral( "transformContext" ) );
273 : :
274 : : //src/dest transforms
275 : 0 : for ( auto it = d->mSourceDestDatumTransforms.constBegin(); it != d->mSourceDestDatumTransforms.constEnd(); ++ it )
276 : : {
277 : 0 : QDomElement transformElem = doc.createElement( QStringLiteral( "srcDest" ) );
278 : 0 : QDomElement srcElem = doc.createElement( QStringLiteral( "src" ) );
279 : 0 : QDomElement destElem = doc.createElement( QStringLiteral( "dest" ) );
280 : :
281 : 0 : it.key().first.writeXml( srcElem, doc );
282 : 0 : it.key().second.writeXml( destElem, doc );
283 : :
284 : 0 : transformElem.appendChild( srcElem );
285 : 0 : transformElem.appendChild( destElem );
286 : :
287 : 0 : transformElem.setAttribute( QStringLiteral( "coordinateOp" ), it.value().operation );
288 : 0 : transformElem.setAttribute( QStringLiteral( "allowFallback" ), it.value().allowFallback ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
289 : 0 : contextElem.appendChild( transformElem );
290 : 0 : }
291 : :
292 : 0 : element.appendChild( contextElem );
293 : 0 : d->mLock.unlock();
294 : 0 : }
295 : :
296 : 8 : void QgsCoordinateTransformContext::readSettings()
297 : : {
298 : 8 : d.detach();
299 : 8 : d->mLock.lockForWrite();
300 : :
301 : 8 : d->mSourceDestDatumTransforms.clear();
302 : :
303 : 8 : QgsSettings settings;
304 : 16 : settings.beginGroup( QStringLiteral( "/Projections" ) );
305 : 8 : QStringList projectionKeys = settings.allKeys();
306 : :
307 : : //collect src and dest entries that belong together
308 : 8 : QMap< QPair< QgsCoordinateReferenceSystem, QgsCoordinateReferenceSystem >, QgsCoordinateTransformContextPrivate::OperationDetails > transforms;
309 : 8 : QStringList::const_iterator pkeyIt = projectionKeys.constBegin();
310 : 8 : for ( ; pkeyIt != projectionKeys.constEnd(); ++pkeyIt )
311 : : {
312 : 0 : if ( pkeyIt->contains( QLatin1String( "coordinateOp" ) ) )
313 : : {
314 : 0 : QStringList split = pkeyIt->split( '/' );
315 : 0 : QString srcAuthId, destAuthId;
316 : 0 : if ( ! split.isEmpty() )
317 : : {
318 : 0 : srcAuthId = split.at( 0 );
319 : 0 : }
320 : 0 : if ( split.size() > 1 )
321 : : {
322 : 0 : destAuthId = split.at( 1 ).split( '_' ).at( 0 );
323 : 0 : }
324 : :
325 : 0 : if ( srcAuthId.isEmpty() || destAuthId.isEmpty() )
326 : 0 : continue;
327 : :
328 : 0 : const QString proj = settings.value( *pkeyIt ).toString();
329 : 0 : const bool allowFallback = settings.value( QStringLiteral( "%1//%2_allowFallback" ).arg( srcAuthId, destAuthId ) ).toBool();
330 : 0 : QgsCoordinateTransformContextPrivate::OperationDetails deets;
331 : 0 : deets.operation = proj;
332 : 0 : deets.allowFallback = allowFallback;
333 : 0 : transforms[ qMakePair( QgsCoordinateReferenceSystem( srcAuthId ), QgsCoordinateReferenceSystem( destAuthId ) )] = deets;
334 : 0 : }
335 : 0 : }
336 : :
337 : : // add transforms to context
338 : 8 : auto transformIt = transforms.constBegin();
339 : 8 : for ( ; transformIt != transforms.constEnd(); ++transformIt )
340 : : {
341 : 0 : d->mSourceDestDatumTransforms.insert( transformIt.key(), transformIt.value() );
342 : 0 : }
343 : :
344 : 8 : d->mLock.unlock();
345 : 8 : settings.endGroup();
346 : 8 : }
347 : :
348 : 0 : void QgsCoordinateTransformContext::writeSettings()
349 : : {
350 : 0 : QgsSettings settings;
351 : 0 : settings.beginGroup( QStringLiteral( "/Projections" ) );
352 : 0 : QStringList groupKeys = settings.allKeys();
353 : 0 : QStringList::const_iterator groupKeyIt = groupKeys.constBegin();
354 : 0 : for ( ; groupKeyIt != groupKeys.constEnd(); ++groupKeyIt )
355 : : {
356 : 0 : if ( groupKeyIt->contains( QLatin1String( "srcTransform" ) ) || groupKeyIt->contains( QLatin1String( "destTransform" ) ) || groupKeyIt->contains( QLatin1String( "coordinateOp" ) ) )
357 : : {
358 : 0 : settings.remove( *groupKeyIt );
359 : 0 : }
360 : 0 : }
361 : :
362 : 0 : for ( auto transformIt = d->mSourceDestDatumTransforms.constBegin(); transformIt != d->mSourceDestDatumTransforms.constEnd(); ++transformIt )
363 : : {
364 : 0 : const QString srcAuthId = transformIt.key().first.authid();
365 : 0 : const QString destAuthId = transformIt.key().second.authid();
366 : :
367 : 0 : if ( srcAuthId.isEmpty() || destAuthId.isEmpty() )
368 : 0 : continue; // not so nice, but alternative would be to shove whole CRS wkt into the settings values...
369 : :
370 : 0 : const QString proj = transformIt.value().operation;
371 : 0 : const bool allowFallback = transformIt.value().allowFallback;
372 : 0 : settings.setValue( srcAuthId + "//" + destAuthId + "_coordinateOp", proj );
373 : 0 : settings.setValue( srcAuthId + "//" + destAuthId + "_allowFallback", allowFallback );
374 : 0 : }
375 : :
376 : 0 : settings.endGroup();
377 : 0 : }
|