Skip to content
Permalink
Browse files

Make QgsCoordinateReferenceSystem use an internal cache

for initializing CRS objects.

This avoids the need for the separate QgsCRSCache class,
and means that the caching benefits are available without the
need for calling methods from QgsCrsCache.
  • Loading branch information
nyalldawson committed Jul 23, 2016
1 parent bb220a0 commit ac36cb5dd208d338b20deac1ca25cbdb28ad7859
@@ -103,6 +103,9 @@ qgsPermissiveToInt()</li>
\subsection qgis_api_break_3_0_QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem

<ul>
<li>QgsCoordinateReferenceSystem now uses internal caches to avoid expensive database lookups
when CRS objects are initialized. This is handled internally, but invalidateCache() must be
called if changes are made to the CRS database.</li>
<li>setCustomSrsValidation() has been renamed to setCustomCrsValidation()</li>
<li>saveAsUserCRS() has been renamed to saveAsUserCrs()</li>
<li>geographicCRSAuthId() has been renamed to geographicCrsAuthId()</li>
@@ -119,19 +119,12 @@
* constructor that automatically recognizes definition format from the given string.
*
* Creation of CRS object involves some queries in a local SQLite database, which may
* be potentially expensive. It is therefore recommended to use QgsCRSCache methods
* that return possibly cached CRS objects instead of constructing new instances that
* involve the lookup overhead.
* be potentially expensive. Consequently, CRS creation methods use an internal cache to avoid
* unnecessary database lookups. If the CRS database is modified, then it is necessary to call
* invalidateCache() to ensure that outdated records are not being returned from the cache.
*
* Since QGIS 2.16 QgsCoordinateReferenceSystem objects are implicitly shared.
*
* The following table summarizes equivalents for non-cached and cached CRS lookup:
*
* Definition | Non-cached | Cached
* ----------- | --------------------- | ------------------------------
* Auth + Code | createFromOgcWmsCrs() | QgsCRSCache::crsByOgcWmsCrs()
* PROJ.4 | createFromProj4() | QgsCRSCache::crsByProj4()
* WKT | createFromWkt() | QgsCRSCache::crsByWkt()
*
* Caveats
* =======
@@ -140,7 +133,7 @@
* used by ESRI. They look very similar, but they are not the same. QGIS is able to consume
* both flavours.
*
* \see QgsCoordinateTransform, QgsCRSCache
* \see QgsCoordinateTransform
*/
class QgsCoordinateReferenceSystem
{
@@ -248,7 +241,7 @@ class QgsCoordinateReferenceSystem
* Accepts both "<auth>:<code>" format and OGC URN "urn:ogc:def:crs:<auth>:[<version>]:<code>".
* It also recognizes "QGIS", "USER", "CUSTOM" authorities, which all have the same meaning
* and refer to QGIS internal CRS IDs.
* @note this method is expensive. Consider using QgsCRSCache::crsByOgcWmsCrs() instead.
* @note this method uses an internal cache. Call invalidateCache() to clear the cache.
* @return True on success else false
*/
// TODO QGIS 3: remove "QGIS" and "CUSTOM", only support "USER" (also returned by authid())
@@ -268,7 +261,7 @@ class QgsCoordinateReferenceSystem
* Otherwise the WKT will be converted to a proj4 string and createFromProj4()
* set up the object.
* @note Some members may be left blank if no match can be found in CRS database.
* @note this method is expensive. Consider using QgsCRSCache::crsByWkt() instead.
* @note this method uses an internal cache. Call invalidateCache() to clear the cache.
* @param theWkt The WKT for the desired spatial reference system.
* @return True on success else false
*/
@@ -278,7 +271,7 @@ class QgsCoordinateReferenceSystem
*
* If the srsid is < USER_CRS_START_ID, system CRS database is used, otherwise
* user's local CRS database from home directory is used.
* @note this method is expensive. Consider using QgsCRSCache::crsBySrsId() instead.
* @note this method uses an internal cache. Call invalidateCache() to clear the cache.
* @param theSrsId The internal QGIS CRS ID for the desired spatial reference system.
* @return True on success else false
*/
@@ -301,7 +294,7 @@ class QgsCoordinateReferenceSystem
* - if none of the above match, use findMatchingProj()
*
* @note Some members may be left blank if no match can be found in CRS database.
* @note this method is expensive. Consider using QgsCRSCache::crsByProj4() instead.
* @note this method uses an internal cache. Call invalidateCache() to clear the cache.
* @param theProjString A proj4 format string
* @return True on success else false
*/
@@ -519,4 +512,11 @@ class QgsCoordinateReferenceSystem
* @note added in QGIS 2.7
*/
static QStringList recentProjections();

/** Clears the internal cache used to initialise QgsCoordinateReferenceSystem objects.
* This should be called whenever the srs database has been modified in order to ensure
* that outdated CRS objects are not created.
* @note added in QGIS 3.0
*/
static void invalidateCache();
};
@@ -167,6 +167,7 @@ bool QgsCustomProjectionDialog::deleteCrs( const QString& id )
sqlite3_close( myDatabase );

