Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 25, 2016
1 parent bb220a0 commit ac36cb5
Show file tree
Hide file tree
Showing 6 changed files with 405 additions and 38 deletions.
3 changes: 3 additions & 0 deletions doc/api_break.dox
Expand Up @@ -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>
Expand Down
30 changes: 15 additions & 15 deletions python/core/qgscoordinatereferencesystem.sip
Expand Up @@ -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
* =======
Expand All @@ -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
{
Expand Down Expand Up @@ -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())
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand All @@ -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
*/
Expand Down Expand Up @@ -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();
};
2 changes: 2 additions & 0 deletions src/app/qgscustomprojectiondialog.cpp
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
Expand Down
164 changes: 159 additions & 5 deletions src/core/qgscoordinatereferencesystem.cpp
Expand Up @@ -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()
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -177,6 +201,10 @@ bool QgsCoordinateReferenceSystem::createFromString( const QString &theDefinitio
}
}
}

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

Expand Down Expand Up @@ -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 );
Expand All @@ -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 ||
Expand All @@ -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;
}

Expand All @@ -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 )
Expand Down Expand Up @@ -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();
Expand All @@ -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;
}

Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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;
}

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

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

return d->mIsValid;
}

Expand Down Expand Up @@ -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.