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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Empower plugins with more functionalities #5189

Merged
merged 13 commits into from Apr 18, 2024
Merged

Empower plugins with more functionalities #5189

merged 13 commits into from Apr 18, 2024

Conversation

nirvn
Copy link
Member

@nirvn nirvn commented Apr 17, 2024

This PR enables plugins to do the following:

  • retrieve map layers by name from a QgsProject instance
  • iterate through features from a vector layer
  • change a feature attribute value through the vector layer's changeAttributeValue(s) functions
  • retrieve field information (such as attribute index from a given field name)

The invoke.patch changes are being upstreamed, but since we're on 3.36 for now, we can enjoy vcpkg's patching powers 馃挭

Upstream PRs:

Iterating through features is done like this:

let layers = qgisProject.mapLayersByName('Apiary')      
let layer = layers[0]      
let it = LayerUtils.createFeatureIteratorFromExpression(layer, "fid IN (47,48,49)")
while (it.hasNext())
{
  let feature = it.next()
  console.log(feature.id)
}

Editing a feature attribute is done like this:

let layers = qgisProject.mapLayersByName('Apiary')      
let layer = layers[0]      
let featureId = 47
let fieldIndex = layer.fields.lookupField('beekeeper')

layer.startEditing()
layer.changeAttributeValue(featureId, fieldIndex, "work!")
layer.commitChanges()

Creating a new feature, editing an attribute, and adding to a layer is done like this:

let feature = FeatureUtils.createFeature(layer)
feature.setAttribute("bin_uuid", string)
feature.setAttribute("check_datetime", new Date().toLocaleString())
        
layer.startEditing()
LayerUtils.addFeature(layer, feature)
layer.commitChanges()

Some more information to explain/justify some of the choices done here.

First, QML Javascript engine does not allow for non-const parameter by reference. This means that any C++ function that has a non-const & parameter can't be made Q_INVOKABLE. In practice, this is most impactful for the feature iterator here because we can't simply expose the QgsFeatureIterator::nextFeature( QgsFeature &feature ). It's a good lesson learned that is good to internalize now as we move forward :)

To deal with this, I've come up with a FeatureIterator which is Javascript friendly, with a bool hasNext() function and a QgsFeature next() function. I settled on this form after researching what kind of iterator template there is out there for Javascript.

This could eventually be upstreamed into QgsFeatureIterator itself. Looking at Qt's QList iterator for example, it does support Java-style iterators with hasNext() / next() / etc. (https://doc.qt.io/qt-6/java-style-iterators.html#java-style-iterators). This is a discussion that's worth having upstream as to whether we want this or not there.

Also, for the record, QML's Javascript engine does not have the capability to expand an object type via Object.prototype.my_function() = function {}, so adding functions to a given QObject or QGadget requires us to do it on the C++ side of things.

Finally, QML's Javascript engine does not allow the creation of objects directly in Javascript (other than via Qt.createComponent / Qt.createQmlObject which is not super graceful). This is why for e.g. Qt has a Qt.point() function to create a point object. That's why for e.g. we can't go let request = QgsFeatureRequest(...) on the Javascript side of things.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

@@ -27,9 +27,9 @@ FeatureUtils::FeatureUtils( QObject *parent )
{
}

QgsFeature FeatureUtils::initFeature( QgsVectorLayer *layer, QgsGeometry geometry )
QgsFeature FeatureUtils::createFeature( QgsVectorLayer *layer, QgsGeometry geometry )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: the parameter 'geometry' is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param]

src/core/utils/featureutils.h:33:

-     static Q_INVOKABLE QgsFeature createFeature( QgsVectorLayer *layer = nullptr, QgsGeometry geometry = QgsGeometry() );
+     static Q_INVOKABLE QgsFeature createFeature( QgsVectorLayer *layer = nullptr, const QgsGeometry& geometry = QgsGeometry() );
Suggested change
QgsFeature FeatureUtils::createFeature( QgsVectorLayer *layer, QgsGeometry geometry )
QgsFeature FeatureUtils::createFeature( QgsVectorLayer *layer, const QgsGeometry& geometry )

{
QgsFeature f( layer->fields() );
QgsFeature f( layer ? layer->fields() : QgsFields() );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'QgsVectorLayer *' -> bool [readability-implicit-bool-conversion]

Suggested change
QgsFeature f( layer ? layer->fields() : QgsFields() );
QgsFeature f( layer != nullptr ? layer->fields() : QgsFields() );

{
QgsFeature f( layer->fields() );
QgsFeature f( layer ? layer->fields() : QgsFields() );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

  QgsFeature f( layer ? layer->fields() : QgsFields() );
             ^

public:
FeatureIterator( QgsVectorLayer *layer = nullptr, const QgsFeatureRequest &request = QgsFeatureRequest() )
{
if ( layer )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'QgsVectorLayer *' -> bool [readability-implicit-bool-conversion]

Suggested change
if ( layer )
if ( layer != nullptr )

@@ -26,7 +26,7 @@ TEST_CASE( "FeatureUtils" )

QgsGeometry geometry = QgsGeometry::fromWkt( QStringLiteral( "Polygon (((8 8, 9 8, 8 9, 8 8)))" ) );

QgsFeature f = FeatureUtils::initFeature( vl.get(), geometry );
QgsFeature f = FeatureUtils::createFeature( vl.get(), geometry );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'f' of type 'QgsFeature' can be declared 'const' [misc-const-correctness]

Suggested change
QgsFeature f = FeatureUtils::createFeature( vl.get(), geometry );
QgsFeature const f = FeatureUtils::createFeature( vl.get(), geometry );

@@ -26,7 +26,7 @@

QgsGeometry geometry = QgsGeometry::fromWkt( QStringLiteral( "Polygon (((8 8, 9 8, 8 9, 8 8)))" ) );

QgsFeature f = FeatureUtils::initFeature( vl.get(), geometry );
QgsFeature f = FeatureUtils::createFeature( vl.get(), geometry );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable name 'f' is too short, expected at least 3 characters [readability-identifier-length]

  QgsFeature f = FeatureUtils::createFeature( vl.get(), geometry );
             ^

@qfield-fairy
Copy link
Collaborator

qfield-fairy commented Apr 17, 2024

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

@@ -199,6 +199,15 @@ QString LayerUtils::fieldType( const QgsField &field )
return QVariant( field.type() ).typeName();
}

bool LayerUtils::addFeature( QgsVectorLayer *layer, QgsFeature feature )
{
if ( layer )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: implicit conversion 'QgsVectorLayer *' -> bool [readability-implicit-bool-conversion]

Suggested change
if ( layer )
if ( layer != nullptr )

Copy link
Member

@domi4484 domi4484 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me

src/core/utils/featureutils.h Outdated Show resolved Hide resolved
@nirvn nirvn enabled auto-merge April 18, 2024 15:18
@nirvn nirvn merged commit 32942c0 into master Apr 18, 2024
22 of 23 checks passed
@nirvn nirvn deleted the plugins branch April 18, 2024 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants