Brendan Morley's postgis threaded extents patch

git-svn-id: http://svn.osgeo.org/qgis/trunk/qgis@2935 c8812cc2-4d05-0410-92ff-de0c093fc19c
This commit is contained in:
gsherman 2005-03-10 05:08:39 +00:00
parent 59bffa930a
commit ffbf03c76f
3 changed files with 298 additions and 62 deletions

View File

@ -1,17 +1,39 @@
INCLUDES = -I../../src INCLUDES = -I../../src
%.moc.cpp: %.h
$(MOC) -o $@ $<
plugindir = ${pkglibdir} plugindir = ${pkglibdir}
plugin_LTLIBRARIES = postgresprovider.la plugin_LTLIBRARIES = postgresprovider.la
postgresprovider_la_SOURCES = qgspostgresprovider.cpp \ ## non-uic files generated from MOC
qgspostgresprovider.h \
../../src/qgsfeature.cpp \
../../src/qgsfeatureattribute.cpp \
../../src/qgsrect.cpp \
../../src/qgspoint.cpp \
../../src/qgsfield.cpp
postgresprovider_MOC = qgspostgresprovider.moc.cpp
postgresprovider_la_SOURCES = \
qgspostgresprovider.cpp \
qgspostgresprovider.moc.cpp \
qgspostgresprovider.h \
../../src/qgsfeature.cpp \
../../src/qgsfeatureattribute.cpp \
../../src/qgsrect.cpp \
../../src/qgspoint.cpp \
../../src/qgsfield.cpp \
qgspostgisbox2d.cpp \
qgspostgisbox2d.h \
qgspostgisbox3d.cpp \
qgspostgisbox3d.h \
qgspostgrescountthread.cpp \
qgspostgrescountthread.h \
qgspostgresextentthread.cpp \
qgspostgresextentthread.h
BUILT_SOURCES = $(postgresprovider_MOC)
postgresprovider_la_LIBADD = $(QT_LDADD) $(PG_LIB) $(GEOS_LDADD) postgresprovider_la_LIBADD = $(QT_LDADD) $(PG_LIB) $(GEOS_LDADD)
postgresprovider_la_LDFLAGS = -avoid-version -module postgresprovider_la_LDFLAGS = -avoid-version -module
postgresprovider_la_CXXFLAGS = $(EXTRA_CXXFLAGS) $(QT_CXXFLAGS) -I$(PG_INC) $(DEBUG_QGIS) $(GEOS_CFLAGS) postgresprovider_la_CXXFLAGS = $(EXTRA_CXXFLAGS) $(QT_CXXFLAGS) -I$(PG_INC) $(DEBUG_QGIS) $(GEOS_CFLAGS)
CLEANFILES = $(BUILT_SOURCES)

View File

