diff --git a/python/plugins/osm/OsmDatabaseManager.py b/python/plugins/osm/OsmDatabaseManager.py index b45cfd05767..31e57e87883 100755 --- a/python/plugins/osm/OsmDatabaseManager.py +++ b/python/plugins/osm/OsmDatabaseManager.py @@ -323,7 +323,6 @@ class OsmDatabaseManager: lay=self.pointLayers[self.currentKey] lay.select([],area,True,True) result=lay.nextFeature(feat) - lay.dataProvider().rewind() if result: return (feat,'Point') @@ -332,7 +331,6 @@ class OsmDatabaseManager: lay=self.lineLayers[self.currentKey] lay.select([],area,True,True) result=lay.nextFeature(feat) - lay.dataProvider().rewind() if result: # line vertices @@ -341,7 +339,7 @@ class OsmDatabaseManager: ,{"minLat":area.yMinimum(),"maxLat":area.yMaximum(),"minLon":area.xMinimum(),"maxLon":area.xMaximum(),"lineId":str(feat.id())}) for rec in c: - feat2=QgsFeature(rec[0],"Point") + feat2=QgsFeature(rec[0]) feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(rec[2],rec[1]))) # without features' attributes here! we don't need them... c.close() @@ -354,7 +352,6 @@ class OsmDatabaseManager: lay=self.polygonLayers[self.currentKey] lay.select([],area,True,True) result=lay.nextFeature(feat) - lay.dataProvider().rewind() if result: # polygon vertices @@ -363,7 +360,7 @@ class OsmDatabaseManager: ,{"minLat":area.yMinimum(),"maxLat":area.yMaximum(),"minLon":area.xMinimum(),"maxLon":area.xMaximum(),"polygonId":str(feat.id())}) for rec in c: - feat2=QgsFeature(rec[0],"Point") + feat2=QgsFeature(rec[0]) feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(rec[2],rec[1]))) # without features' attributes here! we don't need them... c.close() @@ -416,7 +413,7 @@ class OsmDatabaseManager: ,{"minLat":area.yMinimum(),"maxLat":area.yMaximum(),"minLon":area.xMinimum(),"maxLon":area.xMaximum(),"lineId":str(feat.id())}) for rec in c: - feat2=QgsFeature(rec[0],"Point") + feat2=QgsFeature(rec[0]) feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(rec[2],rec[1]))) # without features' attributes here! we don't need them... featMap[feat2.id()]=feat2 @@ -439,7 +436,7 @@ class OsmDatabaseManager: ,{"minLat":area.yMinimum(),"maxLat":area.yMaximum(),"minLon":area.xMinimum(),"maxLon":area.xMaximum(),"polygonId":str(feat.id())}) for rec in c: - feat2=QgsFeature(rec[0],"Point") + feat2=QgsFeature(rec[0]) feat2.setGeometry(QgsGeometry.fromPoint(QgsPoint(rec[2],rec[1]))) # without features' attributes here! we don't need them... featMap[feat2.id()]=feat2 @@ -471,7 +468,7 @@ class OsmDatabaseManager: nodeId=self.__getFreeFeatureId() affected=set() - feat=QgsFeature(nodeId,"Point") + feat=QgsFeature(nodeId) feat.setGeometry(QgsGeometry.fromPoint(QgsPoint(mapPoint.x(),mapPoint.y()))) # should snapping be done? if not, everything's easy @@ -612,7 +609,7 @@ class OsmDatabaseManager: # finishing... c.close() - feat=QgsFeature(lineId,"Line") + feat=QgsFeature(lineId) feat.setGeometry(QgsGeometry.fromPolyline(pline)) if doCommit: @@ -691,7 +688,7 @@ class OsmDatabaseManager: # finish c.close() - feat=QgsFeature(polygonId,"Polygon") + feat=QgsFeature(polygonId) polygon=[] polygon.append(pline) feat.setGeometry(QgsGeometry.fromPolygon(polygon)) diff --git a/src/providers/CMakeLists.txt b/src/providers/CMakeLists.txt index a40b8427446..9b9976f4969 100644 --- a/src/providers/CMakeLists.txt +++ b/src/providers/CMakeLists.txt @@ -6,7 +6,7 @@ ADD_SUBDIRECTORY(memory) ADD_SUBDIRECTORY(ogr) ADD_SUBDIRECTORY(wms) ADD_SUBDIRECTORY(delimitedtext) -#ADD_SUBDIRECTORY(osm) # TODO: enable when migrated to new api +ADD_SUBDIRECTORY(osm) #ADD_SUBDIRECTORY(sqlanywhere) # TODO: enable when migrated to new api ADD_SUBDIRECTORY(gdal) #ADD_SUBDIRECTORY(mssql) # TODO: enable when migrated to new api diff --git a/src/providers/osm/CMakeLists.txt b/src/providers/osm/CMakeLists.txt index 197be2d8781..3a7f5cd3325 100644 --- a/src/providers/osm/CMakeLists.txt +++ b/src/providers/osm/CMakeLists.txt @@ -3,6 +3,7 @@ # Files SET(OSM_SRCS +osmfeatureiterator.cpp osmhandler.cpp osmprovider.cpp osmrenderer.cpp diff --git a/src/providers/osm/osmfeatureiterator.cpp b/src/providers/osm/osmfeatureiterator.cpp new file mode 100644 index 00000000000..984bb70dfe0 --- /dev/null +++ b/src/providers/osm/osmfeatureiterator.cpp @@ -0,0 +1,472 @@ +#include "osmfeatureiterator.h" + +#include "osmprovider.h" + +#include "qgsapplication.h" +#include "qgsgeometry.h" +#include "qgslogger.h" + +QgsOSMFeatureIterator::QgsOSMFeatureIterator( QgsOSMDataProvider* p, const QgsFeatureRequest& request ) + : QgsAbstractFeatureIterator( request ), P( p ) + , mRectGeom(0) +{ + // make sure that only one iterator is active + if ( P->mActiveIterator ) + P->mActiveIterator->close(); + P->mActiveIterator = this; + + + bool hasFilterFid = mRequest.filterType() == QgsFeatureRequest::FilterFid; + bool hasFilterRect = mRequest.filterType() == QgsFeatureRequest::FilterRect; + QgsRectangle rect = mRequest.filterRect(); + mRectGeom = QgsGeometry::fromRect( rect ); + + + if ( P->mFeatureType == QgsOSMDataProvider::PointType ) + { + const char* sqlSelectNode = "SELECT id, lat, lon, timestamp, user FROM node WHERE id=? AND usage=0 AND status<>'R' AND u=1"; + const char* sqlSelectPoints = "SELECT id, lat, lon, timestamp, user FROM node WHERE usage=0 AND status<>'R' AND u=1"; + const char* sqlSelectPointsIn = "SELECT id, lat, lon, timestamp, user FROM node WHERE usage=0 AND status<>'R' AND u=1 " + "AND lat>=? AND lat<=? AND lon>=? AND lon<=?"; + + const char* sql = hasFilterFid ? sqlSelectNode : (hasFilterRect ? sqlSelectPointsIn : sqlSelectPoints); + + if ( sqlite3_prepare_v2( P->mDatabase, sql, -1, &mSelectStmt, 0 ) != SQLITE_OK ) + { + QgsDebugMsg( "sqlite3 statement for points retrieval - prepare failed." ); + return; + } + + if ( hasFilterFid ) + { + sqlite3_bind_int64( mSelectStmt, 1, mRequest.filterFid() ); + } + else if ( hasFilterRect ) + { + // binding variables (boundary) for points selection! + sqlite3_bind_double( mSelectStmt, 1, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 2, rect.yMaximum() ); + sqlite3_bind_double( mSelectStmt, 3, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 4, rect.xMaximum() ); + } + } + else if ( P->mFeatureType == QgsOSMDataProvider::LineType ) + { + const char* sqlSelectWay = "SELECT id, wkb, timestamp, user FROM way WHERE id=? AND status<>'R' AND u=1"; + const char* sqlSelectLines = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=0 AND w.status<>'R' AND w.u=1"; + const char* sqlSelectLinesIn = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=0 AND w.status<>'R' AND w.u=1 " + "AND (((w.max_lat between ? AND ?) OR (w.min_lat between ? AND ?) OR (w.min_lat?)) " + "AND ((w.max_lon between ? AND ?) OR (w.min_lon between ? AND ?) OR (w.min_lon?)))"; + const char* sql = hasFilterFid ? sqlSelectWay : ( hasFilterRect ? sqlSelectLinesIn : sqlSelectLines ); + + if ( sqlite3_prepare_v2( P->mDatabase, sql, -1, &mSelectStmt, 0 ) != SQLITE_OK ) + { + QgsDebugMsg( "sqlite3 statement for lines retrieval - prepare failed." ); + return; + } + + if ( hasFilterFid ) + { + sqlite3_bind_int64( mSelectStmt, 1, mRequest.filterFid() ); + } + else if ( hasFilterRect ) + { + // binding variables (boundary) for lines selection! + sqlite3_bind_double( mSelectStmt, 1, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 2, rect.yMaximum() ); + sqlite3_bind_double( mSelectStmt, 3, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 4, rect.yMaximum() ); + sqlite3_bind_double( mSelectStmt, 5, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 6, rect.yMaximum() ); + + sqlite3_bind_double( mSelectStmt, 7, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 8, rect.xMaximum() ); + sqlite3_bind_double( mSelectStmt, 9, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 10, rect.xMaximum() ); + sqlite3_bind_double( mSelectStmt, 11, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 12, rect.xMaximum() ); + } + } + else // mFeatureType == PolygonType + { + const char* sqlSelectWay = "SELECT id, wkb, timestamp, user FROM way WHERE id=? AND status<>'R' AND u=1"; + const char* sqlSelectPolys = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=1 AND w.status<>'R' AND w.u=1"; + const char* sqlSelectPolysIn = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=1 AND w.status<>'R' AND w.u=1 " + "AND (((w.max_lat between ? AND ?) OR (w.min_lat between ? AND ?) OR (w.min_lat?)) " + "AND ((w.max_lon between ? AND ?) OR (w.min_lon between ? AND ?) OR (w.min_lon?)))"; + const char* sql = hasFilterFid ? sqlSelectWay : ( hasFilterRect ? sqlSelectPolysIn : sqlSelectPolys ); + + if ( sqlite3_prepare_v2( P->mDatabase, sql, -1, &mSelectStmt, 0 ) != SQLITE_OK ) + { + QgsDebugMsg( "sqlite3 statement for polygons retrieval - prepare failed." ); + return; + } + + if ( hasFilterFid ) + { + sqlite3_bind_int64( mSelectStmt, 1, mRequest.filterFid() ); + } + else if ( hasFilterRect ) + { + // binding variables (boundary) for polygons selection! + sqlite3_bind_double( mSelectStmt, 1, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 2, rect.yMaximum() ); + sqlite3_bind_double( mSelectStmt, 3, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 4, rect.yMaximum() ); + sqlite3_bind_double( mSelectStmt, 5, rect.yMinimum() ); + sqlite3_bind_double( mSelectStmt, 6, rect.yMaximum() ); + + sqlite3_bind_double( mSelectStmt, 7, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 8, rect.xMaximum() ); + sqlite3_bind_double( mSelectStmt, 9, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 10, rect.xMaximum() ); + sqlite3_bind_double( mSelectStmt, 11, rect.xMinimum() ); + sqlite3_bind_double( mSelectStmt, 12, rect.xMaximum() ); + } + } + + + // prepare statement for tag retrieval + const char* sqlSelectTags = "SELECT key, val FROM tag WHERE object_id=? AND object_type=?"; + int rc = sqlite3_prepare_v2( P->mDatabase, sqlSelectTags, -1, &mTagsStmt, 0 ); + if ( rc != SQLITE_OK ) + { + QgsDebugMsg( "sqlite3 statement for feature tags selection - prepare failed." ); + return; + } + + const char* sqlSelectTagValue = "SELECT val FROM tag WHERE object_id=? AND object_type=? AND key=?"; + rc = sqlite3_prepare_v2( P->mDatabase, sqlSelectTagValue, -1, &mCustomTagsStmt, 0 ); + if ( rc != SQLITE_OK ) + { + QgsDebugMsg( "sqlite3 statement for tag value selection - prepare failed." ); + return; + } + + +} + + +QgsOSMFeatureIterator::~QgsOSMFeatureIterator() +{ + close(); +} + +bool QgsOSMFeatureIterator::nextFeature( QgsFeature& feature ) +{ + if ( mClosed ) + return false; + + // load next requested feature from sqlite3 database + switch ( sqlite3_step( mSelectStmt ) ) + { + case SQLITE_DONE: // no more features to return + feature.setValid( false ); + close(); + return false; + + case SQLITE_ROW: // another feature to return + if ( P->mFeatureType == QgsOSMDataProvider::PointType ) + return fetchNode( feature ); + else if ( P->mFeatureType == QgsOSMDataProvider::LineType ) + return fetchWay( feature ); + else if ( P->mFeatureType == QgsOSMDataProvider::PolygonType ) + return fetchWay( feature ); + } + + if ( P->mFeatureType == QgsOSMDataProvider::PointType ) + { + QgsDebugMsg( "Getting next feature of type failed." ); + } + else if ( P->mFeatureType == QgsOSMDataProvider::LineType ) + { + QgsDebugMsg( "Getting next feature of type failed." ); + } + else if ( P->mFeatureType == QgsOSMDataProvider::PolygonType ) + { + QgsDebugMsg( "Getting next feature of type failed." ); + } + feature.setValid( false ); + close(); + return false; +} + + +bool QgsOSMFeatureIterator::rewind() +{ + if ( mClosed ) + return false; + + // we have to reset precompiled database statement; thanx to this action the first feature + // (returned by the query) will be selected again with the next calling of sqlite3_step(mStmt) + if ( mSelectStmt ) + sqlite3_reset( mSelectStmt ); + + return false; +} + +bool QgsOSMFeatureIterator::close() +{ + if ( mClosed ) + return false; + + // destruct selected geometry + delete mRectGeom; + mRectGeom = 0; + + sqlite3_finalize( mSelectStmt ); + + // finalize all created sqlite3 statements + sqlite3_finalize( mTagsStmt ); + sqlite3_finalize( mCustomTagsStmt ); + + // tell provider that this iterator is not active anymore + P->mActiveIterator = 0; + + mClosed = true; + return true; +} + + + +bool QgsOSMFeatureIterator::fetchNode( QgsFeature& feature ) +{ + sqlite3_stmt* stmt = mSelectStmt; + bool fetchGeometry = !( mRequest.flags() & QgsFeatureRequest::NoGeometry ); + + int selId = sqlite3_column_int( stmt, 0 ); + double selLat = sqlite3_column_double( stmt, 1 ); + double selLon = sqlite3_column_double( stmt, 2 ); + + feature.setFeatureId( selId ); + feature.setValid( true ); + feature.initAttributes( P->mAttributeFields.count() ); + feature.setFields( &P->mAttributeFields ); // allow name-based attribute lookups + + // fetch feature's geometry + if ( fetchGeometry ) + { + char* geo = new char[21]; + memset( geo, 0, 21 ); + geo[0] = QgsApplication::endian(); + geo[geo[0] == QgsApplication::NDR ? 1 : 4] = QGis::WKBPoint; + memcpy( geo + 5, &selLon, sizeof( double ) ); + memcpy( geo + 13, &selLat, sizeof( double ) ); + feature.setGeometryAndOwnership(( unsigned char * )geo, 24 ); // 24 is size of wkb point structure! + } + + // fetch attributes + fetchAttributes( feature, true ); + + return true; +} + + +bool QgsOSMFeatureIterator::fetchWay( QgsFeature& feature ) +{ + sqlite3_stmt* stmt = mSelectStmt; + bool fetchGeometry = !( mRequest.flags() & QgsFeatureRequest::NoGeometry ); + bool useIntersect = mRequest.flags() & QgsFeatureRequest::ExactIntersect; + + int selId; + QgsGeometry *theGeometry = NULL; + bool fetchMoreRows = true; + int rc = -1; + + do + { + selId = sqlite3_column_int( stmt, 0 ); + unsigned char *pzBlob = 0; + int pnBlob = 0; + + if ( fetchGeometry || useIntersect || mRequest.filterType() == QgsFeatureRequest::FilterRect ) + { + pnBlob = sqlite3_column_bytes( stmt, 1 ); + pzBlob = new unsigned char[pnBlob]; + memcpy( pzBlob, sqlite3_column_blob( stmt, 1 ), pnBlob ); + + // create geometry + theGeometry = new QgsGeometry(); + theGeometry->fromWkb(( unsigned char * ) pzBlob, pnBlob ); + } + + if ( theGeometry && ( theGeometry->type() == 3 ) && ( selId != 0 ) ) + { + // line/polygon geometry is not cached! + char *geo; + int geolen; + P->updateWayWKB( selId, ( P->mFeatureType == QgsOSMDataProvider::LineType ) ? 0 : 1, &geo, &geolen ); + theGeometry->fromWkb(( unsigned char * ) geo, ( size_t ) geolen ); + } + + if ( mRequest.filterType() == QgsFeatureRequest::FilterRect ) + { + if ( useIntersect ) + { + // when using intersect, some features might be ignored if they don't intersect the selection rect + // intersect is a costly operation, use rectangle converted to geos for less conversions + // (this is usually used during identification of an object) + if ( theGeometry->intersects( mRectGeom ) ) + fetchMoreRows = false; + } + else + { + // when using selection rectangle but without exact intersection, check only overlap of bounding box + // (usually used when drawing) + if ( mRequest.filterRect().intersects( theGeometry->boundingBox() ) ) + fetchMoreRows = false; + } + } + else + { + // no filter => always accept the new feature + // (used in attribute table) + fetchMoreRows = false; + } + + // delete the geometry (if any) in case we're not going to use it anyway + if ( fetchMoreRows ) + delete theGeometry; + } + while ( fetchMoreRows && (( rc = sqlite3_step( stmt ) ) == SQLITE_ROW ) ); + + // no more features to return + if ( rc == SQLITE_DONE ) + { + sqlite3_exec( P->mDatabase, "COMMIT;", 0, 0, 0 ); + feature.setValid( false ); + return false; + } + + // fetch feature's geometry + if ( fetchGeometry ) + { + feature.setGeometry( theGeometry ); + } + else + { + delete theGeometry; // make sure it's deleted + } + + feature.setFeatureId( selId ); + feature.setValid( true ); + + // fetch attributes + fetchAttributes( feature, false ); + + return true; +} + + + +void QgsOSMFeatureIterator::fetchAttributes( QgsFeature& feature, bool isNode ) +{ + feature.initAttributes( P->mAttributeFields.count() ); + feature.setFields( &P->mAttributeFields ); // allow name-based attribute lookups + + // node + const char* selTimestamp = ( const char* ) sqlite3_column_text( mSelectStmt, isNode ? 3 : 2 ); + const char* selUser = ( const char* ) sqlite3_column_text( mSelectStmt, isNode ? 4 : 3 ); + int selId = sqlite3_column_int( mSelectStmt, 0 ); + + // TODO[MD]: subset of attributes + + //QgsAttributeList::const_iterator iter; + //for ( iter = fetchAttrs.begin(); iter != fetchAttrs.end(); ++iter ) + for ( int i = 0; i < P->mAttributeFields.count(); ++i ) + { + switch ( i ) + { + case QgsOSMDataProvider::TimestampAttr: + feature.setAttribute( QgsOSMDataProvider::TimestampAttr, QString::fromUtf8( selTimestamp ) ); + break; + case QgsOSMDataProvider::UserAttr: + feature.setAttribute( QgsOSMDataProvider::UserAttr, QString::fromUtf8( selUser ) ); + break; + case QgsOSMDataProvider::TagAttr: + feature.setAttribute( QgsOSMDataProvider::TagAttr, tagsForObject( isNode, selId ) ); + break; + default: // suppose it's a custom tag + if ( i >= QgsOSMDataProvider::CustomTagAttr && i < QgsOSMDataProvider::CustomTagAttr + P->mCustomTagsList.count() ) + { + feature.setAttribute( i, tagForObject( isNode, selId, P->mCustomTagsList[i - QgsOSMDataProvider::CustomTagAttr] ) ); + } + } + } +} + + + +QString QgsOSMFeatureIterator::tagForObject( bool isNode, int id, QString tagKey ) +{ + sqlite3_bind_int( mCustomTagsStmt, 1, id ); + sqlite3_bind_text( mCustomTagsStmt, 2, isNode ? "node" : "way", -1, 0 ); + QByteArray tag = tagKey.toUtf8(); // must keep the byte array until the query is run + sqlite3_bind_text( mCustomTagsStmt, 3, tag.data(), -1, 0 ); + + QString value; + int rc; + + if (( rc = sqlite3_step( mCustomTagsStmt ) ) == SQLITE_ROW ) + { + const char* tagVal = ( const char* ) sqlite3_column_text( mCustomTagsStmt, 0 ); + value = QString::fromUtf8( tagVal ); + } + else + { + // tag wasn't found + sqlite3_reset( mCustomTagsStmt ); // make ready for next retrieval + return ""; + } + + sqlite3_reset( mCustomTagsStmt ); // make ready for next retrieval + return value; +} + + + +QString QgsOSMFeatureIterator::tagsForObject( bool isNode, int id ) +{ + sqlite3_bind_int( mTagsStmt, 1, id ); + sqlite3_bind_text( mTagsStmt, 2, isNode ? "node" : "way", -1, 0 ); + + QString tags; + int rc; + + while (( rc = sqlite3_step( mTagsStmt ) ) == SQLITE_ROW ) + { + const char* tagKey = ( const char* ) sqlite3_column_text( mTagsStmt, 0 ); + const char* tagVal = ( const char* ) sqlite3_column_text( mTagsStmt, 1 ); + QString key = QString::fromUtf8( tagKey ); + QString val = QString::fromUtf8( tagVal ); + + // we concatenate tags this way: "key1"="val1","key2"="val2","key3"="val3" + // -all ; in keyX and valX are replaced by ;; + // -all , in keyX and valX are replaced by ; + // -all - in keyX and valX are replaced by -- + // -all = in keyX and valX are replaced by - + key = key.replace( ';', ";;" ); + val = val.replace( ';', ";;" ); + key = key.replace( ',', ";" ); + val = val.replace( ',', ";" ); + + key = key.replace( '-', "--" ); + val = val.replace( '-', "--" ); + key = key.replace( '=', "-" ); + val = val.replace( '=', "-" ); + + if ( tags.count() > 0 ) + tags += ","; + + tags += QString( "\"%1\"=\"%2\"" ).arg( key ).arg( val ); + } + + if ( rc != SQLITE_DONE ) + { + // no tags for object + //QgsDebugMsg(QString("tags for object failed: type %1 id %2").arg(type).arg(id)); + } + + sqlite3_reset( mTagsStmt ); // make ready for next retrieval + return tags; +} diff --git a/src/providers/osm/osmfeatureiterator.h b/src/providers/osm/osmfeatureiterator.h new file mode 100644 index 00000000000..aa62077bd91 --- /dev/null +++ b/src/providers/osm/osmfeatureiterator.h @@ -0,0 +1,87 @@ +#ifndef OSMFEATUREITERATOR_H +#define OSMFEATUREITERATOR_H + +#include "qgsfeatureiterator.h" + +#include + + +class QgsOSMDataProvider; + +class QgsOSMFeatureIterator : public QgsAbstractFeatureIterator +{ + public: + QgsOSMFeatureIterator( QgsOSMDataProvider* p, const QgsFeatureRequest& request ); + + ~QgsOSMFeatureIterator(); + + //! fetch next feature, return true on success + virtual bool nextFeature( QgsFeature& feature ); + + //! reset the iterator to the starting position + virtual bool rewind(); + + //! end of iterating: free the resources / lock + virtual bool close(); + + protected: + + /** + * Function fetches one node from current sqlite3 statement. + * @param feature output; feature representing fetched node + * @return success of failure flag (true/false) + */ + bool fetchNode( QgsFeature& feature ); + + /** + * Function fetches one way from current sqlite3 statement. + * @param feature output; feature representing fetched way + * @return success of failure flag (true/false) + */ + bool fetchWay( QgsFeature& feature ); + + + /** + * Function returns string of concatenated tags of specified feature. + * @param isNode true for node, false for way + * @param id feature identifier + * @return string of tags concatenation + */ + QString tagsForObject( bool isNode, int id ); + + /** + * Function returns one tag value of specified feature and specified key. + * @param isNode true for node, false for way + * @param id feature identifier + * @param tagKey tag key + * @return tag value + */ + QString tagForObject( bool isNode, int id, QString tagKey ); + + /** + * Add attributes to the feature from the current row + * @param feature + * @param isNode true for node, false for way + */ + void fetchAttributes( QgsFeature& feature, bool isNode ); + + protected: + QgsOSMDataProvider* P; + + //! sqlite3 database statement for selection + sqlite3_stmt *mSelectStmt; + + //! sqlite3 database statement ready to select all feature tags + sqlite3_stmt *mTagsStmt; + + //! sqlite3 database statement ready to select concrete feature tag + sqlite3_stmt *mCustomTagsStmt; + + //! geometry for exact intersection test + QgsGeometry* mRectGeom; + +}; + + + +#endif // OSMFEATUREITERATOR_H diff --git a/src/providers/osm/osmprovider.cpp b/src/providers/osm/osmprovider.cpp index 2ee9ecb1cd7..8d26446a2f4 100644 --- a/src/providers/osm/osmprovider.cpp +++ b/src/providers/osm/osmprovider.cpp @@ -15,6 +15,7 @@ #include "osmprovider.h" #include "osmhandler.h" #include "osmrenderer.h" +#include "osmfeatureiterator.h" #include "qgsfeature.h" #include "qgsfield.h" @@ -40,24 +41,19 @@ const char* QgsOSMDataProvider::attr[] = { "timestamp", "user", "tags" }; QgsOSMDataProvider::QgsOSMDataProvider( QString uri ) : QgsVectorDataProvider( uri ) + , mActiveIterator( 0 ) { QgsDebugMsg( "Initializing provider: " + uri ); - mDatabaseStmt = NULL; mValid = false; // set the selection rectangle to null - mSelectionRectangle = 0; - mSelectionRectangleGeom = NULL; mDatabase = NULL; mInitObserver = NULL; mFeatureType = PointType; // default feature type ~ point // set default boundaries - xMin = -DEFAULT_EXTENT; - xMax = DEFAULT_EXTENT; - yMin = -DEFAULT_EXTENT; - yMax = DEFAULT_EXTENT; + mExtent = QgsRectangle( -DEFAULT_EXTENT, -DEFAULT_EXTENT, DEFAULT_EXTENT, DEFAULT_EXTENT ); // get the filename and other parameters from the URI int fileNameEnd = uri.indexOf( '?' ); @@ -137,14 +133,14 @@ QgsOSMDataProvider::QgsOSMDataProvider( QString uri ) } // set up attributes depending on the feature type - same attributes for both point and way type so far - mAttributeFields[TimestampAttr] = QgsField( attr[TimestampAttr], QVariant::String, "string" ); - mAttributeFields[UserAttr] = QgsField( attr[UserAttr], QVariant::String, "string" ); - mAttributeFields[TagAttr] = QgsField( attr[TagAttr], QVariant::String, "string" ); + mAttributeFields.append( QgsField( attr[TimestampAttr], QVariant::String, "string" ) ); + mAttributeFields.append( QgsField( attr[UserAttr], QVariant::String, "string" ) ); + mAttributeFields.append( QgsField( attr[TagAttr], QVariant::String, "string" ) ); // add custom attributes - these were chosen by user through OSM plugin for ( int tagId = 0; tagId < mCustomTagsList.count(); ++tagId ) { - mAttributeFields[CustomTagAttr+tagId] = QgsField( mCustomTagsList[tagId], QVariant::String, "string" ); + mAttributeFields.append( QgsField( mCustomTagsList[tagId], QVariant::String, "string" ) ); } // get source file name and database file name @@ -226,10 +222,10 @@ QgsOSMDataProvider::QgsOSMDataProvider( QString uri ) QStringList parts = boundaries.split( QChar( ':' ) ); if ( parts.count() == 4 ) { - xMin = parts[0].toDouble(); - yMin = parts[1].toDouble(); - xMax = parts[2].toDouble(); - yMax = parts[3].toDouble(); + mExtent.setXMinimum( parts[0].toDouble() ); + mExtent.setYMinimum( parts[1].toDouble() ); + mExtent.setXMaximum( parts[2].toDouble() ); + mExtent.setYMaximum( parts[3].toDouble() ); } else { @@ -243,94 +239,6 @@ QgsOSMDataProvider::QgsOSMDataProvider( QString uri ) } - // prepare statement for tag retrieval - char sqlSelectTags[] = "SELECT key, val FROM tag WHERE object_id=? AND object_type=?"; - int rc = sqlite3_prepare_v2( mDatabase, sqlSelectTags, sizeof( sqlSelectTags ), &mTagsStmt, 0 ); - if ( rc != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for feature tags selection - prepare failed." ); - return; - } - - char sqlSelectTagValue[] = "SELECT val FROM tag WHERE object_id=? AND object_type=? AND key=?"; - rc = sqlite3_prepare_v2( mDatabase, sqlSelectTagValue, sizeof( sqlSelectTagValue ), &mCustomTagsStmt, 0 ); - if ( rc != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for tag value selection - prepare failed." ); - return; - } - - // prepare statements for feature retrieval - char sqlSelectWay[] = "SELECT id, wkb, timestamp, user FROM way WHERE id=? AND status<>'R' AND u=1"; - rc = sqlite3_prepare_v2( mDatabase, sqlSelectWay, sizeof( sqlSelectWay ), &mWayStmt, 0 ); - if ( rc != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for way retrieval - prepare failed." ); - return; - } - - char sqlSelectNode[] = "SELECT id, lat, lon, timestamp, user FROM node WHERE id=? AND usage=0 AND status<>'R' AND u=1"; - rc = sqlite3_prepare_v2( mDatabase, sqlSelectNode, sizeof( sqlSelectNode ), &mNodeStmt, 0 ); - if ( rc != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for node retrieval - prepare failed." ); - return; - } - - if ( mFeatureType == PointType ) - { - char sqlSelectPoints[] = "SELECT id, lat, lon, timestamp, user FROM node WHERE usage=0 AND status<>'R' AND u=1"; - char sqlSelectPointsIn[] = "SELECT id, lat, lon, timestamp, user FROM node WHERE usage=0 AND status<>'R' AND u=1 \ - AND lat>=? AND lat<=? AND lon>=? AND lon<=?"; - - if ( sqlite3_prepare_v2( mDatabase, sqlSelectPoints, sizeof( sqlSelectPoints ), &mSelectFeatsStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for points retrieval - prepare failed." ); - return; - } - if ( sqlite3_prepare_v2( mDatabase, sqlSelectPointsIn, sizeof( sqlSelectPointsIn ), &mSelectFeatsInStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for points in boundary retrieval - prepare failed." ); - return; - } - } - else if ( mFeatureType == LineType ) - { - char sqlSelectLines[] = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=0 AND w.status<>'R' AND w.u=1"; - char sqlSelectLinesIn[] = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=0 AND w.status<>'R' AND w.u=1 \ - AND (((w.max_lat between ? AND ?) OR (w.min_lat between ? AND ?) OR (w.min_lat?)) \ - AND ((w.max_lon between ? AND ?) OR (w.min_lon between ? AND ?) OR (w.min_lon?)))"; - - if ( sqlite3_prepare_v2( mDatabase, sqlSelectLines, sizeof( sqlSelectLines ), &mSelectFeatsStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for lines retrieval - prepare failed." ); - return; - } - if ( sqlite3_prepare_v2( mDatabase, sqlSelectLinesIn, sizeof( sqlSelectLinesIn ), &mSelectFeatsInStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for lines in boundary retrieval - prepare failed." ); - return; - } - } - else // mFeatureType == PolygonType - { - char sqlSelectPolys[] = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=1 AND w.status<>'R' AND w.u=1"; - char sqlSelectPolysIn[] = "SELECT w.id, w.wkb, w.timestamp, w.user FROM way w WHERE w.closed=1 AND w.status<>'R' AND w.u=1 \ - AND (((w.max_lat between ? AND ?) OR (w.min_lat between ? AND ?) OR (w.min_lat?)) \ - AND ((w.max_lon between ? AND ?) OR (w.min_lon between ? AND ?) OR (w.min_lon?)))"; - - if ( sqlite3_prepare_v2( mDatabase, sqlSelectPolys, sizeof( sqlSelectPolys ), &mSelectFeatsStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for polygons retrieval - prepare failed." ); - return; - } - if ( sqlite3_prepare_v2( mDatabase, sqlSelectPolysIn, sizeof( sqlSelectPolysIn ), &mSelectFeatsInStmt, 0 ) != SQLITE_OK ) - { - QgsDebugMsg( "sqlite3 statement for polygons in boundary retrieval - prepare failed." ); - return; - } - } - // finally OSM provider is initialized and considered to be valid! mValid = true; } @@ -338,16 +246,8 @@ QgsOSMDataProvider::QgsOSMDataProvider( QString uri ) QgsOSMDataProvider::~QgsOSMDataProvider() { - // destruct selected geometry - delete mSelectionRectangleGeom; - - // finalize all created sqlite3 statements - sqlite3_finalize( mTagsStmt ); - sqlite3_finalize( mCustomTagsStmt ); - sqlite3_finalize( mWayStmt ); - sqlite3_finalize( mNodeStmt ); - sqlite3_finalize( mSelectFeatsStmt ); - sqlite3_finalize( mSelectFeatsInStmt ); + if ( mActiveIterator ) + mActiveIterator->close(); // close opened sqlite3 database if ( mDatabase ) @@ -422,82 +322,6 @@ QString QgsOSMDataProvider::storageType() const } -void QgsOSMDataProvider::select( QgsAttributeList fetchAttributes, - QgsRectangle rect, - bool fetchGeometry, - bool useIntersect ) -{ - // re-initialization - delete mSelectionRectangleGeom; - if ( mDatabaseStmt ) - // we must reset sqlite3 statement after recent selection - make it ready for next features selection - sqlite3_reset( mDatabaseStmt ); - - // store list of attributes to fetch, rectangle of area, geometry, etc. - mSelectionRectangle = rect; - mSelectionRectangleGeom = QgsGeometry::fromRect( rect ); - mAttributesToFetch = fetchAttributes; - - // set flags - mFetchGeom = fetchGeometry; - mSelectUseIntersect = useIntersect; - - if ( mSelectionRectangle.isEmpty() ) - { - // we want to select all features from OSM data; we will use mSelectFeatsStmt - // sqlite3 statement that is well prepared for this purpose - mDatabaseStmt = mSelectFeatsStmt; - return; - } - - // we want to select features from specified boundary; we will use mSelectFeatsInStmt - // sqlite3 statement that is well prepared for this purpose - mDatabaseStmt = mSelectFeatsInStmt; - - if ( mFeatureType == PointType ) - { - // binding variables (boundary) for points selection! - sqlite3_bind_double( mDatabaseStmt, 1, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 2, mSelectionRectangle.yMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 3, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 4, mSelectionRectangle.xMaximum() ); - } - else if ( mFeatureType == LineType ) - { - // binding variables (boundary) for lines selection! - sqlite3_bind_double( mDatabaseStmt, 1, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 2, mSelectionRectangle.yMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 3, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 4, mSelectionRectangle.yMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 5, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 6, mSelectionRectangle.yMaximum() ); - - sqlite3_bind_double( mDatabaseStmt, 7, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 8, mSelectionRectangle.xMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 9, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 10, mSelectionRectangle.xMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 11, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 12, mSelectionRectangle.xMaximum() ); - } - else // mFeatureType == PolygonType - { - // binding variables (boundary) for polygons selection! - sqlite3_bind_double( mDatabaseStmt, 1, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 2, mSelectionRectangle.yMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 3, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 4, mSelectionRectangle.yMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 5, mSelectionRectangle.yMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 6, mSelectionRectangle.yMaximum() ); - - sqlite3_bind_double( mDatabaseStmt, 7, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 8, mSelectionRectangle.xMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 9, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 10, mSelectionRectangle.xMaximum() ); - sqlite3_bind_double( mDatabaseStmt, 11, mSelectionRectangle.xMinimum() ); - sqlite3_bind_double( mDatabaseStmt, 12, mSelectionRectangle.xMaximum() ); - } -} - int QgsOSMDataProvider::wayMemberCount( int wayId ) { @@ -530,322 +354,9 @@ int QgsOSMDataProvider::wayMemberCount( int wayId ) } -bool QgsOSMDataProvider::nextFeature( QgsFeature& feature ) -{ - // load next requested feature from sqlite3 database - switch ( sqlite3_step( mDatabaseStmt ) ) - { - case SQLITE_DONE: // no more features to return - feature.setValid( false ); - return false; - - case SQLITE_ROW: // another feature to return - if ( mFeatureType == PointType ) - return fetchNode( feature, mDatabaseStmt, mFetchGeom, mAttributesToFetch ); - else if ( mFeatureType == LineType ) - return fetchWay( feature, mDatabaseStmt, mFetchGeom, mAttributesToFetch ); - else if ( mFeatureType == PolygonType ) - return fetchWay( feature, mDatabaseStmt, mFetchGeom, mAttributesToFetch ); - - default: - if ( mFeatureType == PointType ) - { - QgsDebugMsg( "Getting next feature of type failed." ); - } - else if ( mFeatureType == LineType ) - { - QgsDebugMsg( "Getting next feature of type failed." ); - } - else if ( mFeatureType == PolygonType ) - { - QgsDebugMsg( "Getting next feature of type failed." ); - } - feature.setValid( false ); - return false; - } -} - - -bool QgsOSMDataProvider::featureAtId( QgsFeatureId featureId, - QgsFeature& feature, - bool fetchGeometry, - QgsAttributeList fetchAttributes ) -{ - // load exact feature from sqlite3 database - if ( mFeatureType == PointType ) - { - sqlite3_bind_int64( mNodeStmt, 1, featureId ); - - if ( sqlite3_step( mNodeStmt ) != SQLITE_ROW ) - { - QgsDebugMsg( QString( "Getting information about point with id=%1 failed." ).arg( featureId ) ); - sqlite3_reset( mNodeStmt ); - return false; - } - - fetchNode( feature, mNodeStmt, fetchGeometry, fetchAttributes ); - - // prepare statement for next call - sqlite3_reset( mNodeStmt ); - } - else if (( mFeatureType == LineType ) || ( mFeatureType == PolygonType ) ) - { - sqlite3_bind_int64( mWayStmt, 1, featureId ); - - if ( sqlite3_step( mWayStmt ) != SQLITE_ROW ) - { - QgsDebugMsg( QString( "Getting information about way with id=%1 failed." ).arg( featureId ) ); - sqlite3_reset( mWayStmt ); - return false; - } - - fetchWay( feature, mWayStmt, fetchGeometry, fetchAttributes ); - - // prepare statement for next call - sqlite3_reset( mWayStmt ); - } - return true; -} - - -bool QgsOSMDataProvider::fetchNode( QgsFeature& feature, sqlite3_stmt* stmt, bool fetchGeometry, QgsAttributeList& fetchAttrs ) -{ - int selId = sqlite3_column_int( stmt, 0 ); - double selLat = sqlite3_column_double( stmt, 1 ); - double selLon = sqlite3_column_double( stmt, 2 ); - const char* selTimestamp = ( const char* ) sqlite3_column_text( stmt, 3 ); - const char* selUser = ( const char* ) sqlite3_column_text( stmt, 4 ); - - // fetch feature's geometry - if ( fetchGeometry ) - { - char* geo = new char[21]; - memset( geo, 0, 21 ); - geo[0] = QgsApplication::endian(); - geo[geo[0] == QgsApplication::NDR ? 1 : 4] = QGis::WKBPoint; - memcpy( geo + 5, &selLon, sizeof( double ) ); - memcpy( geo + 13, &selLat, sizeof( double ) ); - feature.setGeometryAndOwnership(( unsigned char * )geo, 24 ); // 24 is size of wkb point structure! - } - - // fetch attributes - QgsAttributeList::const_iterator iter; - for ( iter = fetchAttrs.begin(); iter != fetchAttrs.end(); ++iter ) - { - switch ( *iter ) - { - case TimestampAttr: - feature.addAttribute( TimestampAttr, QString::fromUtf8( selTimestamp ) ); break; - case UserAttr: - feature.addAttribute( UserAttr, QString::fromUtf8( selUser ) ); break; - case TagAttr: - feature.addAttribute( TagAttr, tagsForObject( "node", selId ) ); break; - - default: // suppose it's a custom tag - if ( *iter >= CustomTagAttr && *iter < CustomTagAttr + mCustomTagsList.count() ) - { - feature.addAttribute( *iter, tagForObject( "node", selId, mCustomTagsList[*iter-CustomTagAttr] ) ); - } - } - } - - feature.setFeatureId( selId ); - feature.setValid( true ); - feature.setFieldMap( &mAttributeFields ); // allow name-based attribute lookups - return true; -} - - -bool QgsOSMDataProvider::fetchWay( QgsFeature& feature, sqlite3_stmt* stmt, bool fetchGeometry, QgsAttributeList& fetchAttrs ) -{ - int selId; - const char* selTimestamp; - const char* selUser; - QgsGeometry *theGeometry = NULL; - bool fetchMoreRows = true; - int rc = -1; - - do - { - selId = sqlite3_column_int( stmt, 0 ); - selTimestamp = ( const char* ) sqlite3_column_text( stmt, 2 ); - selUser = ( const char* ) sqlite3_column_text( stmt, 3 ); - unsigned char *pzBlob = 0; - int pnBlob = 0; - - if ( fetchGeometry || mSelectUseIntersect || !mSelectionRectangle.isEmpty() ) - { - pnBlob = sqlite3_column_bytes( stmt, 1 ); - pzBlob = new unsigned char[pnBlob]; - memcpy( pzBlob, sqlite3_column_blob( stmt, 1 ), pnBlob ); - - // create geometry - theGeometry = new QgsGeometry(); - theGeometry->fromWkb(( unsigned char * ) pzBlob, pnBlob ); - } - - if ( theGeometry && ( theGeometry->type() == 3 ) && ( selId != 0 ) ) - { - // line/polygon geometry is not cached! - char *geo; - int geolen; - updateWayWKB( selId, ( mFeatureType == LineType ) ? 0 : 1, &geo, &geolen ); - theGeometry->fromWkb(( unsigned char * ) geo, ( size_t ) geolen ); - } - - if ( mSelectUseIntersect ) - { - // when using intersect, some features might be ignored if they don't intersect the selection rect - // intersect is a costly operation, use rectangle converted to geos for less conversions - // (this is usually used during identification of an object) - if ( theGeometry->intersects( mSelectionRectangleGeom ) ) - fetchMoreRows = false; - } - else if ( !mSelectionRectangle.isEmpty() ) - { - // when using selection rectangle but without exact intersection, check only overlap of bounding box - // (usually used when drawing) - if ( mSelectionRectangle.intersects( theGeometry->boundingBox() ) ) - fetchMoreRows = false; - } - else - { - // no filter => always accept the new feature - // (used in attribute table) - fetchMoreRows = false; - } - - // delete the geometry (if any) in case we're not going to use it anyway - if ( fetchMoreRows ) - delete theGeometry; - } - while ( fetchMoreRows && (( rc = sqlite3_step( stmt ) ) == SQLITE_ROW ) ); - - // no more features to return - if ( rc == SQLITE_DONE ) - { - sqlite3_exec( mDatabase, "COMMIT;", 0, 0, 0 ); - feature.setValid( false ); - return false; - } - - // fetch feature's geometry - if ( fetchGeometry ) - { - feature.setGeometry( theGeometry ); - } - else - { - delete theGeometry; // make sure it's deleted - } - - // fetch attributes - QgsAttributeList::const_iterator iter; - for ( iter = fetchAttrs.begin(); iter != fetchAttrs.end(); ++iter ) - { - switch ( *iter ) - { - case TimestampAttr: - feature.addAttribute( TimestampAttr, QString::fromUtf8( selTimestamp ) ); - break; - case UserAttr: - feature.addAttribute( UserAttr, QString::fromUtf8( selUser ) ); - break; - case TagAttr: - feature.addAttribute( TagAttr, tagsForObject( "way", selId ) ); - break; - default: // suppose it's a custom tag - if ( *iter >= CustomTagAttr && *iter < CustomTagAttr + mCustomTagsList.count() ) - { - feature.addAttribute( *iter, tagForObject( "way", selId, mCustomTagsList[*iter-CustomTagAttr] ) ); - } - } - } - feature.setFeatureId( selId ); - feature.setValid( true ); - feature.setFieldMap( &mAttributeFields ); // allow name-based attribute lookups - return true; -} - - - -QString QgsOSMDataProvider::tagForObject( const char* type, int id, QString tagKey ) -{ - sqlite3_bind_int( mCustomTagsStmt, 1, id ); - sqlite3_bind_text( mCustomTagsStmt, 2, type, -1, 0 ); - QByteArray tag = tagKey.toUtf8(); // must keep the byte array until the query is run - sqlite3_bind_text( mCustomTagsStmt, 3, tag.data(), -1, 0 ); - - QString value; - int rc; - - if (( rc = sqlite3_step( mCustomTagsStmt ) ) == SQLITE_ROW ) - { - const char* tagVal = ( const char* ) sqlite3_column_text( mCustomTagsStmt, 0 ); - value = QString::fromUtf8( tagVal ); - } - else - { - // tag wasn't found - sqlite3_reset( mCustomTagsStmt ); // make ready for next retrieval - return ""; - } - - sqlite3_reset( mCustomTagsStmt ); // make ready for next retrieval - return value; -} - - -QString QgsOSMDataProvider::tagsForObject( const char* type, int id ) -{ - sqlite3_bind_int( mTagsStmt, 1, id ); - sqlite3_bind_text( mTagsStmt, 2, type, -1, 0 ); - - QString tags; - int rc; - - while (( rc = sqlite3_step( mTagsStmt ) ) == SQLITE_ROW ) - { - const char* tagKey = ( const char* ) sqlite3_column_text( mTagsStmt, 0 ); - const char* tagVal = ( const char* ) sqlite3_column_text( mTagsStmt, 1 ); - QString key = QString::fromUtf8( tagKey ); - QString val = QString::fromUtf8( tagVal ); - - // we concatenate tags this way: "key1"="val1","key2"="val2","key3"="val3" - // -all ; in keyX and valX are replaced by ;; - // -all , in keyX and valX are replaced by ; - // -all - in keyX and valX are replaced by -- - // -all = in keyX and valX are replaced by - - key = key.replace( ';', ";;" ); - val = val.replace( ';', ";;" ); - key = key.replace( ',', ";" ); - val = val.replace( ',', ";" ); - - key = key.replace( '-', "--" ); - val = val.replace( '-', "--" ); - key = key.replace( '=', "-" ); - val = val.replace( '=', "-" ); - - if ( tags.count() > 0 ) - tags += ","; - - tags += QString( "\"%1\"=\"%2\"" ).arg( key ).arg( val ); - } - - if ( rc != SQLITE_DONE ) - { - // no tags for object - //QgsDebugMsg(QString("tags for object failed: type %1 id %2").arg(type).arg(id)); - } - - sqlite3_reset( mTagsStmt ); // make ready for next retrieval - return tags; -} - - QgsRectangle QgsOSMDataProvider::extent() { - return QgsRectangle( xMin, yMin, xMax, yMax ); + return mExtent; } @@ -883,6 +394,12 @@ long QgsOSMDataProvider::featureCount() const } +QgsFeatureIterator QgsOSMDataProvider::getFeatures( const QgsFeatureRequest& request ) +{ + return QgsFeatureIterator( new QgsOSMFeatureIterator( this, request ) ); +} + + bool QgsOSMDataProvider::isValid() { return mValid; @@ -940,7 +457,7 @@ QGISEXTERN bool isProvider() } -const QgsFieldMap & QgsOSMDataProvider::fields() const +const QgsFields & QgsOSMDataProvider::fields() const { return mAttributeFields; } @@ -952,15 +469,6 @@ QgsCoordinateReferenceSystem QgsOSMDataProvider::crs() } -void QgsOSMDataProvider::rewind() -{ - // we have to reset precompiled database statement; thanx to this action the first feature - // (returned by the query) will be selected again with the next calling of sqlite3_step(mDatabaseStmt) - if ( mDatabaseStmt ) - sqlite3_reset( mDatabaseStmt ); -} - - int QgsOSMDataProvider::freeFeatureId() { // todo: optimalization - wouldn't be better to keep minimum id in meta table? @@ -1395,14 +903,13 @@ bool QgsOSMDataProvider::loadOsmFile( QString osm_filename ) } // store information got with handler into provider member variables - xMin = handler->xMin; // boundaries defining the area of all features - xMax = handler->xMax; - yMin = handler->yMin; - yMax = handler->yMax; + // boundaries defining the area of all features + mExtent = QgsRectangle( handler->xMin, handler->yMin, handler->xMax, handler->yMax ); // storing boundary information into database QString cmd3 = QString( "INSERT INTO meta ( key, val ) VALUES ('default-area-boundaries','%1:%2:%3:%4');" ) - .arg( xMin, 0, 'f', 10 ).arg( yMin, 0, 'f', 10 ).arg( xMax, 0, 'f', 10 ).arg( yMax, 0, 'f', 10 ); + .arg( mExtent.xMinimum(), 0, 'f', 10 ).arg( mExtent.yMinimum(), 0, 'f', 10 ) + .arg( mExtent.xMaximum(), 0, 'f', 10 ).arg( mExtent.yMaximum(), 0, 'f', 10 ); QByteArray cmd_bytes3 = cmd3.toAscii(); const char *ptr3 = cmd_bytes3.data(); diff --git a/src/providers/osm/osmprovider.h b/src/providers/osm/osmprovider.h index 806bf0aac75..c3b386dd6da 100644 --- a/src/providers/osm/osmprovider.h +++ b/src/providers/osm/osmprovider.h @@ -1,3 +1,6 @@ +#ifndef OSMPROVIDER_H +#define OSMPROVIDER_H + /*************************************************************************** osmprovider.h - provider for OSM; stores OSM data in sqlite3 DB ------------------ @@ -19,6 +22,8 @@ class QgsVectorLayer; +class QgsOSMFeatureIterator; + /** * Quantum GIS provider for OpenStreetMap data. */ @@ -29,7 +34,8 @@ class QgsOSMDataProvider: public QgsVectorDataProvider private: //! provider manages features with one of three geometry types; variable determines feature type of this provider - enum { PointType, LineType, PolygonType } mFeatureType; + enum OSMType { PointType, LineType, PolygonType }; + OSMType mFeatureType; //! supported feature attributes enum Attribute { TimestampAttr = 0, UserAttr = 1, TagAttr, CustomTagAttr }; @@ -53,7 +59,7 @@ class QgsOSMDataProvider: public QgsVectorDataProvider QObject* mInitObserver; //! boundary of all OSM data that provider manages - double xMin, xMax, yMin, yMax; + QgsRectangle mExtent; //! list of feature tags for which feature attributes are created QStringList mCustomTagsList; @@ -72,44 +78,11 @@ class QgsOSMDataProvider: public QgsVectorDataProvider //! pointer to sqlite3 database that keeps OSM data sqlite3 *mDatabase; - //! pointer to main sqlite3 database statement object; this statement serves to select OSM data - sqlite3_stmt *mDatabaseStmt; - - //! pointer to main sqlite3 database statement object; this statement serves to select OSM data - sqlite3_stmt *mSelectFeatsStmt; - - //! pointer to main sqlite3 db stmt object; this stmt serves to select OSM data from some boundary - sqlite3_stmt *mSelectFeatsInStmt; - - //! sqlite3 database statement ready to select all feature tags - sqlite3_stmt *mTagsStmt; - - //! sqlite3 database statement ready to select concrete feature tag - sqlite3_stmt *mCustomTagsStmt; - - //! sqlite3 database statement for exact way selection - sqlite3_stmt *mWayStmt; - - //! sqlite3 database statement for exact node selection - sqlite3_stmt *mNodeStmt; - - // variables used to select OSM data; used mainly in select(), nextFeature() functions: - //! list of supported attribute fields - QgsFieldMap mAttributeFields; - - //! which attributes should be fetched after calling of select() function - QgsAttributeList mAttributesToFetch; - - //! features from which area should be fetched after calling of select() function? - QgsRectangle mSelectionRectangle; - - //! geometry object of area from which features should be fetched after calling of select() function - QgsGeometry* mSelectionRectangleGeom; - - //! determines if intersect should be used while selecting OSM data - bool mSelectUseIntersect; + QgsFields mAttributeFields; + friend class QgsOSMFeatureIterator; + QgsOSMFeatureIterator* mActiveIterator; //!< pointer to currently active iterator (0 if none) public: @@ -133,38 +106,6 @@ class QgsOSMDataProvider: public QgsVectorDataProvider */ virtual QString storageType() const; - /** Select features based on a bounding rectangle. Features can be retrieved with calls to getNextFeature. - * @param fetchAttributes list of attributes which should be fetched - * @param rect spatial filter - * @param fetchGeometry true if the feature geometry should be fetched - * @param useIntersect true if an accurate intersection test should be used, - * false if a test based on bounding box is sufficient - */ - virtual void select( QgsAttributeList fetchAttributes = QgsAttributeList(), - QgsRectangle rect = QgsRectangle(), - bool fetchGeometry = true, - bool useIntersect = false ); - - /** - * Get the next feature resulting from a select operation. - * @param feature feature which will receive data from the provider - * @return true when there was a feature to fetch, false when end was hit - */ - virtual bool nextFeature( QgsFeature& feature ); - - /** - * Gets the feature at the given feature ID. - * @param featureId id of the feature - * @param feature feature which will receive the data - * @param fetchGeometry if true, geometry will be fetched from the provider - * @param fetchAttributes a list containing the indexes of the attribute fields to copy - * @return True when feature was found, otherwise false - */ - virtual bool featureAtId( QgsFeatureId featureId, - QgsFeature& feature, - bool fetchGeometry = true, - QgsAttributeList fetchAttributes = QgsAttributeList() ); - /** * Get feature type. * @return int representing the feature type @@ -181,12 +122,7 @@ class QgsOSMDataProvider: public QgsVectorDataProvider * Return a map of indexes with field names for this layer * @return map of fields */ - virtual const QgsFieldMap & fields() const; - - /** - * Restart reading features from previous select operation. - */ - virtual void rewind(); + virtual const QgsFields & fields() const; /** * Returns a bitmask containing the supported capabilities @@ -196,6 +132,8 @@ class QgsOSMDataProvider: public QgsVectorDataProvider */ virtual int capabilities() const; + virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest& request ); + // Implementation of QgsDataProvider functions @@ -330,42 +268,6 @@ class QgsOSMDataProvider: public QgsVectorDataProvider * @return number of way members */ int wayMemberCount( int wayId ); - - /** - * Function fetches one node from current sqlite3 statement. - * @param feature output; feature representing fetched node - * @param stmt database statement to fetch node from - * @param fetchGeometry determines if node geometry should be fetched also - * @param fetchAttrs list of attributes to be fetched with node - * @return success of failure flag (true/false) - */ - bool fetchNode( QgsFeature& feature, sqlite3_stmt* stmt, bool fetchGeometry, QgsAttributeList& fetchAttrs ); - - /** - * Function fetches one way from current sqlite3 statement. - * @param feature output; feature representing fetched way - * @param stmt database statement to fetch way from - * @param fetchGeometry determines if way geometry should be fetched also - * @param fetchAttrs list of attributes to be fetched with way - * @return success of failure flag (true/false) - */ - bool fetchWay( QgsFeature& feature, sqlite3_stmt* stmt, bool fetchGeometry, QgsAttributeList& fetchAttrs ); - - /** - * Function returns string of concatenated tags of specified feature. - * @param type type of feature (one of "node","way","relation") - * @param id feature identifier - * @return string of tags concatenation - */ - QString tagsForObject( const char* type, int id ); - - /** - * Function returns one tag value of specified feature and specified key. - * @param type type of feature (one of "node","way","relation") - * @param id feature identifier - * @param tagKey tag key - * @return tag value - */ - QString tagForObject( const char* type, int id, QString tagKey ); }; +#endif