[Qgis-developer] new vector api select features

Martin Dobias wonder.sk at gmail.com
Tue Feb 5 13:51:36 PST 2013


Hi Richard

On Tue, Feb 5, 2013 at 7:27 PM, Richard Duivenvoorde
<rdmailings at duif.net> wrote:
> Hi Devs,
>
> I used the following in a python plugin to select features:
>
> self.provider.select(self.provider.attributeIndexes(), mapCanvasExtent,
> True, True)
>
> is there somewhere an example how to do this in the new api using
>
> QgsFeatureIterator QgsOgrProvider::getFeatures( const QgsFeatureRequest&
> request )
>
> and python?

I haven't updated the examples yet, sorry. Here we go in Python:

for f in layer.getFeatures():
  print f["name"].toString(), f.geometry().exportToWkt()

This will use give you all features from a layer with geometries and
attributes. If no request is specified in the getFeatures() call, it
will use default request which fetches everything - equal to calling
getFeatures(QgsFeatureRequest()).

The getFeatures() call returns QgsFeatureIterator instance. Python
bindings of this class support python's iterator protocol [1] so you
can use it directly in a 'for' cycle as shown above. Of course it is
still possible to original C++ API style, but it does not feel very
pythonic:

f = QgsFeature()
iter = layer.getFeatures()
while iter.nextFeature(f):
  print f["name"].toString(), f.geometry().exportToWkt()

I strongly recommend the former variant - it's easier to write and
understand, so less prone to errors.

Access to attributes: there is no f.attributeMap() anymore, because
attributes are now stored in a vector (Python: list) instead of a map
(Python: dict). QgsFeature class emulates python container object [2]
so you can access attributes as if QgsFeature instance was a list or
dictionary, with keys being either field indices or field names:
f[0]  ... first attribute
f["type"]  ... attribute named "type"
It is still possible to get all attributes: f.attributes() returns a
list of values.

Attribute values are returned as QVariant's as before, so in order to
access a string attribute you need to do something like
f["name"].toString(), for integers it is the cryptic
f["speed"].toInt()[0]. At some point (before the release of 2.0) I
would like to switch QGIS to use newer PyQt4 API that does automatic
conversion of QVariant to python objects. This will cause that
f["name"] or f["speed"] will directly return a string or int without
the need to call toString()/toInt() methods).

Currently we have three types of requests: 1. no filter (default), 2.
filter by rectangle, 3. filter by feature ID.

Fetching of features within a rectangle is done this way:
rect = QgsRectangle(-10,-10,10,10)
request = QgsFeatureRequest().setFilterRect(rect)
for f in layer.getFeatures(request):
  # usual stuff here with the feature

For the sake of processing speed, some providers may return features
that are not in the rectangle (only their bounding box intersects the
rectangle). Usually this is not a problem (e.g. for rendering), but
for some cases such as identification of features in particular
rectangle this is not wanted. In old API, setting fourth argument of
select() to true returned only features that really intersected the
rectangle. In new API this is done with a flag:
request.setFlags(QgsFeatureRequest.ExactIntersect)

Another type of requests are requests for a particular feature ID. In
these cases we expect just one feature so we could directly fetch just
one feature from the iterator:
request = QgsFeatureRequest().setFilterFid(14)
f = l.getFeatures(request).next()

In case feature with given ID does not exist, python exception
StopIteration is raised.

If developers would like to optimize the requests to avoid fetching
things that are not required, it is possible to:
- avoid fetching of geometry: request.setFlags(QgsFeatureRequest.NoGeometry)
- fetch only some (or no) attributes - e.g. fetch just first two
attributes: request.setSubsetOfAttributes([0,1])

Even though API allows that, it is currently not possible to have
concurrent feature iterators on one layer or data provider, something
like this:
iter1 = layer.getFeatures()
iter2. = layer.getFeatures()
# do something with both iter1 and iter2
This will NOT work - in the second getFeatures() call the iter1 will
get closed and it will not return any more features. In the future
some providers will support concurrent iterators, there will be
probably another provider capability that would determine if a
provider supports that.

I hope I haven't forgotten any important use case. If more examples
are necessary, I will try to provide them. Please do not hesitate to
ask if things are not clear yet.

Regards
Martin

[1] http://docs.python.org/2/library/stdtypes.html#generator-types
[2] http://docs.python.org/2/reference/datamodel.html#object.__getitem__


More information about the Qgis-developer mailing list