QgsCrsCache::instance()->updateCrsCache( QString( "USER:%1" ).arg( id ) );
QgsCoordinateReferenceSystem::invalidateCache();

return myResult == SQLITE_OK;
}
@@ -292,6 +293,7 @@ bool QgsCustomProjectionDialog::saveCrs( QgsCoordinateReferenceSystem myCRS, con
existingCRSnames[myId] = myName;

QgsCrsCache::instance()->updateCrsCache( QString( "USER:%1" ).arg( myId ) );
QgsCoordinateReferenceSystem::invalidateCache();

// If we have a projection acronym not in the user db previously, add it.
// This is a must, or else we can't select it from the vw_srs table.
@@ -48,6 +48,19 @@

CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::mCustomSrsValidation = nullptr;

QReadWriteLock QgsCoordinateReferenceSystem::mSrIdCacheLock;
QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mSrIdCache;
QReadWriteLock QgsCoordinateReferenceSystem::mOgcLock;
QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mOgcCache;
QReadWriteLock QgsCoordinateReferenceSystem::mProj4CacheLock;
QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mProj4Cache;
QReadWriteLock QgsCoordinateReferenceSystem::mCRSWktLock;
QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mWktCache;
QReadWriteLock QgsCoordinateReferenceSystem::mCRSSrsIdLock;
QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mSrsIdCache;
QReadWriteLock QgsCoordinateReferenceSystem::mCrsStringLock;
QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::mStringCache;

//--------------------------

QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem()
@@ -138,6 +151,17 @@ bool QgsCoordinateReferenceSystem::createFromId( const long theId, CrsType theTy

bool QgsCoordinateReferenceSystem::createFromString( const QString &theDefinition )
{
mCrsStringLock.lockForRead();
QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = mStringCache.constFind( theDefinition );
if ( crsIt != mStringCache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mCrsStringLock.unlock();
return true;
}
mCrsStringLock.unlock();

bool result = false;
QRegExp reCrsId( "^(epsg|postgis|internal)\\:(\\d+)$", Qt::CaseInsensitive );
if ( reCrsId.indexIn( theDefinition ) == 0 )
@@ -177,6 +201,10 @@ bool QgsCoordinateReferenceSystem::createFromString( const QString &theDefinitio
}
}
}

mCrsStringLock.lockForWrite();
mStringCache.insert( theDefinition, *this );
mCrsStringLock.unlock();
return result;
}

@@ -232,6 +260,17 @@ void QgsCoordinateReferenceSystem::setupESRIWktFix()

bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString& theCrs )
{
mOgcLock.lockForRead();
QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = mOgcCache.constFind( theCrs );
if ( crsIt != mOgcCache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mOgcLock.unlock();
return true;
}
mOgcLock.unlock();

QString wmsCrs = theCrs;

QRegExp re( "urn:ogc:def:crs:([^:]+).+([^:]+)", Qt::CaseInsensitive );
@@ -244,12 +283,20 @@ bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString& theCrs )
re.setPattern( "(user|custom|qgis):(\\d+)" );
if ( re.exactMatch( wmsCrs ) && createFromSrsId( re.cap( 2 ).toInt() ) )
{
mOgcLock.lockForWrite();
mOgcCache.insert( theCrs, *this );
mOgcLock.unlock();
return true;
}
}

if ( loadFromDb( QgsApplication::srsDbFilePath(), "lower(auth_name||':'||auth_id)", wmsCrs.toLower() ) )
{
mOgcLock.lockForWrite();
mOgcCache.insert( theCrs, *this );
mOgcLock.unlock();
return true;
}

// NAD27
if ( wmsCrs.compare( "CRS:27", Qt::CaseInsensitive ) == 0 ||
@@ -274,9 +321,17 @@ bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString& theCrs )
d.detach();
createFromOgcWmsCrs( "EPSG:4326" );
d->mAxisInverted = 0;

mOgcLock.lockForWrite();
mOgcCache.insert( theCrs, *this );
mOgcLock.unlock();

return d->mIsValid;
}

mOgcLock.lockForWrite();
mOgcCache.insert( theCrs, QgsCoordinateReferenceSystem() );
mOgcLock.unlock();
return false;
}

@@ -302,14 +357,48 @@ void QgsCoordinateReferenceSystem::validate()

bool QgsCoordinateReferenceSystem::createFromSrid( long id )
{
return loadFromDb( QgsApplication::srsDbFilePath(), "srid", QString::number( id ) );
mSrIdCacheLock.lockForRead();
QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = mSrIdCache.constFind( id );
if ( crsIt != mSrIdCache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mSrIdCacheLock.unlock();
return true;
}
mSrIdCacheLock.unlock();

bool result = loadFromDb( QgsApplication::srsDbFilePath(), "srid", QString::number( id ) );

mSrIdCacheLock.lockForWrite();
mSrIdCache.insert( id, *this );
mSrIdCacheLock.unlock();

return result;
}

