Index: python/core/qgsvectorlayer.sip =================================================================== --- python/core/qgsvectorlayer.sip (revision 11113) +++ python/core/qgsvectorlayer.sip (working copy) @@ -449,6 +449,25 @@ */ virtual void updateExtents(); + /** + * Fetch the specified feature from provider, if it is cached. This + * must be called every time the provider data change outside vector layer control. + * @param featureId id of feature that should be reloaded + * @param rectFrom hint of rectangle, where the feature resided. If not provided, + * whole cache will be searched for the id. + * @param rectTo hint of rectangle, where the feature resides now. If not provided, + * all provider features will be searched for the id. + **/ + void recacheFeature( int featureId, QgsRectangle rectFrom=QgsRectangle(), QgsRectangle rectTo=QgsRectangle() ); + + /** + * Fetch features from specified rectangle from provider, if it is cached. This + * must be called every time the provider data change outside vector layer control. + * @param rect rectangle containing changed features. All features within + * the rectangle will be reloaded. + **/ + void recacheFeatures( QgsRectangle rect ); + signals: /** This signal is emited when selection was changed */ Index: src/app/qgsoptions.cpp =================================================================== --- src/app/qgsoptions.cpp (revision 11113) +++ src/app/qgsoptions.cpp (working copy) @@ -140,6 +140,15 @@ cbxHideSplash->setChecked( settings.value( "/qgis/hideSplash", false ).toBool() ); cbxAttributeTableDocked->setChecked( settings.value( "/qgis/dockAttributeTable", false ).toBool() ); + //features cache mode + QString cacheMode = settings.value( "qgis/vectorLayerCacheMode", "heuristics" ).toString(); + if ( cacheMode == "all" ) + radCacheAll->setChecked(true); + else if ( cacheMode == "nothing" ) + radCacheNothing->setChecked(true); + else + radCacheHeuristics->setChecked(true); + //set the colour for selections int myRed = settings.value( "/qgis/default_selection_color_red", 255 ).toInt(); int myGreen = settings.value( "/qgis/default_selection_color_green", 255 ).toInt(); @@ -345,7 +354,13 @@ settings.setValue( "qgis/capitaliseLayerName", capitaliseCheckBox->isChecked() ); settings.setValue( "qgis/askToSaveProjectChanges", chbAskToSaveProjectChanges->isChecked() ); settings.setValue( "qgis/warnOldProjectVersion", chbWarnOldProjectVersion->isChecked() ); - + + settings.setValue( "qgis/vectorLayerCacheMode", + radCacheNothing->isChecked() ? "nothing" : ( + radCacheAll->isChecked() ? "all" : "heuristics" + ) + ); + //overlay placement method int overlayIndex = mOverlayAlgorithmComboBox->currentIndex(); if ( overlayIndex == 1 ) Index: src/core/qgsvectorlayer.cpp =================================================================== --- src/core/qgsvectorlayer.cpp (revision 11113) +++ src/core/qgsvectorlayer.cpp (working copy) @@ -71,6 +71,7 @@ #include "qgsvectoroverlay.h" #include "qgslogger.h" #include "qgsmaplayerregistry.h" +#include "qgsfeaturecache.h" #ifdef Q_WS_X11 #include "qgsclipper.h" @@ -107,6 +108,8 @@ mActiveCommand( NULL ) { mActions = new QgsAttributeAction; + + mFeatureCache=new QgsFeatureCache; // if we're given a provider type, try to create and bind one to this layer if ( ! mProviderKey.isEmpty() ) @@ -167,9 +170,13 @@ delete mLabel; +#ifdef ENABLE_GEOMETRYCACHE // Destroy any cached geometries and clear the references to them deleteCachedGeometries(); +#endif + delete mFeatureCache; + delete mActions; //delete remaining overlays @@ -687,9 +694,11 @@ if ( mEditable ) { +#ifdef ENABLE_GEOMETRYCACHE // Destroy all cached geometries and clear the references to them deleteCachedGeometries(); mCachedGeometriesRect = rendererContext.extent(); +#endif vertexMarker = currentVertexMarkerType(); mVertexMarkerOnlyForSelection = settings.value( "/qgis/digitizing/marker_only_for_selected", false ).toBool(); } @@ -699,12 +708,57 @@ int featureCount = 0; QgsFeature fet; QgsAttributeList attributes = mRenderer->classificationAttributes(); - select( attributes, rendererContext.extent() ); + + QgsRectangle renderExtent=rendererContext.extent(); + + //apply cache mode + QSettings settings; + QString cacheMode=settings.value( "qgis/vectorLayerCacheMode", "heuristics" ).toString(); + if ( cacheMode == "heuristics" ) + mFeatureCache->setCacheMode(QgsFeatureCache::CACHE_HEURISTICS); + else if ( cacheMode == "all" ) + mFeatureCache->setCacheMode(QgsFeatureCache::CACHE_ALL); + else + mFeatureCache->setCacheMode(QgsFeatureCache::CACHE_NOTHING); + + mFeatureCache->applyCurrentCacheMode(renderExtent); + + select( attributes, renderExtent); + if ( !mFeatureCache->isSelectValid() ) + { +#ifdef LIMIT_CACHED_EXTENT + if ( mDataProvider->extent() == mFeatureCache->getCachedRectangle() ) + { + //everything is cached already, retrieve selected part + //note: this will fail, if requested classification attributes changed + mFeatureCache->makeSelectValid(); + } + - + if ( !mFeatureCache->isSelectValid() ) + { + //cache still invalid, we'll have to refill the cache + + if ( renderExtent.contains( mDataProvider->extent() ) ) + { + //only cache as big rectangle as the provider can provide + select( attributes, mDataProvider->extent() ); + } +#endif + mFeatureCache->clear(); +#ifdef LIMIT_CACHED_EXTENT + } +#endif + } + try { while ( nextFeature( fet ) ) { + if ( !mFeatureCache->isInitialized() ) + { + mFeatureCache->insert( fet ); + } if ( rendererContext.renderingStopped() ) { @@ -736,8 +790,10 @@ if ( mEditable ) { +#ifdef ENABLE_GEOMETRYCACHE // Cache this for the use of (e.g.) modifying the feature's uncommitted geometry. mCachedGeometries[fet.id()] = *fet.geometry(); +#endif if ( !mVertexMarkerOnlyForSelection || sel ) { @@ -770,6 +826,11 @@ ++featureCount; } + + if ( !mFeatureCache->isInitialized() ) + { + mFeatureCache->setAsInitialized(); + } } catch ( QgsCsException &cse ) { @@ -787,18 +848,22 @@ if ( mEditable ) { +#ifdef ENABLE_GEOMETRYCACHE QgsDebugMsg( QString( "Cached %1 geometries." ).arg( mCachedGeometries.count() ) ); +#endif } return TRUE; // Assume success always } +#ifdef ENABLE_GEOMETRYCACHE void QgsVectorLayer::deleteCachedGeometries() { // Destroy any cached geometries mCachedGeometries.clear(); mCachedGeometriesRect = QgsRectangle(); } +#endif void QgsVectorLayer::drawVertexMarker( int x, int y, QPainter& p, QgsVectorLayer::VertexMarkerType type ) { @@ -1193,6 +1258,8 @@ mFetchChangedGeomIt = mChangedGeometries.begin(); } + QgsAttributeList attrToSelect; + //look in the normal features of the provider if ( mFetchAttributes.size() > 0 ) { @@ -1208,20 +1275,39 @@ provAttributes << *it; } - mDataProvider->select( provAttributes, rect, fetchGeometries, useIntersect ); + attrToSelect=provAttributes; } else - mDataProvider->select( mFetchAttributes, rect, fetchGeometries, useIntersect ); + attrToSelect=mFetchAttributes; } else { - mDataProvider->select( QgsAttributeList(), rect, fetchGeometries, useIntersect ); + attrToSelect=QgsAttributeList(); } + + bool cacheSelectSuccessful; + if ( rect == QgsRectangle() ) + { + //we can't give QgsRectangle() to feature cache's select(), as it would only select + //extent of cache - instead of extent of the real data + cacheSelectSuccessful=mFeatureCache->select( + attrToSelect, mDataProvider->extent(), fetchGeometries, useIntersect + ); + } + else + { + cacheSelectSuccessful=mFeatureCache->select( + attrToSelect, rect, fetchGeometries, useIntersect + ); + } + + if ( ! cacheSelectSuccessful ) + mDataProvider->select( attrToSelect, rect, fetchGeometries, useIntersect ); } bool QgsVectorLayer::nextFeature( QgsFeature &f ) { - if ( !mFetching ) + if ( !mFetching && !mFeatureCache->isSelectValid() ) return false; if ( mEditable ) @@ -1320,8 +1406,18 @@ // no more added features } - while ( dataProvider()->nextFeature( f ) ) + while ( true ) { + if ( mFeatureCache->isSelectValid() ) { + if ( !mFeatureCache->nextFeature ( f ) ) + break; + } + else + { + if ( !dataProvider()->nextFeature( f ) ) + break; + } + if ( mFetchConsidered.contains( f.id() ) ) continue; @@ -1400,19 +1496,45 @@ } } + bool gotFeature; + // regular features if ( fetchAttributes ) { - if ( mDataProvider->featureAtId( featureId, f, fetchGeometries, mDataProvider->attributeIndexes() ) ) + if ( !( + mFeatureCache->isInitialized() && + mFeatureCache->areAttributesCached( mDataProvider->attributeIndexes() ) && + mFeatureCache->featureAtId( featureId, f, fetchGeometries, true ) + ) ) { + //feature not in cache, try the provider + gotFeature=mDataProvider->featureAtId( featureId, f, fetchGeometries, mDataProvider->attributeIndexes() ); + } + else + { + gotFeature=true; + } + + if ( gotFeature ) + { updateFeatureAttributes( f ); return true; } } else { - if ( mDataProvider->featureAtId( featureId, f, fetchGeometries, QgsAttributeList() ) ) + if ( !( mFeatureCache->isInitialized() && mFeatureCache->featureAtId( featureId, f, fetchGeometries, false ) ) ) { + //feature not in cache, try the provider + gotFeature=mDataProvider->featureAtId( featureId, f, fetchGeometries, QgsAttributeList() ); + } + else + { + gotFeature=true; + } + + if ( gotFeature ) + { return true; } } @@ -1448,7 +1570,9 @@ // and add to the known added features. f.setFeatureId( addedIdLowWaterMark ); editFeatureAdd( f ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[f.id()] = *f.geometry(); +#endif setModified( true ); @@ -1471,22 +1595,35 @@ if ( mDataProvider ) { QgsGeometry geometry; + if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit +#ifdef ENABLE_GEOMETRYCACHE if ( !mCachedGeometries.contains( atFeatureId ) ) { return false; } geometry = mCachedGeometries[atFeatureId]; //mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; +#else + QgsFeature f; + + if ( !featureAtId( atFeatureId, f, true, false ) ) + { + return false; + } + geometry=*(f.geometry()); +#endif } else { geometry = mChangedGeometries[atFeatureId]; } geometry.insertVertex( x, y, beforeVertex ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[atFeatureId] = geometry; +#endif editGeometryChange( atFeatureId, geometry ); setModified( true, true ); // only geometry was changed @@ -1510,12 +1647,22 @@ if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit +#ifdef ENABLE_GEOMETRYCACHE if ( !mCachedGeometries.contains( atFeatureId ) ) { return false; } geometry = mCachedGeometries[atFeatureId]; //mChangedGeometries[atFeatureId] = mCachedGeometries[atFeatureId]; +#else + QgsFeature f; + + if ( !featureAtId( atFeatureId, f, true, false ) ) + { + return false; + } + geometry=*(f.geometry()); +#endif } else { @@ -1523,7 +1670,9 @@ } geometry.moveVertex( x, y, atVertex ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[atFeatureId] = geometry; +#endif editGeometryChange( atFeatureId, geometry ); setModified( true, true ); // only geometry was changed @@ -1547,11 +1696,21 @@ if ( !mChangedGeometries.contains( atFeatureId ) ) { // first time this geometry has changed since last commit +#ifdef ENABLE_GEOMETRYCACHE if ( !mCachedGeometries.contains( atFeatureId ) ) { return false; } geometry = mCachedGeometries[atFeatureId]; +#else + QgsFeature f; + + if ( !featureAtId( atFeatureId, f, true, false ) ) + { + return false; + } + geometry=*(f.geometry()); +#endif } else { @@ -1559,7 +1718,9 @@ } geometry.deleteVertex( atVertex ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[atFeatureId] = geometry; +#endif editGeometryChange( atFeatureId, geometry ); setModified( true, true ); // only geometry was changed @@ -1657,22 +1818,31 @@ QgsGeometry geom = *changedIt; int returnValue = geom.addIsland( ring ); editGeometryChange( selectedFeatureId, geom ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[selectedFeatureId] = geom; +#endif return returnValue; } //look if id of selected feature belongs to an added feature - /* for ( QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt ) { if ( addedIt->id() == selectedFeatureId ) { - return addedIt->geometry()->addIsland( ring ); - mCachedGeometries[selectedFeatureId] = *addedIt->geometry(); + QgsGeometry translateGeom( *( addedIt->geometry() ) ); + + int errorCode = translateGeom.addIsland( ring ); + if ( errorCode == 0 ) + { + editGeometryChange( selectedFeatureId, translateGeom ); + setModified( true, true ); + } + + return errorCode; } } - */ +#ifdef ENABLE_GEOMETRYCACHE //is the feature contained in the view extent (mCachedGeometries) ? QgsGeometryMap::iterator cachedIt = mCachedGeometries.find( selectedFeatureId ); if ( cachedIt != mCachedGeometries.end() ) @@ -1688,21 +1858,26 @@ } else //maybe the selected feature has been moved outside the visible area and therefore is not contained in mCachedGeometries { +#endif QgsFeature f; QgsGeometry* fGeom = 0; if ( featureAtId( selectedFeatureId, f, true, false ) ) { fGeom = f.geometryAndOwnership(); + if ( fGeom ) { int errorCode = fGeom->addIsland( ring ); editGeometryChange( selectedFeatureId, *fGeom ); setModified( true, true ); delete fGeom; + return errorCode; } } +#ifdef ENABLE_GEOMETRYCACHE } +#endif return 6; //geometry not found } @@ -1720,16 +1895,24 @@ } //look if id of selected feature belongs to an added feature - /* for ( QgsFeatureList::iterator addedIt = mAddedFeatures.begin(); addedIt != mAddedFeatures.end(); ++addedIt ) { if ( addedIt->id() == featureId ) { - return addedIt->geometry()->translate( dx, dy ); + QgsGeometry translateGeom( *( addedIt->geometry() ) ); + + int errorCode = translateGeom.translate( dx, dy ); + if ( errorCode == 0 ) + { + editGeometryChange( featureId, translateGeom ); + setModified( true, true ); + } + + return errorCode; } } - */ +#ifdef ENABLE_GEOMETRYCACHE //else look in mCachedGeometries to make access faster QgsGeometryMap::iterator cachedIt = mCachedGeometries.find( featureId ); if ( cachedIt != mCachedGeometries.end() ) @@ -1742,6 +1925,7 @@ } return errorCode; } +#endif //else get the geometry from provider (may be slow) QgsFeature f; @@ -1827,8 +2011,10 @@ { //change this geometry editGeometryChange( select_it->id(), *( select_it->geometry() ) ); +#ifdef ENABLE_GEOMETRYCACHE //update of cached geometries is necessary because we use addTopologicalPoints() later mCachedGeometries[select_it->id()] = *( select_it->geometry() ); +#endif //insert new features for ( int i = 0; i < newGeometries.size(); ++i ) @@ -2621,7 +2807,10 @@ } editGeometryChange( fid, *geom ); +#ifdef ENABLE_GEOMETRYCACHE mCachedGeometries[fid] = *geom; +#endif + setModified( true, true ); return true; } @@ -3030,7 +3219,9 @@ } } +#ifdef ENABLE_GEOMETRYCACHE deleteCachedGeometries(); +#endif if ( success ) { @@ -3040,6 +3231,8 @@ mUpdatedFields.clear(); mMaxUpdatedIndex = -1; undoStack()->clear(); + mFeatureCache->clear(); + emit editingStopped(); } @@ -3100,8 +3293,13 @@ mMaxUpdatedIndex = -1; } +#ifdef ENABLE_GEOMETRYCACHE deleteCachedGeometries(); +#endif + //TODO: is this needed here? I think not + //mFeatureCache->clear(); + mEditable = false; emit editingStopped(); @@ -3311,7 +3509,7 @@ { return 1; } - + QList featureList; QgsRectangle searchRect( startPoint.x() - snappingTolerance, startPoint.y() - snappingTolerance, startPoint.x() + snappingTolerance, startPoint.y() + snappingTolerance ); @@ -3320,6 +3518,7 @@ int n = 0; QgsFeature f; +#ifdef ENABLE_GEOMETRYCACHE if ( mCachedGeometriesRect.contains( searchRect ) ) { QgsDebugMsg( "Using cached geometries for snapping." ); @@ -3338,15 +3537,24 @@ else { // snapping outside cached area +#endif select( QgsAttributeList(), searchRect, true, true ); - + + if ( !mFeatureCache->isSelectValid() && ( mDataProvider->extent() == mFeatureCache->getCachedRectangle() ) ) + { + //everything is cached already, what ain't in cache, ain't nowhere in layer :-) + mFeatureCache->makeSelectValid(); + } + while ( nextFeature( f ) ) { snapToGeometry( startPoint, f.id(), f.geometry(), sqrSnappingTolerance, snappingResults, snap_to ); ++n; } +#ifdef ENABLE_GEOMETRYCACHE } +#endif return n == 0 ? 2 : 0; } @@ -4077,7 +4285,7 @@ } else { - // added feature TODO: + // added feature for ( int i = 0; i < mAddedFeatures.size(); i++ ) { if ( mAddedFeatures[i].id() == fid ) @@ -4096,3 +4304,58 @@ // it's not ideal to trigger refresh from here triggerRepaint(); } + +void QgsVectorLayer::recacheFeature( int featureId, QgsRectangle rectFrom, QgsRectangle rectTo ) +{ + if ( rectTo==QgsRectangle() ) + rectTo=mDataProvider->extent(); + + QgsRectangle cachedRect=mFeatureCache->getCachedRectangle(); + + rectFrom=rectFrom.intersect( &cachedRect ); + rectTo=rectTo.intersect( &cachedRect ); + + mFeatureCache->remove( featureId, rectFrom ); + + //now reinsert the feature from provider + if ( mDataProvider->capabilities() & QgsVectorDataProvider::SelectGeometryAtId ) + { + //either using existing method + QgsFeature fet; + + mDataProvider->featureAtId( featureId, fet, true, mRenderer->classificationAttributes() ); + mFeatureCache->insert( fet, false ); + } + else + { + //or fallback to search for feature id in specified rectangle + mDataProvider->select( mRenderer->classificationAttributes(), rectTo ); + + QgsFeature fet; + while ( mDataProvider->nextFeature( fet ) ) + { + if ( fet.id() == featureId ) + { + mFeatureCache->insert( fet, false ); + mDataProvider->rewind(); + break; + } + } + } +} + +void QgsVectorLayer::recacheFeatures( QgsRectangle rect ) +{ + QgsRectangle cachedRect=mFeatureCache->getCachedRectangle(); + rect=rect.intersect( &cachedRect ); + + mFeatureCache->remove( rect ); + + mDataProvider->select( mRenderer->classificationAttributes(), rect ); + + QgsFeature fet; + while ( mDataProvider->nextFeature( fet ) ) + { + mFeatureCache->insert( fet, false ); + } +} Index: src/core/qgsfeaturecache.h =================================================================== --- src/core/qgsfeaturecache.h (revision 0) +++ src/core/qgsfeaturecache.h (revision 0) @@ -0,0 +1,215 @@ +/*************************************************************************** + qgsfeaturecache.h - Spatial Feature Cache Class + -------------------------------------- + Date : 19-Jun-2009 + Copyright : (C) 2009 by Andrej Krutak + email : andree at andree.sk + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSFEATURECACHE_H +#define QGSFEATURECACHE_H + +#include +#include +#include "qgsfeature.h" +#include "qgis.h" +#include "qgsrectangle.h" +#include "qgslabel.h" +#include "qgsspatialindex.h" + +/** + * A feature cache class. Stores inserted features in memory and enables + * efficient selects and feature retrievals. + * + * Usage of the class: + * + * After creating the class object, the cache is empty. To fill, one first + * has to specify, what region will be filled - this is done by calling + * select() method, where one specifies rectangle and attributes to be cached. + * The method will obviously fail, because no data are cached yet. + * (Also, until the cache is filled, isInitialized() will return false.) + * + * After that, insert(feature, true) method has to be called to efficiently + * insert contained features. After all features are inserted, setAsInitialized() + * has to be called. This method efficiently commits all inserted features into + * the cache. After this, the cache is initialized and all subsequent calls + * to select(), using subset of previously specified rectangle and attributes, + * will be successful. Features may then be retrieved by repetetive calls + * of nextFeature(). + * + * One might want to retrieve features even if the requested rectangle doesn't + * completely fit into cached area. In case the requested attributes are + * a subset of cached ones, this is possible - just call makeSelectValid() + * after select(). By calling this, it's possible to retrieve even partial + * cache matches. + * + * To modify existing cache data, one may use insert(feature, false), and + * remove() methods, which to immediate changes to the cached data. Note that + * calling these methods for large ammounts of features may be quite slow - + * in case a big part of tree has to be 'edited', it might be much faster + * to clear() the cache and refill it again using previously mentioned method. + * + * There are several cache modes, that change the way cache keeps data stored. + * It's possible to set new mode using setCacheMode(). To apply the mode + * for a specified select, applyCurrentCacheMode() has to be called. The method + * tells cache, what rectangle is currently required by user - according to this + * the cache will determine, what data to discard (if any). +**/ +class CORE_EXPORT QgsFeatureCache +{ +public: + QgsFeatureCache(); + ~QgsFeatureCache(); + + /** + * Inserts the specified feature to cache. + * @param f feature to be inserted + * @param deferredInsert if true, feature will only be inserted after calling + * setAsInitialized(); otherwise the feature is inserted + * immediately (which is pretty costly in case of inserting + * more than a few features) + * @returns whether the operation was successful. + **/ + bool insert( QgsFeature &f, bool deferredInsert=true ); + + /** + * Remove feature with specified id. If rect is specified, it will be + * used as hint to find the feature (it could be e.g. feture's bounding rect.) + **/ + bool remove( int featureId, QgsRectangle rect = QgsRectangle() ); + + /** + * Remove features intersecting specified rectangle. + **/ + bool remove( QgsRectangle rect ); + + /** + * Returns, whether the cache is properly initialized (and thus the + * nextFeature() and featureAtId() are usable) + **/ + inline bool isInitialized() { return mIsInitialized; } + + /** + * Returns, whether the last called select() left the + * cache in state valid for iteration using nextFeature() + **/ + inline bool isSelectValid() { return mSelectValid; } + + /** + * Sets the cache as valid; to be called after it's filled with + * features (@see insertFeature()) and only when the cache was empty before. + * The cache parameters (contained rectangle, attributes) are set to current + * select parameters. + **/ + void setAsInitialized(); + + /** Clear the cache; also sets it as not valid */ + void clear(); + + /** + * Select features; returns whether the cache is valid for the specified select + * @param fetchAttributes attributes to fetch + * @param rect rectangle to fetch the features from. If rect==QgsRectangle(), + * all features in cache are selected. + * @param fetchGeometry also fetch geometries + * @param useIntersect only retrieve features that really (not only their bounding rect) intersect with rectangle + * @returns True, if select was successful (i.e. nextFeature() will return data). + **/ + bool select( QgsAttributeList fetchAttributes, + QgsRectangle rect = QgsRectangle(), + bool fetchGeometry = true, + bool useIntersect = false); + + /** + * Make the last selected features fetchable using nextFeature(), if at least selected + * attributes are subset of the the cached ones. + **/ + void makeSelectValid(); + + /** Retrieve next feature from the select; Returns false if there are no more features. */ + bool nextFeature( QgsFeature& feature ); + + /** Returns the feature with specified Id. */ + bool featureAtId( int featureId, QgsFeature &f, bool fetchGeometries = true, bool fetchAttributes = true ); + + /** cache modes */ + enum CacheMode { + ///store all inserted features + CACHE_ALL=0, + + /** + * Store features and use heuristics to crop the stored rectangle size at + * selects - and so optimize size of occupied memory. + **/ + CACHE_HEURISTICS, + + ///don't store any features + CACHE_NOTHING, + }; + + /** Sets cache mode. */ + void setCacheMode(CacheMode mode); + + /** Apply current cache mode to currently cached data (using specified rectangle). */ + void applyCurrentCacheMode(QgsRectangle const &rect); + + /** Returns cached rectangle. This is the biggest selectable rectangle. */ + QgsRectangle getCachedRectangle() { return mCachedRectangle; } + + /** Returns true, if cached attributes are superset of specified attributes list */ + bool areAttributesCached(QgsAttributeList list); + +private: + /** Filter out cached data outside the specified rectangle */ + void filterTo(QgsRectangle const &r); + + /** Contains, whether the cache has been filled for the current select */ + bool mIsInitialized; + + /** Holds cached features */ + QgsSpatialIndex *mCachedFeatures; + + /** Contains features waiting to be really inserted into tree by setAsInitialized() */ + QLinkedList mCachedFeaturesList; + + /** Rectangle that is currently cached */ + QgsRectangle mCachedRectangle; + + /** Contains attribute list, that are stored in cached features */ + QgsAttributeList mCachedAttributes; + + /** Holds, whether geometries are also cached (alongside attributes) */ + bool mCachedIncludingGeometries; + + /** Holds, whether the current select is valid */ + bool mSelectValid; + + /** Select parameters - attributes */ + QgsAttributeList mSelectedAttributes; + + /** Select parameters - rectangle */ + QgsRectangle mSelectedRectangle;; + + /** Select parameters - fetch geometries?*/ + bool mSelectedFetchGeometries; + + /** Select parameters - use intersect?*/ + bool mSelectedUseIntersect; + + /** List of selected features */ + QLinkedList mSelectedFeaturesList; + + /** Select parameters - iterator in the mCachedFeatures */ + QLinkedList::iterator mSelectedFeaturesIterator; + + CacheMode mCacheMode; +}; + +#endif //QGSFEATURECACHE_H Index: src/core/qgsvectorlayer.h =================================================================== --- src/core/qgsvectorlayer.h (revision 11113) +++ src/core/qgsvectorlayer.h (working copy) @@ -44,6 +44,7 @@ class QgsUndoCommand; class QgsVectorDataProvider; class QgsVectorOverlay; +class QgsFeatureCache; class QgsRectangle; @@ -511,6 +512,25 @@ */ virtual void updateExtents(); + /** + * Fetch the specified feature from provider, if it is cached. This + * must be called every time the provider data change outside vector layer control. + * @param featureId id of feature that should be reloaded + * @param rectFrom hint of rectangle, where the feature resided. If not provided, + * whole cache will be searched for the id. + * @param rectTo hint of rectangle, where the feature resides now. If not provided, + * all provider features will be searched for the id. + **/ + void recacheFeature( int featureId, QgsRectangle rectFrom=QgsRectangle(), QgsRectangle rectTo=QgsRectangle() ); + + /** + * Fetch features from specified rectangle from provider, if it is cached. This + * must be called every time the provider data change outside vector layer control. + * @param rect rectangle containing changed features. All features within + * the rectangle will be reloaded. + **/ + void recacheFeatures( QgsRectangle rect ); + signals: /** This signal is emited when selection was changed */ @@ -589,8 +609,10 @@ /** Goes through all features and finds a free id (e.g. to give it temporarily to a not-commited feature) */ int findFreeId(); +#ifdef ENABLE_GEOMETRYCACHE /**Deletes the geometries in mCachedGeometries*/ void deleteCachedGeometries(); +#endif /** Draws a vertex symbol at (screen) coordinates x, y. (Useful to assist vertex editing.) */ void drawVertexMarker( int x, int y, QPainter& p, QgsVectorLayer::VertexMarkerType type ); @@ -656,12 +678,17 @@ /** Flag indicating whether the layer has been modified since the last commit */ bool mModified; +#ifdef ENABLE_GEOMETRYCACHE /** cache of the committed geometries retrieved *for the current display* */ QgsGeometryMap mCachedGeometries; /** extent for which there are cached geometries */ QgsRectangle mCachedGeometriesRect; +#endif + /** Cache of features (used mainly for displaying of features) */ + QgsFeatureCache *mFeatureCache; + /** Set holding the feature IDs that are activated. Note that if a feature subsequently gets deleted (i.e. by its addition to mDeletedFeatureIds), it always needs to be removed from mSelectedFeatureIds as well. Index: src/core/spatialindex/tools/ExternalSort.h =================================================================== --- src/core/spatialindex/tools/ExternalSort.h (revision 11113) +++ src/core/spatialindex/tools/ExternalSort.h (working copy) @@ -73,10 +73,14 @@ void initializeRuns( std::deque >& runs ); void mergeRuns(); +#ifndef EXTERNALSORT_SORTUSINGPRIORITYQUEUE + std::vector m_buffer; +#else std::priority_queue < PQEntry*, std::vector, PQEntry::ascendingComparator > m_buffer; +#endif unsigned long m_cMaxBufferSize; bool m_bFitsInBuffer; Index: src/core/spatialindex/tools/ExternalSort.cc =================================================================== --- src/core/spatialindex/tools/ExternalSort.cc (revision 11113) +++ src/core/spatialindex/tools/ExternalSort.cc (working copy) @@ -27,6 +27,8 @@ #include "ExternalSort.h" #include "qgslogger.h" +#include + #ifdef _MSC_VER #define UNUSED(symbol) symbol #else @@ -153,7 +155,11 @@ m_pTemplateRecord = o->clone(); SmartPointer tf; +#ifdef EXTERNALSORT_SORTUSINGPRIORITYQUEUE m_buffer.push( new PQEntry( pS, m_pComparator, tf ) ); +#else + m_buffer.push_back( new PQEntry( pS, m_pComparator, tf ) ); +#endif } if ( bEOF && runs.size() == 0 ) @@ -161,10 +167,18 @@ if ( ! m_buffer.empty() ) { +#ifndef EXTERNALSORT_SORTUSINGPRIORITYQUEUE + std::sort(m_buffer.begin(), m_buffer.end()); +#endif + TemporaryFile* tf = new TemporaryFile(); while ( ! m_buffer.empty() ) { +#ifdef EXTERNALSORT_SORTUSINGPRIORITYQUEUE PQEntry* pqe = m_buffer.top(); m_buffer.pop(); +#else + PQEntry* pqe = m_buffer.back(); m_buffer.pop_back(); +#endif tf->storeNextObject( pqe->m_pRecord ); delete pqe; } Index: src/core/spatialindex/qgsspatialindex.cpp =================================================================== --- src/core/spatialindex/qgsspatialindex.cpp (revision 11113) +++ src/core/spatialindex/qgsspatialindex.cpp (working copy) @@ -22,11 +22,113 @@ #include "qgslogger.h" #include "SpatialIndex.h" +#include "spatialindex/SpatialIndexImpl.h" +#include "rtree/Node.h" +#include "rtree/Leaf.h" +#include "rtree/Index.h" +#include "rtree/BulkLoader.h" + +#include "rtree/RTree.h" + +#include + using namespace SpatialIndex; +/** Convert rectangle to a region suitable for use in the spatial index library */ +inline Tools::Geometry::Region rectToRegion( QgsRectangle rect ) +{ + double pt1[2], pt2[2]; + pt1[0] = rect.xMinimum(); + pt1[1] = rect.yMinimum(); + pt2[0] = rect.xMaximum(); + pt2[1] = rect.yMaximum(); + return Tools::Geometry::Region( pt1, pt2, 2 ); +} -// custom visitor that adds found features to list +inline QgsRectangle regionToRect( Tools::Geometry::Region rect ) +{ + return QgsRectangle( rect.getLow(0), rect.getLow(1), rect.getHigh(0), rect.getHigh(1) ); +} + + +/** returns, whether the rectBig contains rectSmall (with specified overflow exceptions) */ +inline bool RectContainsWithOverflow( QgsRectangle rectBig, QgsRectangle rectSmall, + bool left, bool right, + bool over, bool under ) +{ + bool rv=true; + + rv &= left || !left && (rectBig.xMinimum() < rectSmall.xMinimum()); + rv &= right || !right && (rectBig.xMaximum() > rectSmall.xMaximum()); + rv &= over || !over && (rectBig.yMinimum() < rectSmall.yMinimum()); + rv &= under || !under && (rectBig.yMaximum() > rectSmall.yMaximum()); + + return rv; +} + + + +/** + * Creates a handle (pointer to pointer...) to specified feature. This + * is needed for the RTree, as we can't directly change data node's data + * (without removing and reinserting the node) and also because the data provided + * to RTree is being memcopied (so we can't place a object there - we wouldn't be + * able to delete it nicely later). + * If the target is nonzero, it's an array where address of the + * pointer to feature will be stored. Otherwise the array will be allocated + * by the function. + * @param f Feature to be handled + * @param target if nonzero, it's considered to be a array of size sizeof(byte*), + * The return value will be stored in this array. + * @returns Handle to the feature (newly allocated or ==target, if target!=0). + * The return value is an array containing the handle's value. Crazy stuff. +**/ +inline byte* QgsFeaturePtr2ByteArray(QgsFeature* f, byte *target=0) +{ + //alloc a doublepointer to QgsFeature + //Data (rv) -> fPtrPtr -> QgsFeature (f) + //need to do this, because we can't change contents of Data, once inserted... + + byte* rv; + + if (target==0) + rv=new byte[sizeof(byte*)]; + else + rv=target; + + //create link: fPtrPtr -> f + QgsFeature **fPtrPtr=new QgsFeature *; + *fPtrPtr=f; + + //copy fPtrPtr address to rv + memcpy(rv, &fPtrPtr, sizeof(QgsFeature**)); + + return rv; +} + + + +/** + * Inverse operation to QgsFeaturePtr2ByteArray. Also deallocates the specified + * pointer's array. Returns pointer to pointer to the feature referenced by handle. +**/ +inline QgsFeature** ByteArray2QgsFeaturePtr(byte *p) +{ + QgsFeature** rv; + + //retrieve pointer to fPtrPtr + memcpy(&rv, p, sizeof(QgsFeature**)); + + //release received data copy + delete []p; + + return rv; +} + + + +/** Visitor that adds found features to list */ class QgisVisitor : public SpatialIndex::IVisitor { public: @@ -47,17 +149,193 @@ }; -QgsSpatialIndex::QgsSpatialIndex() + +/** Visitor that's freeing QgsFeature** objects stored in the Rtree. */ +class QgisFreeMemVisitor : public SpatialIndex::IVisitor { + public: + QgisFreeMemVisitor( bool dontDeletePointer=false ): + mOnlyFreeSpecifiedId(false), mDontDeletePointer(dontDeletePointer) {} + + QgisFreeMemVisitor( long onlyFreeId, bool dontDeletePointer=false ): + mOnlyFreeId(onlyFreeId), mOnlyFreeSpecifiedId(true), + mDontDeletePointer(dontDeletePointer) {} + + void visitNode( const INode& n ) {} + + void visitData( const IData& d ) + { + if ( mOnlyFreeSpecifiedId && ( d.getIdentifier() != mOnlyFreeId ) ) + return; + + long unsigned len; + byte *data; + d.getData(len, &data); + + QgsFeature **ptr=ByteArray2QgsFeaturePtr(data); + + if ( !ptr ) + return; + + if (*ptr!=0) + { + delete *ptr; + *ptr=0; + } + + if ( !mDontDeletePointer ) + delete ptr; + } + + void visitData( std::vector& v ) {} + + private: + bool mDontDeletePointer; + bool mOnlyFreeSpecifiedId; + long mOnlyFreeId; +}; + + + +/** Visitor storing found features to a QLinkedList. */ +class QgisFeatureVisitor : public SpatialIndex::IVisitor +{ + public: + /** + * If unlinkFeatureFromData==true, then the features are also detached from + * the rtree (used e.g. when some features are extracted from tree and + * the tree is deleted afterwards (which erases all contained features)) + */ + QgisFeatureVisitor( QLinkedList & list, bool unlinkFeatureFromData = false ) + : mList( list ), mUnlinkFeatureFromData( unlinkFeatureFromData ) {} + + void visitNode( const INode& n ) {} + + void visitData( const IData& d ) + { + long unsigned len; + byte *data; + d.getData(len, &data); + + QgsFeature **f=ByteArray2QgsFeaturePtr(data); + + mList.push_back( *f ); + + if ( mUnlinkFeatureFromData ) + *f=0; + } + + void visitData( std::vector& v ) {} + + private: + QLinkedList& mList; + + bool mUnlinkFeatureFromData; +}; + + + +/** + * A trivial data stream providing RTree::Data objects from list to ExternalSort. +**/ +class QgisFeatureLinkedListDataStream : public IDataStream +{ +public: + QgisFeatureLinkedListDataStream(QLinkedList &list) : mList(list) + { rewind(); } + + virtual ~QgisFeatureLinkedListDataStream() { } + + virtual IData* getNext() + { + if (!hasNext()) + return 0; + + Region r=rectToRegion((*mListIt)->geometry()->boundingBox()); + + //We basically create a pointer to feature in list and hand pointer + //to that pointer (=handle) to the caller (most probably it'll be + //the Tools::ExternalSort::initializeRuns ...) + + byte ptr[sizeof(byte*)]; + QgsFeaturePtr2ByteArray(*mListIt, ptr); + + RTree::Data* ret = new RTree::Data( + sizeof(byte*), ptr, + r, (*mListIt)->id() + ); + + mListIt++; + + return ret; + } + + virtual bool hasNext() throw (Tools::NotSupportedException) + { return mListIt!=mList.end(); } + + virtual long unsigned size() throw (Tools::NotSupportedException) + { throw Tools::NotSupportedException("Operation not supported."); } + + virtual void rewind() throw (Tools::NotSupportedException) + { mListIt=mList.begin(); } + +private: + QLinkedList &mList; + QLinkedList::iterator mListIt; +}; + + +/** A strategy pattern class determining maximal bounding rectangle of a rtree */ +class DetermineIndexedRectStrategy : public IQueryStrategy +{ +public: + QgsRectangle mIndexedRect; + +public: + void getNextEntry(const IEntry& entry, long& nextEntry, bool& hasNext) + { + Region region; + + //The first time we are called, entry points to the root. We don't need more. + hasNext = false; + + IShape* ps; + entry.getShape(&ps); + ps->getMBR(region); + delete ps; + + mIndexedRect=regionToRect(region); + } +}; + + +QgsSpatialIndex::QgsSpatialIndex(bool copyFeatures) +{ + initialize(QLinkedList(), copyFeatures); +} + + + +QgsSpatialIndex::QgsSpatialIndex(QLinkedList features, + bool copyFeatures ) +{ + initialize(features, copyFeatures); +} + + + +void QgsSpatialIndex::initialize(QLinkedList features, bool copyFeatures) +{ + mCopyFeatures=copyFeatures; + // for now only memory manager mStorageManager = StorageManager::createNewMemoryStorageManager(); // create buffer - unsigned int capacity = 10; bool writeThrough = false; mStorage = StorageManager::createNewRandomEvictionsBuffer( *mStorageManager, capacity, writeThrough ); - + // R-Tree parameters double fillFactor = 0.7; unsigned long indexCapacity = 10; @@ -67,27 +345,66 @@ // create R-tree long indexId; + mRTree = RTree::createNewRTree( *mStorage, fillFactor, indexCapacity, leafCapacity, dimension, variant, indexId ); + + if (!features.empty()) { + //Bulk load provided features into the empty tree + QgisFeatureLinkedListDataStream stream(features); + + unsigned long bindex = static_cast( std::floor( static_cast( indexCapacity * fillFactor ) ) ); + unsigned long bleaf = static_cast( std::floor( static_cast( leafCapacity * fillFactor ) ) ); + + SpatialIndex::RTree::BulkLoader bl; + + bl.bulkLoadUsingSTR( static_cast( mRTree ), stream, bindex, bleaf, 50000000 ); + + //all features' pointers were added to index, make sure noone else will + //use them,.. + features.clear(); + } } -QgsSpatialIndex:: ~QgsSpatialIndex() + + +QgsSpatialIndex::~QgsSpatialIndex() { + uninitialize(); +} + + + +void QgsSpatialIndex::uninitialize() +{ + QgisFreeMemVisitor freeingVisitor; + + //free all feature pointers and features in the tree + //TODO: what is maximal range? or how to select all features in rtree? + // + DetermineIndexedRectStrategy getRectStrategy; + mRTree->queryStrategy(getRectStrategy); + + mRTree->intersectsWithQuery( + rectToRegion( getRectStrategy.mIndexedRect ), + freeingVisitor + ); + + //delete the rest of index delete mRTree; delete mStorage; delete mStorageManager; } + + Tools::Geometry::Region QgsSpatialIndex::rectToRegion( QgsRectangle rect ) { - double pt1[2], pt2[2]; - pt1[0] = rect.xMinimum(); - pt1[1] = rect.yMinimum(); - pt2[0] = rect.xMaximum(); - pt2[1] = rect.yMaximum(); - return Tools::Geometry::Region( pt1, pt2, 2 ); + return ::rectToRegion( rect ); } + + bool QgsSpatialIndex::featureInfo( QgsFeature& f, Tools::Geometry::Region& r, long& id ) { QgsGeometry *g = f.geometry(); @@ -99,17 +416,41 @@ return true; } + + bool QgsSpatialIndex::insertFeature( QgsFeature& f ) { + if (!mCopyFeatures) + { + return insertFeature(&f); + } + else + { + QgsFeature *fP=new QgsFeature(f); + return insertFeature(fP); + } +} + + + +bool QgsSpatialIndex::insertFeature( QgsFeature* f ) +{ Tools::Geometry::Region r; long id; - if ( !featureInfo( f, r, id ) ) + if ( !featureInfo( *f, r, id ) ) return false; // TODO: handle possible exceptions correctly try { - mRTree->insertData( 0, 0, r, id ); + if (!mCopyFeatures) + { + mRTree->insertData( 0, 0, r, id ); + } + else + { + mRTree->insertData( sizeof(QgsFeature*), QgsFeaturePtr2ByteArray(f), r, id ); + } } catch ( Tools::Exception &e ) { @@ -129,6 +470,8 @@ return true; } + + bool QgsSpatialIndex::deleteFeature( QgsFeature& f ) { Tools::Geometry::Region r; @@ -136,22 +479,154 @@ if ( !featureInfo( f, r, id ) ) return false; - // TODO: handle exceptions + return deleteFeature( id, f.geometry()->boundingBox() ); +} + + + +bool QgsSpatialIndex::deleteFeature( long id, QgsRectangle rect) +{ + QgsFeature *f=getFeatureWithId(rect, id); + + if (!f) return false; + + QgsRectangle fBoundRect=f->geometry()->boundingBox(); + + Tools::Geometry::Region r = rectToRegion( fBoundRect ); + + if (mCopyFeatures) + { + QgisFreeMemVisitor freeIdVisitor(id); + + mRTree->intersectsWithQuery( + r, + freeIdVisitor + ); + } + return mRTree->deleteData( r, id ); } + + +void QgsSpatialIndex::deleteFeatures( QgsRectangle rect ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList features=getIntersectingFeatures(rect); + QLinkedList::iterator fIt; + + QLinkedList featIds; + QLinkedList::iterator idsIt; + + QLinkedList featRects; + QLinkedList::iterator rectIt; + + for (fIt=features.begin(); fIt!=features.end(); fIt++) + { + featIds.push_back((*fIt)->id()); + featRects.push_back((*fIt)->geometry()->boundingBox()); + } + + Tools::Geometry::Region r = rectToRegion( rect ); + + if (mCopyFeatures) + { + QgisFreeMemVisitor freeingVisitor; + + mRTree->intersectsWithQuery( + r, + freeingVisitor + ); + } + + for (idsIt=featIds.begin(), rectIt=featRects.begin(); idsIt!=featIds.end(); idsIt++, rectIt++) + { + Tools::Geometry::Region r2 = rectToRegion( *rectIt ); + mRTree->deleteData( r2, *idsIt ); + } +} + + + +void QgsSpatialIndex::filterFeaturesToRect( QgsRectangle rect ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList feats=getIntersectingFeatures(rect, true); + + uninitialize(); + initialize(feats, mCopyFeatures); +} + + + QList QgsSpatialIndex::intersects( QgsRectangle rect ) { QList list; - QgisVisitor visitor( list ); + getFeaturesInRect( rect, true, list ); + + return list; +} - Tools::Geometry::Region r = rectToRegion( rect ); - mRTree->intersectsWithQuery( r, visitor ); +QList QgsSpatialIndex::contains( QgsRectangle rect ) +{ + QList list; + getFeaturesInRect( rect, false, list ); + return list; } + + +void QgsSpatialIndex::getFeaturesInRect( QgsRectangle rect, bool intersectIsEnough, QList &features ) +{ + QgisVisitor visitor( features ); + + if ( rect == QgsRectangle() ) + { + DetermineIndexedRectStrategy getRectStrategy; + mRTree->queryStrategy(getRectStrategy); + + rect=getRectStrategy.mIndexedRect; + } + + Tools::Geometry::Region r = rectToRegion( rect ); + + if (intersectIsEnough) + mRTree->intersectsWithQuery( r, visitor ); + else + mRTree->containsWhatQuery( r, visitor ); +} + + + +void QgsSpatialIndex::getFeaturesInRect( QgsRectangle rect, bool intersectIsEnough, QLinkedList &features, bool unlinkFromIndex ) +{ + QgisFeatureVisitor visitor( features, unlinkFromIndex ); + + if ( rect == QgsRectangle() ) + { + DetermineIndexedRectStrategy getRectStrategy; + mRTree->queryStrategy(getRectStrategy); + + rect=getRectStrategy.mIndexedRect; + } + + Tools::Geometry::Region r = rectToRegion( rect ); + + if (intersectIsEnough) + mRTree->intersectsWithQuery( r, visitor ); + else + mRTree->containsWhatQuery( r, visitor ); +} + + + QList QgsSpatialIndex::nearestNeighbor( QgsPoint point, int neighbors ) { QList list; @@ -166,3 +641,71 @@ return list; } + + + +QLinkedList QgsSpatialIndex::getIntersectingFeatures( QgsRectangle rect, bool unlinkFromIndex ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList list; + getFeaturesInRect( rect, true, list, unlinkFromIndex ); + + return list; +} + + + +QLinkedList QgsSpatialIndex::getContainedFeatures( QgsRectangle rect, bool unlinkFromIndex ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList list; + getFeaturesInRect( rect, false, list, unlinkFromIndex ); + + return list; +} + + + +QLinkedList QgsSpatialIndex::getNearestNeighborFeatures( QgsPoint point, int neighbors ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList list; + + QgisFeatureVisitor visitor( list ); + + double pt[2]; + pt[0] = point.x(); + pt[1] = point.y(); + Tools::Geometry::Point p( pt, 2 ); + + mRTree->nearestNeighborQuery( neighbors, p, visitor ); + + return list; +} + + + +QgsFeature* QgsSpatialIndex::getFeatureWithId( QgsRectangle rect, long id ) +{ + if (!mCopyFeatures) + throw std::invalid_argument("!mCopyFeatures"); //TODO: more meaningful exception... + + QLinkedList fIds=getIntersectingFeatures(rect); + + QLinkedList::iterator i=fIds.begin(); + + while (i!=fIds.end()) { + if ((*i)->id()==id) + return *i; + + i++; + } + + return 0; +} Index: src/core/spatialindex/qgsspatialindex.h =================================================================== --- src/core/spatialindex/qgsspatialindex.h (revision 11113) +++ src/core/spatialindex/qgsspatialindex.h (working copy) @@ -41,10 +41,13 @@ class QgsRectangle; class QgsPoint; #include +#include +/** + * A quite trivial wrapper around SpatialIndex library, using qgis features. +**/ class CORE_EXPORT QgsSpatialIndex { - public: /* creation of spatial index */ @@ -55,40 +58,102 @@ /** create new spatial index that stores its data on disk */ //static QgsSpatialIndex* createDiskIndex(QString fileName); - /** constructor - creates R-tree */ - QgsSpatialIndex(); - + /** + * constructor - creates spatial index + * @param copyFeatures - if true, spatial index will keep + * copies of inserted features. + */ + QgsSpatialIndex(bool copyFeatures=false); + + /** + * constructor - creates spatial index + * @param features - initial contents of spatial index + * @param copyFeatures - if true, spatial index will keep + * copies of inserted features. + */ + QgsSpatialIndex(QLinkedList features, bool copyFeatures=false); + /** destructor finalizes work with spatial index */ ~QgsSpatialIndex(); /* operations */ - /** add feature to index */ + /** + * Add feature to index. + * More precisely - if class was created with copyFeatures, then + * a copy of specified feature is added to index. + */ bool insertFeature( QgsFeature& f ); - + + /** + * Add feature to index (inserts the specified feature w/o copying). + */ + bool insertFeature( QgsFeature* f ); + /** remove feature from index */ bool deleteFeature( QgsFeature& f ); + + /** remove feature from index */ + bool deleteFeature( long id, QgsRectangle rect ); + + /** remove features from index */ + void deleteFeatures( QgsRectangle rect ); - + /** + * Filter the index so that it only contains features from + * specified rectangle. + * Throws exception if index was created with copyFeatures==false. + */ + void filterFeaturesToRect( QgsRectangle rect ); + /* queries */ /** returns features that intersect the specified rectangle */ QList intersects( QgsRectangle rect ); + + /** returns features that reside completely in the specified rectangle */ + QList contains( QgsRectangle rect ); /** returns nearest neighbors (their count is specified by second parameter) */ QList nearestNeighbor( QgsPoint point, int neighbors ); + /** returns features that intersect the specified rectangle (Throws exception it index was created with copyFeatures==false) */ + QLinkedList getIntersectingFeatures( QgsRectangle rect, bool unlinkFromIndex=false ); + + /** returns features that reside completely in the specified rectangle (Throws exception if index was created with copyFeatures==false) */ + QLinkedList getContainedFeatures( QgsRectangle rect, bool unlinkFromIndex=false ); + + /** returns nearest neighbors (their count is specified by second parameter) (Throws exception if index was created with copyFeatures==false) */ + QLinkedList getNearestNeighborFeatures( QgsPoint point, int neighbors ); + + /** + * Returns feature from specified rectangle with specified id, + * or NULL if it doesn't exist. + * Throws exception if index was created with copyFeatures==false. + */ + QgsFeature* getFeatureWithId( QgsRectangle rect, long id ); + protected: - Tools::Geometry::Region rectToRegion( QgsRectangle rect ); bool featureInfo( QgsFeature& f, Tools::Geometry::Region& r, long& id ); - private: + /** initialize basic stuff */ + void initialize(QLinkedList features, bool copyFeatures); + + /** free all occupied memory */ + void uninitialize(); + + /** returns features (in features parameter) contained or intersecting with the specified rect */ + void getFeaturesInRect( QgsRectangle rect, bool intersectIsEnough, QList &features ); + + /** returns features (in features parameter) contained or intersecting with the specified rect */ + void getFeaturesInRect( QgsRectangle rect, bool intersectIsEnough, QLinkedList &features, bool unlinkFromIndex ); + /** storage manager */ SpatialIndex::IStorageManager* mStorageManager; @@ -98,6 +163,7 @@ /** R-tree containing spatial index */ SpatialIndex::ISpatialIndex* mRTree; + bool mCopyFeatures; }; #endif Index: src/core/CMakeLists.txt =================================================================== --- src/core/CMakeLists.txt (revision 11113) +++ src/core/CMakeLists.txt (working copy) @@ -51,6 +51,7 @@ qgsvectordataprovider.cpp qgsvectorfilewriter.cpp qgsvectorlayer.cpp + qgsfeaturecache.cpp qgsvectorlayerundocommand.cpp qgsvectoroverlay.cpp @@ -194,6 +195,7 @@ raster renderer symbology + spatialindex spatialindex/include ${PROJ_INCLUDE_DIR} ${GEOS_INCLUDE_DIR} Index: src/core/qgsfeaturecache.cpp =================================================================== --- src/core/qgsfeaturecache.cpp (revision 0) +++ src/core/qgsfeaturecache.cpp (revision 0) @@ -0,0 +1,303 @@ +/** + * \file + * Implementation and usage of QgsFeatureCache class + * + * QgsFeatureCache (QFC for short) uses a RTree to store all the inserted + * features. QFC may be used for several usages - inside qgis it is used to + * speed up rendering and editing of vector layers. Because retrieving features + * from provider at each redraw could be quite time-consuming (esp. in case + * the provider's database is stored on a remote server - like postgis database), + * feature once loaded from provider are generally considered non-changing until + * the next commit. If this wasn't true, cache would contain different data + * than the provider and thus user wouldn't see the real data - in case data + * could change outside qgis it's required to either disable cache or to + * implement a mechanism to let cache know, what changed since the last + * cache fill. The functionality is available in QgsVectorLayer and won't + * be discussed here any more. + * + * General workflow on how to use feature cache is to select data from provider + * and insert them into the cache. After that, data may be selected and fetched + * from QFC the same way they are fetched from provider. + * + * The most important question is - when to fill the cache? Obviously, one could + * copy complete contents of vector layer into cache - but that would needlessly + * occupy memory. Therefore, it's best to only fill it with data used the most + * during workflow. In case of GIS editing, vector data are used for drawing + * maps to the user in most of the cases. Therefore, in qgis, the cache is used + * to cache features (geometries+attributes) needed to draw the required extent. + * This also ensures that there will be enough data to perform simple editing + * operations like moving vertices etc. (which mostly only use geometries within + * the currently drawn extent). + * + * Another question is when to empty the cache. QFC implements a few cache + * emptying strategies (set by setCacheMode() ) and the set strategy is applied + * after calling applyCurrentCacheMode() method. There are two trivial modes, + * one keeping everything and one keeping the cache empty at all times (i.e. + * the cache is not used in this case). + * + * Last currently implemented mode/strategy is called "heuristics". This strategy + * compares the currently cached region with the requested one - and in case its + * much bigger, the cached area is downsized to free it's (probably) unused parts. + * This heuristics is based on the idea that user mostly starts editing with a big + * map and he zooms-in afterwards, editing only a small part of the whole map. + * This means he mostly doesn't need all the data in memory... + * +**/ + + +#include "qgsfeaturecache.h" +#include "qgsgeometry.h" +#include +#include + + + +QgsFeatureCache::QgsFeatureCache() +{ + mIsInitialized=false; + mSelectValid=false; + + mCacheMode=CACHE_ALL; + + mCachedFeatures=new QgsSpatialIndex(true); +} + + + +QgsFeatureCache::~QgsFeatureCache() +{ + delete mCachedFeatures; +} + + + +void QgsFeatureCache::setAsInitialized() +{ + if (mCacheMode==CACHE_NOTHING) + return; + + mCachedRectangle=mSelectedRectangle; + mCachedAttributes=mSelectedAttributes; + mCachedIncludingGeometries=mSelectedFetchGeometries; + + //insert inserted features to the index for real now.... :-) + delete mCachedFeatures; + mCachedFeatures=new QgsSpatialIndex(mCachedFeaturesList, true); + mCachedFeaturesList.clear(); + + mIsInitialized=true; +} + + + +/** Are all attributes of a in b? */ +bool operator <=(QgsAttributeList const &a, QgsAttributeList const &b) +{ + QgsAttributeList::const_iterator aIt; + for (aIt=a.begin(); aIt!=a.end(); aIt++) { + if (!b.contains(*aIt)) + return false; + } + + return true; +} + + + +std::ostream& operator<< (std::ostream& stream, QgsAttributeList const &a) +{ + QgsAttributeList::const_iterator aIt; + stream << "["; + for (aIt=a.begin(); aIt!=a.end(); aIt++) { + stream << *aIt << ", "; + } + stream << "]"; + return stream; +} + + + +float volume(QgsRectangle const &r) +{ + return sqrt(r.width()*r.width()+r.height()*r.height()); +} + + + +bool QgsFeatureCache::select( + QgsAttributeList fetchAttributes, + QgsRectangle rect, + bool fetchGeometry, + bool useIntersect +) { + mSelectValid=true; + + if (mIsInitialized) { + if ( ( (rect != QgsRectangle()) && !mCachedRectangle.contains(rect) ) || + !(fetchAttributes<=mCachedAttributes) ) { + //the current cache isn't valid for the select + mSelectValid=false; + } + } + + mSelectedAttributes=fetchAttributes; + mSelectedRectangle=rect; + mSelectedFetchGeometries=fetchGeometry; + mSelectedUseIntersect=useIntersect; + + mSelectedFeaturesList=mCachedFeatures->getIntersectingFeatures(rect); + mSelectedFeaturesIterator=mSelectedFeaturesList.begin(); + + mSelectValid = mSelectValid && mIsInitialized; + + return mSelectValid; +} + +void QgsFeatureCache::makeSelectValid() +{ + if ( mSelectedAttributes <= mCachedAttributes ) + { + //std::cerr << "Make valid"<filterFeaturesToRect(r); + + mCachedRectangle=r; +} + + + +bool QgsFeatureCache::nextFeature( QgsFeature& feature ) +{ + if (!mIsInitialized || !mSelectValid) + return false; + + while (mSelectedFeaturesIterator!=mSelectedFeaturesList.end()) { + if (!mSelectedUseIntersect || (mSelectedUseIntersect && (*mSelectedFeaturesIterator)->geometry()->intersects(mSelectedRectangle))) { + feature=**mSelectedFeaturesIterator; + mSelectedFeaturesIterator++; + return true; + } + mSelectedFeaturesIterator++; + } + + mSelectValid=false; + + return false; +} + + + +bool QgsFeatureCache::featureAtId( int featureId, QgsFeature &f, bool fetchGeometries, bool fetchAttributes) +{ + if (!mIsInitialized || !mSelectValid) + return false; + + QgsFeature* rv=mCachedFeatures->getFeatureWithId(mCachedRectangle, featureId); + + if (rv==0) + return false; + + f=*rv; + + return true; +} + + + +bool QgsFeatureCache::insert(QgsFeature &f, bool deferredInsert) +{ + if (mCacheMode==CACHE_NOTHING) + return true; + + try { + if (deferredInsert) + mCachedFeaturesList.push_back(new QgsFeature(f)); + else + mCachedFeatures->insertFeature(f); + } catch (...) { + mIsInitialized=false; + return false; + } + + return true; +} + + + +bool QgsFeatureCache::remove( int featureId, QgsRectangle rect ) +{ + if ( rect==QgsRectangle() ) + rect=getCachedRectangle(); + + return mCachedFeatures->deleteFeature( featureId, rect); +} + + + +bool QgsFeatureCache::remove( QgsRectangle rect ) +{ + mCachedFeatures->deleteFeatures( rect ); + + return true; +} + + + +void QgsFeatureCache::setCacheMode(CacheMode mode) +{ + mCacheMode=mode; +} + + + +void QgsFeatureCache::applyCurrentCacheMode(QgsRectangle const &rect) +{ + if (mCacheMode==CACHE_ALL) + { + //keep everything in cache.. + return; + } + else if (mCacheMode==CACHE_HEURISTICS) + { + if (volume(mCachedRectangle)>8*volume(rect)) { + //the user wants "significantly less" than we have cached + QgsRectangle r=rect; + r.scale(2); + filterTo(mCachedRectangle.intersect(&r)); + } + } + else if (mCacheMode==CACHE_NOTHING) + { + clear(); + } +} + +bool QgsFeatureCache::areAttributesCached(QgsAttributeList list) +{ + return list<=mCachedAttributes; +} Index: src/ui/qgsoptionsbase.ui =================================================================== --- src/ui/qgsoptionsbase.ui (revision 11113) +++ src/ui/qgsoptionsbase.ui (working copy) @@ -1,7 +1,8 @@ - + + QgsOptionsBase - - + + 0 0 @@ -9,60 +10,60 @@ 517 - + QGIS Options - + - + true - + true - - - - + + + + Qt::Horizontal - + QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - 0 + + + + 1 - - + + &General - - - - + + + + Project files - - + + 11 - - + + Prompt to save project changes when required - - + + Warn when opening a project file saved with an older version of QGIS @@ -70,28 +71,28 @@ - - - + + + Default Map Appearance (overridden by project properties) - - - - + + + + Selection color - + pbnMeasureColour - + - + Qt::Horizontal - + 40 20 @@ -99,35 +100,35 @@ - - - + + + 100 0 - + - - - + + + Background color - + pbnCanvasColor - + - + Qt::Horizontal - + 40 20 @@ -135,15 +136,15 @@ - - - + + + 100 0 - + @@ -151,74 +152,74 @@ - - - + + + &Application - - - - - + + + + + 0 0 - + Icon theme - + cmbTheme - - - + + + false - + - - - - <b>Note: </b>Theme changes take effect the next time QGIS is started + + + + <b>Note: </b>Theme changes take effect the next time QGIS is started - + Qt::AlignVCenter - - - + + + Capitalise layer names in legend - - - + + + Display classification attribute names in legend - - - + + + Hide splash screen at startup - - - + + + Open attribute table in a dock window @@ -226,15 +227,15 @@ - + - + Qt::Vertical - + QSizePolicy::Minimum - + 577 21 @@ -244,79 +245,79 @@ - - + + &Rendering - - - - + + + + Rendering behavior - - - - + + + + By default new la&yers added to the map should be displayed - - - + + + Number of features to draw before updating the display - + spinBoxUpdateThreshold - - - + + + Map display will be updated (drawn) after this many features have been read from the data source - + 1000000 - + 1000 - - - - <b>Note:</b> Use zero to prevent display updates until all features have been rendered + + + + <b>Note:</b> Use zero to prevent display updates until all features have been rendered - - - + + + Rendering quality - - + + 11 - - + + Make lines appear less jagged at the expense of some drawing performance - - + + Selecting this will unselect the 'make lines less' jagged toggle - + Fix problems with incorrectly filled polygons @@ -324,12 +325,12 @@ - + - + Qt::Vertical - + 20 40 @@ -337,72 +338,108 @@ + + + + Features cache + + + + + + Cache nothing (not recommended) + + + + + + + Heuristics (recommended) + + + true + + + + + + + Cache all layers' features + + + false + + + + + + - - + + &Map tools - - + + 11 - - - + + + Panning and zooming - - + + 11 - - + + - + Zoom - + Zoom and recenter - + Zoom to mouse cursor - + Nothing - - - + + + Zoom factor - - - + + + Mouse wheel action - - - + + + 1 - + 1.100000000000000 - + 2.000000000000000 @@ -410,21 +447,21 @@ - - - + + + Measure tool - - + + 11 - + - + Qt::Horizontal - + 191 20 @@ -432,38 +469,38 @@ - - - + + + 100 0 - + - - + + - - - + + + Rubberband color - + cmbEllipsoid - - - + + + Ellipsoid for distance calculations - + cmbEllipsoid @@ -471,47 +508,47 @@ - - - + + + Search radius - - + + 11 - - - - <b>Note:</b> Specify the search radius as a percentage of the map width + + + + <b>Note:</b> Specify the search radius as a percentage of the map width - + true - - - + + + Search radius for identifying features and displaying map tips - + spinBoxIdentifyValue - - - + + + % - + 100.000000000000000 - + 0.010000000000000 - + 5.000000000000000 @@ -519,12 +556,12 @@ - + - + Qt::Vertical - + 20 40 @@ -534,12 +571,12 @@ - - + + Overlay - - + + 10 10 @@ -547,11 +584,11 @@ 111 - + Position - - + + 20 40 @@ -559,23 +596,23 @@ 42 - + - - + + Placement algorithm: - + - + Qt::Horizontal - + 221 20 @@ -587,56 +624,56 @@ - - + + Digitizing - - - - + + + + Rubberband - - - - + + + + Line width - + mLineWidthSpinBox - - - + + + Line width in pixels - + 1 - - - + + + Line colour - + mLineColourToolButton - - - + + + 100 0 - + @@ -644,25 +681,25 @@ - - - + + + Snapping - - - - + + + + Default snap mode - + - + Qt::Horizontal - + 311 20 @@ -670,29 +707,29 @@ - - - - + + + + 0 0 - - - + + + Default snapping tolerance in layer units - + - + Qt::Horizontal - + 241 20 @@ -700,29 +737,29 @@ - - - + + + 5 - + 99999999.989999994635582 - - - + + + Search radius for vertex edits in layer units - + - + Qt::Horizontal - + 61 20 @@ -730,45 +767,45 @@ - - - + + + 5 - + 99999999.989999994635582 - - - - + + + + 0 0 - + map units - + pixels - - + + - + map units - + pixels @@ -777,32 +814,32 @@ - - - + + + Vertex markers - - - - + + + + Show markers only for selected features - - - + + + Marker style - + - + Qt::Horizontal - + 281 20 @@ -810,10 +847,10 @@ - - - - + + + + 0 0 @@ -823,18 +860,18 @@ - - - + + + Enter attribute values - + - - + + Suppress attributes pop-up windows after each created feature - + false @@ -842,12 +879,12 @@ - + - + Qt::Vertical - + 547 71 @@ -857,20 +894,20 @@ - - + + CRS - - + + 11 - + - + Qt::Vertical - + 51 31 @@ -878,42 +915,42 @@ - - - + + + Select Global Default ... - - + + - - - + + + When layer is loaded that has no coordinate reference system (CRS) - - + + 11 - - + + Prompt for CRS - - + + Project wide default CRS will be used - - + + Global default CRS displa&yed below will be used @@ -923,39 +960,39 @@ - - + + Locale - - - - + + + + Override system locale - + true - - - - + + + + Locale to use instead - + cboLocale - - + + - - - - <b>Note:</b> Enabling / changing overide on local requires an application restart + + + + <b>Note:</b> Enabling / changing overide on local requires an application restart - + true @@ -963,12 +1000,12 @@ - + - + Qt::Vertical - + 501 51 @@ -976,15 +1013,15 @@ - - - + + + Additional Info - - - - + + + + Detected active locale on your system: @@ -994,102 +1031,102 @@ - - + + Proxy - - - - + + + + Use proxy for web access - + false - + true - - - - + + + + Host - + leProxyHost - - + + - - - + + + Port - + leProxyPort - - + + - - - + + + User - + leProxyUser - - - + + + Leave this blank if no proxy username / password are required - - - + + + Password - + leProxyPassword - - - + + + Leave this blank if no proxy username / password are required - + QLineEdit::Password - - - + + + Proxy type - - + + - + - + Qt::Horizontal - + 241 20 @@ -1097,33 +1134,33 @@ - - - + + + Exclude URLs: - - - + + + Add - - - + + + Remove - + - + Qt::Horizontal - + 391 20 @@ -1131,8 +1168,8 @@ - - + + @@ -1143,7 +1180,7 @@ - + QgsColorButton