@ -39,7 +39,16 @@
#include "../../src/qgsfield.h" #include "../../src/qgsfield.h"
#include "../../src/qgsrect.h" #include "../../src/qgsrect.h"
#include "../../src/qgsprovidercountcalcevent.h"
#include "../../src/qgsproviderextentcalcevent.h"
#include "qgspostgresprovider.h" #include "qgspostgresprovider.h"
#include "qgspostgrescountthread.h"
#include "qgspostgresextentthread.h"
#include "qgspostgisbox3d.h"
#ifdef WIN32 #ifdef WIN32
#define QGISEXTERN extern "C" __declspec( dllexport ) #define QGISEXTERN extern "C" __declspec( dllexport )
#else #else
@ -230,6 +239,29 @@ QgsPostgresProvider::QgsPostgresProvider(QString uri):dataSourceUri(uri)
getPrimaryKey(); getPrimaryKey();
selectSQL += " from " + tableName; selectSQL += " from " + tableName;
//--std::cout << "selectSQL: " << (const char *)selectSQL << std::endl; //--std::cout << "selectSQL: " << (const char *)selectSQL << std::endl;
// Kick off the long running threads
std::cout << "QgsPostgresProvider: About to touch mExtentThread" << std::endl;
mExtentThread.setConnInfo( connInfo );
mExtentThread.setTableName( tableName );
mExtentThread.setSqlWhereClause( sqlWhereClause );
mExtentThread.setGeometryColumn( geometryColumn );
mExtentThread.setCallback( this );
std::cout << "QgsPostgresProvider: About to start mExtentThread" << std::endl;
mExtentThread.start();
std::cout << "QgsPostgresProvider: Main thread just dispatched mExtentThread" << std::endl;
std::cout << "QgsPostgresProvider: About to touch mCountThread" << std::endl;
mCountThread.setConnInfo( connInfo );
mCountThread.setTableName( tableName );
mCountThread.setSqlWhereClause( sqlWhereClause );
mCountThread.setGeometryColumn( geometryColumn );
mCountThread.setCallback( this );
std::cout << "QgsPostgresProvider: About to start mCountThread" << std::endl;
mCountThread.start();
std::cout << "QgsPostgresProvider: Main thread just dispatched mCountThread" << std::endl;
} else { } else {
// the table is not a geometry table // the table is not a geometry table
@ -270,7 +302,27 @@ QgsPostgresProvider::QgsPostgresProvider(QString uri):dataSourceUri(uri)
QgsPostgresProvider::~QgsPostgresProvider() QgsPostgresProvider::~QgsPostgresProvider()
{ {
std::cout << "QgsPostgresProvider: About to wait for mExtentThread" << std::endl;
mExtentThread.wait();
std::cout << "QgsPostgresProvider: Finished waiting for mExtentThread" << std::endl;
std::cout << "QgsPostgresProvider: About to wait for mCountThread" << std::endl;
mCountThread.wait();
std::cout << "QgsPostgresProvider: Finished waiting for mCountThread" << std::endl;
// Make sure all events from threads have been processed
// (otherwise they will get destroyed prematurely)
QApplication::sendPostedEvents(this, QGis::ProviderExtentCalcEvent);
QApplication::sendPostedEvents(this, QGis::ProviderCountCalcEvent);
PQfinish(connection); PQfinish(connection);
std::cout << "QgsPostgresProvider: deconstructing." << std::endl;
//pLog.flush(); //pLog.flush();
} }
@ -433,7 +485,7 @@ QgsFeature* QgsPostgresProvider::getNextFeature(std::list<int> const & attlist)
{ {
// XXX I'm assuming swapping from big-endian, or network, byte order to little endian // XXX I'm assuming swapping from big-endian, or network, byte order to little endian
#ifdef QGISDEBUG #ifdef QGISDEBUG
qWarning("swapping endian for oid"); //XXX TOO MUCH OUTPUT!!! qWarning("swapping endian for oid");
#endif #endif
// convert oid to opposite endian // convert oid to opposite endian
// XXX "Opposite?" Umm, that's not enough information. // XXX "Opposite?" Umm, that's not enough information.
@ -499,8 +551,21 @@ void QgsPostgresProvider::select(QgsRect * rect, bool useIntersect)
std::cout << "Binary cursor: " << declare << std::endl; std::cout << "Binary cursor: " << declare << std::endl;
#endif #endif
if(useIntersect){ if(useIntersect){
declare += " where intersects(" + geometryColumn; // declare += " where intersects(" + geometryColumn;
declare += ", setsrid('BOX3D(" + rect->stringRep(); // declare += ", GeometryFromText('BOX3D(" + rect->stringRep();
// declare += ")'::box3d,";
// declare += srid;
// declare += "))";
// Contributed by #qgis irc "creeping"
// This version actually invokes PostGIS's use of spatial indexes
declare += " where " + geometryColumn;
declare += " && GeometryFromText('BOX3D(" + rect->stringRep();
declare += ")'::box3d,";
declare += srid;
declare += ")";
declare += " and intersects(" + geometryColumn;
declare += ", GeometryFromText('BOX3D(" + rect->stringRep();
declare += ")'::box3d,"; declare += ")'::box3d,";
declare += srid; declare += srid;
declare += "))"; declare += "))";
@ -576,6 +641,13 @@ return gPtr;
} */ } */
void QgsPostgresProvider::setExtent( QgsRect* newExtent )
{
layerExtent.setXmax( newExtent->xMax() );
layerExtent.setXmin( newExtent->xMin() );
layerExtent.setYmax( newExtent->yMax() );
layerExtent.setYmin( newExtent->yMin() );
}
// TODO - make this function return the real extent_ // TODO - make this function return the real extent_
QgsRect *QgsPostgresProvider::extent() QgsRect *QgsPostgresProvider::extent()
@ -1190,35 +1262,62 @@ void QgsPostgresProvider::setSubsetString(QString theSQL)
calculateExtents(); calculateExtents();
} }
long QgsPostgresProvider::getFeatureCount() long QgsPostgresProvider::getFeatureCount()
{ {
// get total number of features // get total number of features
QString sql = "select count(*) from " + tableName;
if(sqlWhereClause.length() > 0) // First get an approximate count; then delegate to
{ // a thread the task of getting the full count.
sql += " where " + sqlWhereClause;
} QString sql = "select reltuples from pg_catalog.pg_class where relname = '" +
tableName + "'";
std::cerr << "QgsPostgresProvider: Running SQL: " <<
sql << std::endl;
//QString sql = "select count(*) from " + tableName;
//if(sqlWhereClause.length() > 0)
//{
// sql += " where " + sqlWhereClause;
//}
PGresult *result = PQexec(connection, (const char *) sql); PGresult *result = PQexec(connection, (const char *) sql);
#ifdef QGISDEBUG
std::cerr << "QgsPostgresProvider: Approximate Number of features as text: " <<
PQgetvalue(result, 0, 0) << std::endl;
#endif
numberFeatures = QString(PQgetvalue(result, 0, 0)).toLong(); numberFeatures = QString(PQgetvalue(result, 0, 0)).toLong();
PQclear(result); PQclear(result);
#ifdef QGISDEBUG #ifdef QGISDEBUG
std::cerr << "Number of features: " << numberFeatures << std::endl; std::cerr << "QgsPostgresProvider: Approximate Number of features: " <<
numberFeatures << std::endl;
#endif #endif
return numberFeatures; return numberFeatures;
} }
// TODO: use the estimateExtents procedure of PostGIS and PostgreSQL 8
// This tip thanks to #qgis irc nick "creeping"
void QgsPostgresProvider::calculateExtents() void QgsPostgresProvider::calculateExtents()
{ {
// get the extents // get the approximate extent by retreiving the bounding box
// of the first few items with a geometry
QString sql = "select extent(" + geometryColumn + ") from " + tableName; QString sql = "select box3d(" + geometryColumn + ") from " + tableName +
" where ";
if(sqlWhereClause.length() > 0) if(sqlWhereClause.length() > 0)
{ {
sql += " where " + sqlWhereClause; sql += "(" + sqlWhereClause + ") and ";
} }
sql += "not IsEmpty(" + geometryColumn + ") limit 5";
#if WASTE_TIME #if WASTE_TIME
sql = "select xmax(extent(" + geometryColumn + ")) as xmax," sql = "select xmax(extent(" + geometryColumn + ")) as xmax,"
"xmin(extent(" + geometryColumn + ")) as xmin," "xmin(extent(" + geometryColumn + ")) as xmin,"
@ -1226,50 +1325,108 @@ void QgsPostgresProvider::calculateExtents()
#endif #endif
#ifdef QGISDEBUG #ifdef QGISDEBUG
qDebug("+++++++++QgsPostgresProvider::calculateExtents - Getting extents using schema.table: " + sql); qDebug("QgsPostgresProvider::calculateExtents - Getting approximate extent using: '" + sql + "'");
#endif #endif
PGresult *result = PQexec(connection, (const char *) sql); PGresult *result = PQexec(connection, (const char *) sql);
std::string box3d = PQgetvalue(result, 0, 0);
// TODO: Guard against the result having no rows
if (box3d != "")
for (int i = 0; i < PQntuples(result); i++)
{ {
std::string s; std::string box3d = PQgetvalue(result, i, 0);
box3d = box3d.substr(box3d.find_first_of("(")+1); if (0 == i)
box3d = box3d.substr(box3d.find_first_not_of(" ")); {
s = box3d.substr(0, box3d.find_first_of(" ")); // create the initial extent
double minx = strtod(s.c_str(), NULL); layerExtent = QgsPostGisBox3d(box3d);
}
box3d = box3d.substr(box3d.find_first_of(" ")+1); else
s = box3d.substr(0, box3d.find_first_of(" ")); {
double miny = strtod(s.c_str(), NULL); // extend the initial extent
QgsPostGisBox3d b = QgsPostGisBox3d(box3d);
box3d = box3d.substr(box3d.find_first_of(",")+1);
box3d = box3d.substr(box3d.find_first_not_of(" ")); layerExtent.combineExtentWith( &b );
s = box3d.substr(0, box3d.find_first_of(" ")); }
double maxx = strtod(s.c_str(), NULL);
std::cout << "QgsPostgresProvider: After row " << i << ", extent is: "
box3d = box3d.substr(box3d.find_first_of(" ")+1); << layerExtent.xMin() << ", " << layerExtent.yMin() <<
s = box3d.substr(0, box3d.find_first_of(" ")); " " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
double maxy = strtod(s.c_str(), NULL);
layerExtent.setXmax(maxx);
layerExtent.setXmin(minx);
layerExtent.setYmax(maxy);
layerExtent.setYmin(miny);
#ifdef QGISDEBUG
QString xMsg;
QTextOStream(&xMsg).precision(18);
QTextOStream(&xMsg).width(18);
QTextOStream(&xMsg) << "Set extents to: " << layerExtent.
xMin() << ", " << layerExtent.yMin() << " " << layerExtent.xMax() << ", " << layerExtent.yMax();
std::cerr << xMsg << std::endl;
#endif
// clear query result
PQclear(result);
} }
#ifdef QGISDEBUG
QString xMsg;
QTextOStream(&xMsg).precision(18);
QTextOStream(&xMsg).width(18);
QTextOStream(&xMsg) << "QgsPostgresProvider: Set extents to: " << layerExtent.
xMin() << ", " << layerExtent.yMin() << " " << layerExtent.xMax() << ", " << layerExtent.yMax();
std::cerr << xMsg << std::endl;
#endif
std::cout << "QgsPostgresProvider: Set limit 5 extents to: "
<< layerExtent.xMin() << ", " << layerExtent.yMin() <<
" " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
// clear query result
PQclear(result);
} }
/**
* Event sink for events from threads
*/
void QgsPostgresProvider::customEvent( QCustomEvent * e )
{
std::cout << "QgsPostgresProvider: received a custom event " << e->type() << std::endl;
switch ( e->type() )
{
case (QEvent::Type) QGis::ProviderExtentCalcEvent:
std::cout << "QgsPostgresProvider: extent has been calculated" << std::endl;
// Collect the new extent from the event and set this layer's
// extent with it.
setExtent( (QgsRect*) e->data() );
std::cout << "QgsPostgresProvider: new extent has been saved" << std::endl;
std::cout << "QgsPostgresProvider: Set extent to: "
<< layerExtent.xMin() << ", " << layerExtent.yMin() <<
" " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl;
std::cout << "QgsPostgresProvider: emitting fullExtentCalculated()" << std::endl;
emit fullExtentCalculated();
// TODO: Only uncomment this when the overview map canvas has been subclassed
// from the QgsMapCanvas
// std::cout << "QgsPostgresProvider: emitting repaintRequested()" << std::endl;
// emit repaintRequested();
break;
case (QEvent::Type) QGis::ProviderCountCalcEvent:
std::cout << "QgsPostgresProvider: count has been calculated" << std::endl;
QgsProviderCountCalcEvent* e1 = (QgsProviderCountCalcEvent*) e;
long numberFeatures = e1->numberFeatures();
std::cout << "QgsPostgresProvider: count is " << numberFeatures << std::endl;
break;
}
std::cout << "QgsPostgresProvider: Finished processing custom event " << e->type() << std::endl;
}
bool QgsPostgresProvider::deduceEndian() bool QgsPostgresProvider::deduceEndian()
{ {
// need to store the PostgreSQL endian format used in binary cursors // need to store the PostgreSQL endian format used in binary cursors
@ -1391,7 +1548,9 @@ bool QgsPostgresProvider::getGeometryDetails()
#endif #endif
return valid; return valid;
} }
/** /**
* Class factory to return a pointer to a newly created * Class factory to return a pointer to a newly created
* QgsPostgresProvider object * QgsPostgresProvider object

View File

@ -1,5 +1,5 @@
/*************************************************************************** /***************************************************************************
qgspostgresprovider.h - Data provider for PostgrSQL/PostGIS layers qgspostgresprovider.h - Data provider for PostgreSQL/PostGIS layers
------------------- -------------------
begin : Jan 2, 2004 begin : Jan 2, 2004
copyright : (C) 2003 by Gary E.Sherman copyright : (C) 2003 by Gary E.Sherman
@ -15,13 +15,21 @@
* * * *
***************************************************************************/ ***************************************************************************/
/* $Id$ */ /* $Id$ */
#ifndef QGSPOSTGRESPROVIDER_H
#define QGSPOSTGRESPROVIDER_H
extern "C" extern "C"
{ {
#include <libpq-fe.h> #include <libpq-fe.h>
} }
#include "../../src/qgsvectordataprovider.h" #include "../../src/qgsvectordataprovider.h"
#include "../../src/qgsrect.h"
#include <list> #include <list>
#include<qstring.h> #include <fstream>
#include <qstring.h>
#include <qobject.h>
//#include <qmutex.h>
class QgsFeature; class QgsFeature;
class QgsField; class QgsField;
@ -29,9 +37,12 @@ class OGRDataSource;
class OGRLayer; class OGRLayer;
#include "qgsdatasourceuri.h" #include "qgsdatasourceuri.h"
#include "qgspostgrescountthread.h"
#include "qgspostgresextentthread.h"
/** /**
\class QgsPostgresProvider \class QgsPostgresProvider
\brief Data provider for PostgrSQL/PostGIS layers. \brief Data provider for PostgreSQL/PostGIS layers.
This provider implements the This provider implements the
interface defined in the QgsDataProvider class to provide access to spatial interface defined in the QgsDataProvider class to provide access to spatial
@ -39,6 +50,9 @@ data residing in a PostgreSQL/PostGIS enabled database.
*/ */
class QgsPostgresProvider:public QgsVectorDataProvider class QgsPostgresProvider:public QgsVectorDataProvider
{ {
Q_OBJECT
public: public:
/** /**
* Constructor for the provider. The uri must be in the following format: * Constructor for the provider. The uri must be in the following format:
@ -142,6 +156,12 @@ public:
*/ */
QString endianString(); QString endianString();
/**
* Changes the stored extent for this layer to the supplied extent.
* For example, this is called when the extent worker thread has a result.
*/
void setExtent( QgsRect* newExtent );
/** Return the extent for this data layer /** Return the extent for this data layer
*/ */
virtual QgsRect *extent(); virtual QgsRect *extent();
@ -237,6 +257,26 @@ public:
/**Returns a bitmask containing the supported capabilities*/ /**Returns a bitmask containing the supported capabilities*/
int capabilities() const; int capabilities() const;
signals:
/**
* This is emitted whenever the worker thread has fully calculated the
* PostGIS extents for this layer, and its event has been received by this
* provider.
*/
void fullExtentCalculated();
/**
* This is emitted when this provider is satisfied that all objects
* have had a chance to adjust themselves after they'd been notified that
* the full extent is available.
*
* \note It currently isn't being emitted because we don't have an easy way
* for the overview canvas to only be repainted. In the meantime
* we are satisfied for the overview to reflect the new extent
* when the user adjusts the extent of the main map canvas.
*/
void repaintRequested();
private: private:
@ -250,6 +290,14 @@ private:
//! Data source URI struct for this layer //! Data source URI struct for this layer
QgsDataSourceURI mUri; QgsDataSourceURI mUri;
//! Child thread for calculating extents.
QgsPostgresExtentThread mExtentThread;
//! Child thread for calculating count.
QgsPostgresCountThread mCountThread;
/** /**
* Pointer to the PostgreSQL query result object. If this pointer is 0, * Pointer to the PostgreSQL query result object. If this pointer is 0,
* there is no current selection set. Any future getNextFeature requests * there is no current selection set. Any future getNextFeature requests
@ -361,4 +409,11 @@ private:
//! Calculate the extents of the layer //! Calculate the extents of the layer
void calculateExtents(); void calculateExtents();
/**
* Event sink for events from threads
*/
void customEvent ( QCustomEvent * e );
}; };
#endif