Skip to content

Commit 3ada8a2

Browse files
committedApr 17, 2017
Use a transparent cache to avoid db lookups when setting QgsDistanceArea ellipsoid
1 parent 4925fab commit 3ada8a2

File tree

3 files changed

+200
-37
lines changed

3 files changed

+200
-37
lines changed
 

‎src/core/qgsdistancearea.cpp

Lines changed: 100 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
#define RAD2DEG(r) (180.0 * (r) / M_PI)
4747
#define POW2(x) ((x)*(x))
4848

49+
QReadWriteLock QgsDistanceArea::sEllipsoidCacheLock;
50+
QHash< QString, QgsDistanceArea::EllipsoidParameters > QgsDistanceArea::sEllipsoidCache;
4951

5052
QgsDistanceArea::QgsDistanceArea()
5153
{
@@ -83,22 +85,35 @@ void QgsDistanceArea::setSourceAuthId( const QString &authId )
8385

8486
bool QgsDistanceArea::setEllipsoid( const QString &ellipsoid )
8587
{
86-
QString radius, parameter2;
87-
//
88-
// SQLITE3 stuff - get parameters for selected ellipsoid
89-
//
90-
sqlite3 *myDatabase = nullptr;
91-
const char *myTail = nullptr;
92-
sqlite3_stmt *myPreparedStatement = nullptr;
93-
int myResult;
94-
9588
// Shortcut if ellipsoid is none.
9689
if ( ellipsoid == GEO_NONE )
9790
{
9891
mEllipsoid = GEO_NONE;
9992
return true;
10093
}
10194

95+
// check cache
96+
sEllipsoidCacheLock.lockForRead();
97+
QHash< QString, EllipsoidParameters >::const_iterator cacheIt = sEllipsoidCache.constFind( ellipsoid );
98+
if ( cacheIt != sEllipsoidCache.constEnd() )
99+
{
100+
// found a match in the cache
101+
sEllipsoidCacheLock.unlock();
102+
if ( cacheIt.value().valid )
103+
{
104+
mEllipsoid = ellipsoid;
105+
setFromParams( cacheIt.value() );
106+
return true;
107+
}
108+
else
109+
{
110+
return false;
111+
}
112+
}
113+
sEllipsoidCacheLock.unlock();
114+
115+
EllipsoidParameters params;
116+
102117
// Check if we have a custom projection, and set from text string.
103118
// Format is "PARAMETER:<semi-major axis>:<semi minor axis>
104119
// Numbers must be with (optional) decimal point and no other separators (C locale)
@@ -111,54 +126,80 @@ bool QgsDistanceArea::setEllipsoid( const QString &ellipsoid )
111126
double semiMinor = paramList[2].toDouble( & semiMinorOk );
112127
if ( semiMajorOk && semiMinorOk )
113128
{
114-
return setEllipsoid( semiMajor, semiMinor );
129+
params.semiMajor = semiMajor;
130+
params.semiMinor = semiMinor;
131+
params.useCustomParameters = true;
115132
}
116133
else
117134
{
118-
return false;
135+
params.valid = false;
119136
}
137+
138+
sEllipsoidCacheLock.lockForWrite();
139+
sEllipsoidCache.insert( ellipsoid, params );
140+
sEllipsoidCacheLock.unlock();
141+
if ( params.valid )
142+
setFromParams( params );
143+
return params.valid;
120144
}
121145

146+
// cache miss - get from database
147+
148+
QString radius, parameter2;
149+
//
150+
// SQLITE3 stuff - get parameters for selected ellipsoid
151+
//
152+
sqlite3 *database = nullptr;
153+
const char *tail = nullptr;
154+
sqlite3_stmt *preparedStatement = nullptr;
122155
// Continue with PROJ.4 list of ellipsoids.
123156

124157
//check the db is available
125-
myResult = sqlite3_open_v2( QgsApplication::srsDatabaseFilePath().toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, nullptr );
126-
if ( myResult )
158+
int result = sqlite3_open_v2( QgsApplication::srsDatabaseFilePath().toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
159+
if ( result )
127160
{
128-
QgsMessageLog::logMessage( QObject::tr( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
161+
QgsMessageLog::logMessage( QObject::tr( "Can't open database: %1" ).arg( sqlite3_errmsg( database ) ) );
129162
// XXX This will likely never happen since on open, sqlite creates the
130163
// database if it does not exist.
131164
return false;
132165
}
133166
// Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
134-
QString mySql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + '\'';
135-
myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
167+
QString sql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + '\'';
168+
result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &preparedStatement, &tail );
136169
// XXX Need to free memory from the error msg if one is set
137-
if ( myResult == SQLITE_OK )
170+
if ( result == SQLITE_OK )
138171
{
139-
if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
172+
if ( sqlite3_step( preparedStatement ) == SQLITE_ROW )
140173
{
141-
radius = QString( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 0 ) ) );
142-
parameter2 = QString( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 1 ) ) );
174+
radius = QString( reinterpret_cast< const char * >( sqlite3_column_text( preparedStatement, 0 ) ) );
175+
parameter2 = QString( reinterpret_cast< const char * >( sqlite3_column_text( preparedStatement, 1 ) ) );
143176
}
144177
}
145178
// close the sqlite3 statement
146-
sqlite3_finalize( myPreparedStatement );
147-
sqlite3_close( myDatabase );
179+
sqlite3_finalize( preparedStatement );
180+
sqlite3_close( database );
148181

