Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release-3_42] fetch feature with geometry when opening a feature context menu in attribute table (fix #48964) #60825

Merged
merged 2 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions python/PyQt6/core/auto_generated/vector/qgsvectorlayercache.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,18 @@ Returns the set of feature IDs for features which are cached.
.. seealso:: :py:func:`isFidCached`
%End

bool featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtId( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param feature: The result of the operation will be written to this feature
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: ``True`` in case of success
:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature
%End

bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id with all attributes, if the cached feature
already contains all attributes, calling this function has the same effect as calling
Expand All @@ -201,14 +201,33 @@ already contains all attributes, calling this function has the same effect as ca
Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param feature: The result of the operation will be written to this feature
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: ``True`` in case of success
:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature

.. seealso:: :py:func:`featureAtId`

.. versionadded:: 3.32
%End

bool completeFeatureAtId( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id with all attributes and geometry, if the cached feature
already contains all attributes and geometry, calling this function has the same effect as calling
:py:func:`~QgsVectorLayerCache.featureAtId`.

Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature

.. seealso:: :py:func:`featureAtId`

.. versionadded:: 3.44
%End

bool removeCachedFeature( QgsFeatureId fid );
Expand Down
1 change: 1 addition & 0 deletions python/PyQt6/gui/auto_additions/qgsfeaturelistmodel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# The following has been generated automatically from src/gui/attributetable/qgsfeaturelistmodel.h
QgsFeatureListModel.FeatureInfoRole = QgsFeatureListModel.Role.FeatureInfoRole
QgsFeatureListModel.FeatureRole = QgsFeatureListModel.Role.FeatureRole
QgsFeatureListModel.FeatureWithGeometryRole = QgsFeatureListModel.Role.FeatureWithGeometryRole
try:
QgsFeatureListModel.FeatureInfo.__attribute_docs__ = {'isNew': 'True if feature is a newly added feature.', 'isEdited': 'True if feature has been edited.'}
QgsFeatureListModel.FeatureInfo.__group__ = ['attributetable']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class QgsFeatureListModel : QSortFilterProxyModel, QgsFeatureModel
enum Role /BaseType=IntEnum/
{
FeatureInfoRole,
FeatureRole
FeatureRole,
FeatureWithGeometryRole,
};

public:
Expand Down
31 changes: 25 additions & 6 deletions python/core/auto_generated/vector/qgsvectorlayercache.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -181,18 +181,18 @@ Returns the set of feature IDs for features which are cached.
.. seealso:: :py:func:`isFidCached`
%End

bool featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtId( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param feature: The result of the operation will be written to this feature
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: ``True`` in case of success
:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature
%End

bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id with all attributes, if the cached feature
already contains all attributes, calling this function has the same effect as calling
Expand All @@ -201,14 +201,33 @@ already contains all attributes, calling this function has the same effect as ca
Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param feature: The result of the operation will be written to this feature
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: ``True`` in case of success
:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature

.. seealso:: :py:func:`featureAtId`

.. versionadded:: 3.32
%End

bool completeFeatureAtId( QgsFeatureId featureId, QgsFeature &feature /Out/, bool skipCache = false );
%Docstring
Gets the feature at the given feature id with all attributes and geometry, if the cached feature
already contains all attributes and geometry, calling this function has the same effect as calling
:py:func:`~QgsVectorLayerCache.featureAtId`.

Considers the changed, added, deleted and permanent features

:param featureId: The id of the feature to query
:param skipCache: Will query the layer regardless if the feature is in the cache already

:return: - ``True`` in case of success
- feature: The result of the operation will be written to this feature

.. seealso:: :py:func:`featureAtId`

.. versionadded:: 3.44
%End

bool removeCachedFeature( QgsFeatureId fid );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class QgsFeatureListModel : QSortFilterProxyModel, QgsFeatureModel
enum Role
{
FeatureInfoRole,
FeatureRole
FeatureRole,
FeatureWithGeometryRole,
};

public:
Expand Down
31 changes: 31 additions & 0 deletions src/core/vector/qgsvectorlayercache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,33 @@ bool QgsVectorLayerCache::featureAtIdWithAllAttributes( QgsFeatureId featureId,
return featureFound;
}

bool QgsVectorLayerCache::completeFeatureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache )
{
bool featureFound = false;

QgsCachedFeature *cachedFeature = nullptr;

if ( !skipCache )
{
cachedFeature = mCache[ featureId ];
}

if ( cachedFeature && cachedFeature->allAttributesFetched() && cachedFeature->geometryFetched() )
{
feature = QgsFeature( *cachedFeature->feature() );
featureFound = true;
}
else if ( mLayer->getFeatures( QgsFeatureRequest()
.setFilterFid( featureId ) )
.nextFeature( feature ) )
{
cacheFeature( feature, true, true );
featureFound = true;
}

return featureFound;
}

bool QgsVectorLayerCache::removeCachedFeature( QgsFeatureId fid )
{
bool removed = mCache.remove( fid );
Expand Down Expand Up @@ -561,3 +588,7 @@ bool QgsVectorLayerCache::QgsCachedFeature::allAttributesFetched() const
return mAllAttributesFetched;
}

bool QgsVectorLayerCache::QgsCachedFeature::geometryFetched() const
{
return mGeometryFetched;
}
30 changes: 25 additions & 5 deletions src/core/vector/qgsvectorlayercache.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
* \param feat The feature to cache. A copy will be made.
* \param vlCache The cache to inform when the feature has been removed from the cache.
* \param allAttributesFetched TRUE if the feature was fetched with all attributes (and not a subset)
* \param geometryFetched TRUE if the feature was fetched with geometry, \since QGIS 3.44
*/
QgsCachedFeature( const QgsFeature &feat, QgsVectorLayerCache *vlCache, bool allAttributesFetched )
QgsCachedFeature( const QgsFeature &feat, QgsVectorLayerCache *vlCache, bool allAttributesFetched, bool geometryFetched )
: mCache( vlCache )
, mAllAttributesFetched( allAttributesFetched )
, mGeometryFetched( geometryFetched )
{
mFeature = new QgsFeature( feat );
}
Expand All @@ -84,10 +86,13 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject

bool allAttributesFetched() const;

bool geometryFetched() const;

private:
QgsFeature *mFeature = nullptr;
QgsVectorLayerCache *mCache = nullptr;
bool mAllAttributesFetched = true;
bool mGeometryFetched = false;

friend class QgsVectorLayerCache;
Q_DISABLE_COPY( QgsCachedFeature )
Expand Down Expand Up @@ -253,7 +258,7 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
* \param skipCache Will query the layer regardless if the feature is in the cache already
* \returns TRUE in case of success
*/
bool featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtId( QgsFeatureId featureId, QgsFeature &feature SIP_OUT, bool skipCache = false );

/**
* Gets the feature at the given feature id with all attributes, if the cached feature
Expand All @@ -268,7 +273,22 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
* \see featureAtId()
* \since QGIS 3.32
*/
bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature, bool skipCache = false );
bool featureAtIdWithAllAttributes( QgsFeatureId featureId, QgsFeature &feature SIP_OUT, bool skipCache = false );

/**
* Gets the feature at the given feature id with all attributes and geometry, if the cached feature
* already contains all attributes and geometry, calling this function has the same effect as calling
* featureAtId().
*
* Considers the changed, added, deleted and permanent features
* \param featureId The id of the feature to query
* \param feature The result of the operation will be written to this feature
* \param skipCache Will query the layer regardless if the feature is in the cache already
* \returns TRUE in case of success
* \see featureAtId()
* \since QGIS 3.44
*/
bool completeFeatureAtId( QgsFeatureId featureId, QgsFeature &feature SIP_OUT, bool skipCache = false );

/**
* Removes the feature identified by fid from the cache if present.
Expand Down Expand Up @@ -417,9 +437,9 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject

void connectJoinedLayers() const;

inline void cacheFeature( QgsFeature &feat, bool allAttributesFetched )
inline void cacheFeature( QgsFeature &feat, bool allAttributesFetched, bool geometryFetched = false )
{
QgsCachedFeature *cachedFeature = new QgsCachedFeature( feat, this, allAttributesFetched );
QgsCachedFeature *cachedFeature = new QgsCachedFeature( feat, this, allAttributesFetched, geometryFetched || mCacheGeometry );
mCache.insert( feat.id(), cachedFeature );
if ( mCacheUnorderedKeys.find( feat.id() ) == mCacheUnorderedKeys.end() )
{
Expand Down
8 changes: 8 additions & 0 deletions src/gui/attributetable/qgsfeaturelistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ QVariant QgsFeatureListModel::data( const QModelIndex &index, int role ) const

return QVariant::fromValue( feat );
}
else if ( role == FeatureWithGeometryRole )
{
QgsFeature feat;

mFilterModel->layerCache()->completeFeatureAtId( idxToFid( index ), feat );

return QVariant::fromValue( feat );
}
else if ( role == Qt::TextAlignmentRole )
{
return static_cast<Qt::Alignment::Int>( Qt::AlignLeft );
Expand Down
3 changes: 2 additions & 1 deletion src/gui/attributetable/qgsfeaturelistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class GUI_EXPORT QgsFeatureListModel : public QSortFilterProxyModel, public QgsF
enum Role
{
FeatureInfoRole = 0x1000, // Make sure no collisions with roles on QgsAttributeTableModel
FeatureRole
FeatureRole, //!< Feature with all attributes and no geometry
FeatureWithGeometryRole, //!< Feature with all attributes and geometry, \since QGIS 3.42
};

public:
Expand Down
2 changes: 1 addition & 1 deletion src/gui/attributetable/qgsfeaturelistview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ void QgsFeatureListView::contextMenuEvent( QContextMenuEvent *event )

if ( index.isValid() )
{
const QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureRole ).value<QgsFeature>();
const QgsFeature feature = mModel->data( index, QgsFeatureListModel::FeatureWithGeometryRole ).value<QgsFeature>();

QgsActionMenu *menu = new QgsActionMenu( mModel->layerCache()->layer(), feature, QStringLiteral( "Feature" ), this );

Expand Down
36 changes: 36 additions & 0 deletions tests/src/core/testqgsvectorlayercache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ class TestVectorLayerCache : public QObject

void onCommittedFeaturesAdded( const QString &, const QgsFeatureList & );

void testCompleteFeatureAtId();

private:
QgsVectorLayerCache *mVectorLayerCache = nullptr;
QgsCacheIndexFeatureId *mFeatureIdIndex = nullptr;
Expand Down Expand Up @@ -493,5 +495,39 @@ void TestVectorLayerCache::onCommittedFeaturesAdded( const QString &layerId, con
mAddedFeatures.append( features );
}

void TestVectorLayerCache::testCompleteFeatureAtId()
{
QgsVectorLayerCache cache( mPointsLayer, static_cast<int>( mPointsLayer->dataProvider()->featureCount() ) );
// cache only attributes
cache.setCacheGeometry( false );
cache.invalidate();
cache.setFullCache( true );

QgsFeature f;
QgsFeatureIterator it = cache.getFeatures();
it.nextFeature( f );

QVERIFY( cache.isFidCached( f.id() ) );
QVERIFY( cache.mCache[f.id()]->allAttributesFetched() );
QVERIFY( !cache.mCache[f.id()]->geometryFetched() );

cache.featureAtIdWithAllAttributes( 0, f );
QVERIFY( cache.mCache[0]->allAttributesFetched() );
QVERIFY( !cache.mCache[0]->geometryFetched() );
QVERIFY( !f.attribute( 0 ).isNull() );
QVERIFY( !f.attribute( 1 ).isNull() );
QVERIFY( !f.attribute( 2 ).isNull() );
QVERIFY( f.geometry().isNull() );

cache.completeFeatureAtId( 0, f );
QVERIFY( cache.isFidCached( 0 ) );
QVERIFY( cache.mCache[0]->allAttributesFetched() );
QVERIFY( cache.mCache[0]->geometryFetched() );
QVERIFY( !f.attribute( 0 ).isNull() );
QVERIFY( !f.attribute( 1 ).isNull() );
QVERIFY( !f.attribute( 2 ).isNull() );
QVERIFY( !f.geometry().isNull() );
}

QGSTEST_MAIN( TestVectorLayerCache )
#include "testqgsvectorlayercache.moc"
Loading