bool QgsCoordinateReferenceSystem::createFromSrsId( long id )
{
return loadFromDb( id < USER_CRS_START_ID ? QgsApplication::srsDbFilePath() :
QgsApplication::qgisUserDbFilePath(),
"srs_id", QString::number( id ) );
mCRSSrsIdLock.lockForRead();
QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = mSrsIdCache.constFind( id );
if ( crsIt != mSrsIdCache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mCRSSrsIdLock.unlock();
return true;
}
mCRSSrsIdLock.unlock();

bool result = loadFromDb( id < USER_CRS_START_ID ? QgsApplication::srsDbFilePath() :
QgsApplication::qgisUserDbFilePath(),
"srs_id", QString::number( id ) );

mCRSSrsIdLock.lockForWrite();
mSrsIdCache.insert( id, *this );
mCRSSrsIdLock.unlock();

return result;
}

bool QgsCoordinateReferenceSystem::loadFromDb( const QString& db, const QString& expression, const QString& value )
@@ -428,6 +517,17 @@ bool QgsCoordinateReferenceSystem::createFromWkt( const QString &theWkt )
{
d.detach();

mCRSWktLock.lockForRead();
QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = mWktCache.constFind( theWkt );
if ( crsIt != mWktCache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mCRSWktLock.unlock();
return true;
}
mCRSWktLock.unlock();

d->mIsValid = false;
d->mWkt.clear();
d->mProj4.clear();
@@ -450,6 +550,10 @@ bool QgsCoordinateReferenceSystem::createFromWkt( const QString &theWkt )
QgsDebugMsg( "INPUT: " + theWkt );
QgsDebugMsg( QString( "UNUSED WKT: %1" ).arg( pWkt ) );
QgsDebugMsg( "---------------------------------------------------------------\n" );

mCRSWktLock.lockForWrite();
mWktCache.insert( theWkt, *this );
mCRSWktLock.unlock();
return d->mIsValid;
}

@@ -459,7 +563,11 @@ bool QgsCoordinateReferenceSystem::createFromWkt( const QString &theWkt )
.arg( OSRGetAuthorityName( d->mCRS, nullptr ),
OSRGetAuthorityCode( d->mCRS, nullptr ) );
QgsDebugMsg( "authid recognized as " + authid );
return createFromOgcWmsCrs( authid );
bool result = createFromOgcWmsCrs( authid );
mCRSWktLock.lockForWrite();
mWktCache.insert( theWkt, *this );
mCRSWktLock.unlock();
return result;
}

// always morph from esri as it doesn't hurt anything
@@ -498,6 +606,10 @@ bool QgsCoordinateReferenceSystem::createFromWkt( const QString &theWkt )

CPLFree( proj4src );

mCRSWktLock.lockForWrite();
mWktCache.insert( theWkt, *this );
mCRSWktLock.unlock();

return d->mIsValid;
//setMapunits will be called by createfromproj above
}
@@ -511,6 +623,17 @@ bool QgsCoordinateReferenceSystem::createFromProj4( const QString &theProj4Strin
{
d.detach();

mProj4CacheLock.lockForRead();
QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = mProj4Cache.constFind( theProj4String );
if ( crsIt != mProj4Cache.constEnd() )
{
// found a match in the cache
*this = crsIt.value();
mProj4CacheLock.unlock();
return true;
}
mProj4CacheLock.unlock();

//
// Examples:
// +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
@@ -529,6 +652,11 @@ bool QgsCoordinateReferenceSystem::createFromProj4( const QString &theProj4Strin
if ( myStart == -1 )
{
QgsDebugMsg( "proj string supplied has no +proj argument" );

mProj4CacheLock.lockForWrite();
mProj4Cache.insert( theProj4String, *this );
mProj4CacheLock.unlock();

return d->mIsValid;
}

@@ -694,6 +822,10 @@ bool QgsCoordinateReferenceSystem::createFromProj4( const QString &theProj4Strin
setProj4String( myProj4String );
}

mProj4CacheLock.lockForWrite();
mProj4Cache.insert( theProj4String, *this );
mProj4CacheLock.unlock();

return d->mIsValid;
}

@@ -2215,3 +2347,25 @@ QStringList QgsCoordinateReferenceSystem::recentProjections()
}
return projections;
}

void QgsCoordinateReferenceSystem::invalidateCache()
{
mSrIdCacheLock.lockForWrite();
mSrIdCache.clear();
mSrIdCacheLock.unlock();
mOgcLock.lockForWrite();
mOgcCache.clear();
mOgcLock.unlock();
mProj4CacheLock.lockForWrite();
mProj4Cache.clear();
mProj4CacheLock.unlock();
mCRSWktLock.lockForWrite();
mWktCache.clear();
mCRSWktLock.unlock();
mCRSSrsIdLock.lockForWrite();
mSrsIdCache.clear();
mCRSSrsIdLock.unlock();
mCrsStringLock.lockForWrite();
mStringCache.clear();
mCrsStringLock.unlock();
}

0 comments on commit ac36cb5

Please sign in to comment.
You can’t perform that action at this time.