149182
// row for this ellipsoid wasn't found?
150183
if ( radius.isEmpty() || parameter2.isEmpty() )
151184
{
152185
QgsDebugMsg( QString( "setEllipsoid: no row in tbl_ellipsoid for acronym '%1'" ).arg( ellipsoid ) );
186+
params.valid = false;
187+
sEllipsoidCacheLock.lockForWrite();
188+
sEllipsoidCache.insert( ellipsoid, params );
189+
sEllipsoidCacheLock.unlock();
153190
return false;
154191
}
155192

156193
// get major semiaxis
157194
if ( radius.left( 2 ) == QLatin1String( "a=" ) )
158-
mSemiMajor = radius.midRef( 2 ).toDouble();
195+
params.semiMajor = radius.midRef( 2 ).toDouble();
159196
else
160197
{
161198
QgsDebugMsg( QString( "setEllipsoid: wrong format of radius field: '%1'" ).arg( radius ) );
199+
params.valid = false;
200+
sEllipsoidCacheLock.lockForWrite();
201+
sEllipsoidCache.insert( ellipsoid, params );
202+
sEllipsoidCacheLock.unlock();
162203
return false;
163204
}
164205

@@ -167,21 +208,25 @@ bool QgsDistanceArea::setEllipsoid( const QString &ellipsoid )
167208
// second one must be computed using formula: invf = a/(a-b)
168209
if ( parameter2.left( 2 ) == QLatin1String( "b=" ) )
169210
{
170-
mSemiMinor = parameter2.midRef( 2 ).toDouble();
171-
mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
211+
params.semiMinor = parameter2.midRef( 2 ).toDouble();
212+
params.inverseFlattening = params.semiMajor / ( params.semiMajor - params.semiMinor );
172213
}
173214
else if ( parameter2.left( 3 ) == QLatin1String( "rf=" ) )
174215
{
175-
mInvFlattening = parameter2.midRef( 3 ).toDouble();
176-
mSemiMinor = mSemiMajor - ( mSemiMajor / mInvFlattening );
216+
params.inverseFlattening = parameter2.midRef( 3 ).toDouble();
217+
params.semiMinor = params.semiMajor - ( params.semiMajor / params.inverseFlattening );
177218
}
178219
else
179220
{
180221
QgsDebugMsg( QString( "setEllipsoid: wrong format of parameter2 field: '%1'" ).arg( parameter2 ) );
222+
params.valid = false;
223+
sEllipsoidCacheLock.lockForWrite();
224+
sEllipsoidCache.insert( ellipsoid, params );
225+
sEllipsoidCacheLock.unlock();
181226
return false;
182227
}
183228

184-
QgsDebugMsg( QString( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( mSemiMajor ).arg( mSemiMinor ).arg( mInvFlattening ) );
229+
QgsDebugMsg( QString( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( params.semiMajor ).arg( params.semiMinor ).arg( params.inverseFlattening ) );
185230

186231

187232
// get spatial ref system for ellipsoid
@@ -193,29 +238,31 @@ bool QgsDistanceArea::setEllipsoid( const QString &ellipsoid )
193238
// familiar with the code (should also give a more descriptive name to the generated CRS)
194239
if ( destCRS.srsid() == 0 )
195240
{
196-
QString myName = QStringLiteral( " * %1 (%2)" )
197-
.arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
198-
destCRS.toProj4() );
199-
destCRS.saveAsUserCrs( myName );
241+
QString name = QStringLiteral( " * %1 (%2)" )
242+
.arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
243+
destCRS.toProj4() );
244+
destCRS.saveAsUserCrs( name );
200245
}
201246
//
202247

