diff --git a/providers/postgres/Makefile.am b/providers/postgres/Makefile.am index e843052c67f..6ef677fed57 100644 --- a/providers/postgres/Makefile.am +++ b/providers/postgres/Makefile.am @@ -1,17 +1,39 @@ INCLUDES = -I../../src +%.moc.cpp: %.h + $(MOC) -o $@ $< + plugindir = ${pkglibdir} plugin_LTLIBRARIES = postgresprovider.la -postgresprovider_la_SOURCES = qgspostgresprovider.cpp \ - qgspostgresprovider.h \ - ../../src/qgsfeature.cpp \ - ../../src/qgsfeatureattribute.cpp \ - ../../src/qgsrect.cpp \ - ../../src/qgspoint.cpp \ - ../../src/qgsfield.cpp +## non-uic files generated from MOC +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_LDFLAGS = -avoid-version -module postgresprovider_la_CXXFLAGS = $(EXTRA_CXXFLAGS) $(QT_CXXFLAGS) -I$(PG_INC) $(DEBUG_QGIS) $(GEOS_CFLAGS) + +CLEANFILES = $(BUILT_SOURCES) diff --git a/providers/postgres/qgspostgresprovider.cpp b/providers/postgres/qgspostgresprovider.cpp index e62b9f1360a..5aebd5e1394 100644 --- a/providers/postgres/qgspostgresprovider.cpp +++ b/providers/postgres/qgspostgresprovider.cpp @@ -39,7 +39,16 @@ #include "../../src/qgsfield.h" #include "../../src/qgsrect.h" +#include "../../src/qgsprovidercountcalcevent.h" +#include "../../src/qgsproviderextentcalcevent.h" + #include "qgspostgresprovider.h" + +#include "qgspostgrescountthread.h" +#include "qgspostgresextentthread.h" + +#include "qgspostgisbox3d.h" + #ifdef WIN32 #define QGISEXTERN extern "C" __declspec( dllexport ) #else @@ -230,6 +239,29 @@ QgsPostgresProvider::QgsPostgresProvider(QString uri):dataSourceUri(uri) getPrimaryKey(); selectSQL += " from " + tableName; //--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 { // the table is not a geometry table @@ -270,7 +302,27 @@ QgsPostgresProvider::QgsPostgresProvider(QString uri):dataSourceUri(uri) 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); + + std::cout << "QgsPostgresProvider: deconstructing." << std::endl; + //pLog.flush(); } @@ -433,7 +485,7 @@ QgsFeature* QgsPostgresProvider::getNextFeature(std::list const & attlist) { // XXX I'm assuming swapping from big-endian, or network, byte order to little endian #ifdef QGISDEBUG - qWarning("swapping endian for oid"); +//XXX TOO MUCH OUTPUT!!! qWarning("swapping endian for oid"); #endif // convert oid to opposite endian // 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; #endif if(useIntersect){ - declare += " where intersects(" + geometryColumn; - declare += ", setsrid('BOX3D(" + rect->stringRep(); +// declare += " where intersects(" + geometryColumn; +// 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 += srid; 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_ QgsRect *QgsPostgresProvider::extent() @@ -1190,35 +1262,62 @@ void QgsPostgresProvider::setSubsetString(QString theSQL) calculateExtents(); } + long QgsPostgresProvider::getFeatureCount() { - // get total number of features - QString sql = "select count(*) from " + tableName; - if(sqlWhereClause.length() > 0) - { - sql += " where " + sqlWhereClause; - } + // get total number of features + + // First get an approximate count; then delegate to + // a thread the task of getting the full count. + + 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); + +#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(); PQclear(result); #ifdef QGISDEBUG - std::cerr << "Number of features: " << numberFeatures << std::endl; + std::cerr << "QgsPostgresProvider: Approximate Number of features: " << + numberFeatures << std::endl; #endif return numberFeatures; } +// TODO: use the estimateExtents procedure of PostGIS and PostgreSQL 8 +// This tip thanks to #qgis irc nick "creeping" 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) { - sql += " where " + sqlWhereClause; + sql += "(" + sqlWhereClause + ") and "; } + sql += "not IsEmpty(" + geometryColumn + ") limit 5"; + #if WASTE_TIME sql = "select xmax(extent(" + geometryColumn + ")) as xmax," "xmin(extent(" + geometryColumn + ")) as xmin," @@ -1226,50 +1325,108 @@ void QgsPostgresProvider::calculateExtents() #endif #ifdef QGISDEBUG - qDebug("+++++++++QgsPostgresProvider::calculateExtents - Getting extents using schema.table: " + sql); + qDebug("QgsPostgresProvider::calculateExtents - Getting approximate extent using: '" + sql + "'"); #endif PGresult *result = PQexec(connection, (const char *) sql); - std::string box3d = PQgetvalue(result, 0, 0); - - if (box3d != "") + + // TODO: Guard against the result having no rows + + for (int i = 0; i < PQntuples(result); i++) { - std::string s; - - box3d = box3d.substr(box3d.find_first_of("(")+1); - box3d = box3d.substr(box3d.find_first_not_of(" ")); - s = box3d.substr(0, box3d.find_first_of(" ")); - double minx = strtod(s.c_str(), NULL); - - box3d = box3d.substr(box3d.find_first_of(" ")+1); - s = box3d.substr(0, box3d.find_first_of(" ")); - double miny = strtod(s.c_str(), NULL); - - box3d = box3d.substr(box3d.find_first_of(",")+1); - box3d = box3d.substr(box3d.find_first_not_of(" ")); - s = box3d.substr(0, box3d.find_first_of(" ")); - double maxx = strtod(s.c_str(), NULL); - - box3d = box3d.substr(box3d.find_first_of(" ")+1); - s = box3d.substr(0, box3d.find_first_of(" ")); - 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); + std::string box3d = PQgetvalue(result, i, 0); + + if (0 == i) + { + // create the initial extent + layerExtent = QgsPostGisBox3d(box3d); + } + else + { + // extend the initial extent + QgsPostGisBox3d b = QgsPostGisBox3d(box3d); + + layerExtent.combineExtentWith( &b ); + } + + std::cout << "QgsPostgresProvider: After row " << i << ", extent is: " + << layerExtent.xMin() << ", " << layerExtent.yMin() << + " " << layerExtent.xMax() << ", " << layerExtent.yMax() << std::endl; + } + +#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() { // need to store the PostgreSQL endian format used in binary cursors @@ -1391,7 +1548,9 @@ bool QgsPostgresProvider::getGeometryDetails() #endif return valid; } - + + + /** * Class factory to return a pointer to a newly created * QgsPostgresProvider object diff --git a/providers/postgres/qgspostgresprovider.h b/providers/postgres/qgspostgresprovider.h index 189a65510b7..7dfb8e4e122 100644 --- a/providers/postgres/qgspostgresprovider.h +++ b/providers/postgres/qgspostgresprovider.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgspostgresprovider.h - Data provider for PostgrSQL/PostGIS layers + qgspostgresprovider.h - Data provider for PostgreSQL/PostGIS layers ------------------- begin : Jan 2, 2004 copyright : (C) 2003 by Gary E.Sherman @@ -15,13 +15,21 @@ * * ***************************************************************************/ /* $Id$ */ + +#ifndef QGSPOSTGRESPROVIDER_H +#define QGSPOSTGRESPROVIDER_H + extern "C" { #include } #include "../../src/qgsvectordataprovider.h" +#include "../../src/qgsrect.h" #include -#include +#include +#include +#include +//#include class QgsFeature; class QgsField; @@ -29,9 +37,12 @@ class OGRDataSource; class OGRLayer; #include "qgsdatasourceuri.h" +#include "qgspostgrescountthread.h" +#include "qgspostgresextentthread.h" + /** \class QgsPostgresProvider -\brief Data provider for PostgrSQL/PostGIS layers. +\brief Data provider for PostgreSQL/PostGIS layers. This provider implements the 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 { + + Q_OBJECT + public: /** * Constructor for the provider. The uri must be in the following format: @@ -142,6 +156,12 @@ public: */ 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 */ virtual QgsRect *extent(); @@ -237,6 +257,26 @@ public: /**Returns a bitmask containing the supported capabilities*/ 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: @@ -250,6 +290,14 @@ private: //! Data source URI struct for this layer 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, * there is no current selection set. Any future getNextFeature requests @@ -361,4 +409,11 @@ private: //! Calculate the extents of the layer void calculateExtents(); + /** + * Event sink for events from threads + */ + void customEvent ( QCustomEvent * e ); + }; + +#endif