203248
// set transformation from project CRS to ellipsoid coordinates
204-
mCoordTransform.setDestinationCrs( destCRS );
249+
params.crs = destCRS;
205250

206-
mEllipsoid = ellipsoid;
251+
sEllipsoidCacheLock.lockForWrite();
252+
sEllipsoidCache.insert( ellipsoid, params );
253+
sEllipsoidCacheLock.unlock();
207254

208-
// precalculate some values for area calculations
209-
computeAreaInit();
255+
mEllipsoid = ellipsoid;
210256

257+
setFromParams( params );
211258
return true;
212259
}
213260

214261
// Inverse flattening is calculated with invf = a/(a-b)
215262
// Also, b = a-(a/invf)
216263
bool QgsDistanceArea::setEllipsoid( double semiMajor, double semiMinor )
217264
{
218-
mEllipsoid = QStringLiteral( "PARAMETER:%1:%2" ).arg( semiMajor ).arg( semiMinor );
265+
mEllipsoid = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ) ).arg( qgsDoubleToString( semiMinor ) );
219266
mSemiMajor = semiMajor;
220267
mSemiMinor = semiMinor;
221268
mInvFlattening = mSemiMajor / ( mSemiMajor - mSemiMinor );
@@ -787,6 +834,22 @@ void QgsDistanceArea::computeAreaInit()
787834
m_E = -m_E;
788835
}
789836

837+
void QgsDistanceArea::setFromParams( const QgsDistanceArea::EllipsoidParameters &params )
838+
{
839+
if ( params.useCustomParameters )
840+
{
841+
setEllipsoid( params.semiMajor, params.semiMinor );
842+
}
843+
else
844+
{
845+
mSemiMajor = params.semiMajor;
846+
mSemiMinor = params.semiMinor;
847+
mInvFlattening = params.inverseFlattening;
848+
mCoordTransform.setDestinationCrs( params.crs );
849+
// precalculate some values for area calculations
850+
computeAreaInit();
851+
}
852+
}
790853

791854
double QgsDistanceArea::computePolygonArea( const QList<QgsPoint> &points ) const
792855
{

‎src/core/qgsdistancearea.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "qgis_core.h"
2020
#include <QList>
21+
#include <QReadWriteLock>
2122
#include "qgscoordinatetransform.h"
2223
#include "qgsunittypes.h"
2324

@@ -327,6 +328,31 @@ class CORE_EXPORT QgsDistanceArea
327328
*/
328329
void computeAreaInit();
329330

331+
/**
332+
* Contains parameter definitions for an ellipsoid.
333+
* \since QGIS 3.0
334+
*/
335+
struct EllipsoidParameters
336+
{
337+
//! Whether ellipsoid parameters are valid
338+
bool valid = true;
339+
340+
//! Semi-major axis
341+
double semiMajor = -1.0;
342+
//! Semi-minor axis
343+
double semiMinor = -1.0;
344+
345+
//! Whether custom parameters alone should be used (semiMajor/semiMinor only)
346+
bool useCustomParameters = false;
347+
348+
//! Inverse flattening
349+
double inverseFlattening = -1.0;
350+
//! Associated coordinate reference system
351+
QgsCoordinateReferenceSystem crs;
352+
};
353+
354+
void setFromParams( const EllipsoidParameters &params );
355+
330356
enum MeasureType
331357
{
332358
Default,
@@ -364,6 +390,10 @@ class CORE_EXPORT QgsDistanceArea
364390
double m_E; /* area of the earth */
365391
double m_TwoPI;
366392

393+
// ellipsoid cache
394+
static QReadWriteLock sEllipsoidCacheLock;
395+
static QHash< QString, EllipsoidParameters > sEllipsoidCache;
396+
367397
};
368398

369399
#endif

‎tests/src/core/testqgsdistancearea.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class TestQgsDistanceArea: public QObject
3737
void initTestCase();
3838
void cleanupTestCase();
3939
void basic();
40+
void noEllipsoid();
41+
void cache();
4042
void test_distances();
4143
void regression13601();
4244
void collections();
@@ -102,6 +104,74 @@ void TestQgsDistanceArea::basic()
102104
QCOMPARE( resultA, resultB );
103105
}
104106

107+
void TestQgsDistanceArea::noEllipsoid()
108+
{
109+
QgsDistanceArea da;
110+
da.setEllipsoidalMode( true );
111+
da.setEllipsoid( GEO_NONE );
112+
QVERIFY( !da.willUseEllipsoid() );
113+
QCOMPARE( da.ellipsoid(), GEO_NONE );
114+
}
115+
116+
void TestQgsDistanceArea::cache()
117+
{
118+
// test that ellipsoid can be retrieved correctly from cache
119+
QgsDistanceArea da;
120+
da.setEllipsoidalMode( true );
121+
122+
// warm cache
123+
QVERIFY( da.setEllipsoid( QStringLiteral( "Ganymede2000" ) ) );
124+
QVERIFY( da.willUseEllipsoid() );
125+
QCOMPARE( da.ellipsoidSemiMajor(), 2632400.0 );
126+
QCOMPARE( da.ellipsoidSemiMinor(), 2632350.0 );
127+
QCOMPARE( da.ellipsoidInverseFlattening(), 52648.0 );
128+
QCOMPARE( da.ellipsoid(), QStringLiteral( "Ganymede2000" ) );
129+
130+
// a second time, so ellipsoid is fetched from cache
131+
QgsDistanceArea da2;
132+
da2.setEllipsoidalMode( true );
133+
QVERIFY( da2.setEllipsoid( QStringLiteral( "Ganymede2000" ) ) );
134+
QVERIFY( da2.willUseEllipsoid() );
135+
QCOMPARE( da2.ellipsoidSemiMajor(), 2632400.0 );
136+
QCOMPARE( da2.ellipsoidSemiMinor(), 2632350.0 );
137+
QCOMPARE( da2.ellipsoidInverseFlattening(), 52648.0 );
138+
QCOMPARE( da2.ellipsoid(), QStringLiteral( "Ganymede2000" ) );
139+
140+
// using parameters
141+
QgsDistanceArea da3;
142+
da3.setEllipsoidalMode( true );
143+
QVERIFY( da3.setEllipsoid( QStringLiteral( "PARAMETER:2631400:2341350" ) ) );
144+
QVERIFY( da3.willUseEllipsoid() );
145+
QCOMPARE( da3.ellipsoidSemiMajor(), 2631400.0 );
146+
QCOMPARE( da3.ellipsoidSemiMinor(), 2341350.0 );
147+
QGSCOMPARENEAR( da3.ellipsoidInverseFlattening(), 9.07223, 0.00001 );
148+
QCOMPARE( da3.ellipsoid(), QStringLiteral( "PARAMETER:2631400:2341350" ) );
149+
150+
// again, to check parameters with cache
151+
QgsDistanceArea da4;
152+
da4.setEllipsoidalMode( true );
153+
QVERIFY( da4.setEllipsoid( QStringLiteral( "PARAMETER:2631400:2341350" ) ) );
154+
QVERIFY( da4.willUseEllipsoid() );
155+
QCOMPARE( da4.ellipsoidSemiMajor(), 2631400.0 );
156+
QCOMPARE( da4.ellipsoidSemiMinor(), 2341350.0 );
157+
QGSCOMPARENEAR( da4.ellipsoidInverseFlattening(), 9.07223, 0.00001 );
158+
QCOMPARE( da4.ellipsoid(), QStringLiteral( "PARAMETER:2631400:2341350" ) );
159+
160+
// invalid
161+
QgsDistanceArea da5;
162+
da5.setEllipsoidalMode( true );
163+
QVERIFY( !da5.setEllipsoid( QStringLiteral( "MyFirstEllipsoid" ) ) );
164+
QVERIFY( !da5.willUseEllipsoid() );
165+
QCOMPARE( da5.ellipsoid(), GEO_NONE );
166+
167+
// invalid again, should be cached
168+
QgsDistanceArea da6;
169+
da6.setEllipsoidalMode( true );
170+
QVERIFY( !da6.setEllipsoid( QStringLiteral( "MyFirstEllipsoid" ) ) );
171+
QVERIFY( !da6.willUseEllipsoid() );
172+
QCOMPARE( da6.ellipsoid(), GEO_NONE );
173+
}
174+
105175
void TestQgsDistanceArea::test_distances()
106176
{
107177
// Read the file of Geod data

0 commit comments

Comments
 (0)
Please sign in to comment.