Try moving the setExtent()
call on the QgsMapSettings
object inside your loop and pass the bounding box of the current feature's geometry on each iteration:
def finished():
img = render.renderedImage()
# save the image; img.save("C:\QGIS Project\Restructured\Rev1\\test_render.png","png")
img.save(f'C:\QGIS Project\Restructured\Rev1\\test_render_{feature[0]}.png', "png")
layer = QgsProject.instance().mapLayersByName("Rotated")[0]
settings = QgsMapSettings()
settings.setLayers([layer])
settings.setBackgroundColor(QColor(255, 255, 255))
settings.setOutputSize(QSize(400, 400))
for feature in layer.getFeatures():
layer.setSubsetString("fid = {}".format(feature.id()))
settings.setExtent(feature.geometry().boundingBox())
render = QgsMapRendererParallelJob(settings)
render.finished.connect(finished)
# Start the rendering
render.start()
render.waitForFinished()
layer.setSubsetString("")
By the way, if you wish to add a small margin around each feature's extent you can use the grow()
method on the QgsRectangle
object returned by feature.geometry().boundingBox()
. So your loop block could look like:
for feature in layer.getFeatures():
layer.setSubsetString("fid = {}".format(feature.id()))
extent = feature.geometry().boundingBox()
extent.grow(0.01) # Play with the value to find a suitable margin
settings.setExtent(extent)
render = QgsMapRendererParallelJob(settings)
render.finished.connect(finished)
# Start the rendering
render.start()
render.waitForFinished()
You have a number of issues here. Firstly, with the addExpressionField()
method, you are trying to add a virtual field to a QgsVectorLayer
object which is not loaded into a project, which simply won't work. Virtual fields are added to the end of an attribute table in a project vector layer and exist only in the project settings in which they have been created. They cannot be permanently added to the layer's data provider. Please see the first point under the Note section here.
Other than that you have some logic errors e.g. you are adding fields to the layer for every feature, so you should move that section out of the feature iterator loop. Also you need to call updateFields()
after adding the fields.
I strongly recommend studying the PyQGIS Developer Cookbook, especially the following sections:
Modifying Vector Layers
Expressions with features
Try the following script:
path = 'D:\\shps\\SHP'
for root, directory, files in os.walk(path):
for file in files:
if file.endswith('.shp'):
full_path = os.path.join(root,file)
vlayer = QgsVectorLayer(full_path,file[:-4],'ogr')
if not vlayer.isValid() or not vlayer.geometryType() == QgsWkbTypes.PolygonGeometry:
if not vlayer.isValid():
print(f'Layer from {file} not valid')
if vlayer.geometryType() != QgsWkbTypes.PolygonGeometry:
print(f'Layer from {file} not a polygon layer')
continue
vlayer.dataProvider().addAttributes([
QgsField('ID', QVariant.String, len=50),
QgsField('STUDY', QVariant.String, len=100),
QgsField('PROJECT', QVariant.String, len=100),
QgsField('STAGE', QVariant.String, len=100),
QgsField('DATE', QVariant.String, len=30),
QgsField('COMPANY', QVariant.String, len=50),
QgsField('AREA_HA', QVariant.Double, len=19, prec=9)
])
vlayer.updateFields()# Need this line!
e = QgsExpression('$area/10000')
c = QgsExpressionContext()
c.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(vlayer))
att_map = {}
for ft in vlayer.getFeatures():
c.setFeature(ft)
att_map[ft.id()] = {vlayer.fields().lookupField('AREA_HA'): e.evaluate(c)}
vlayer.dataProvider().changeAttributeValues(att_map)
Best Answer
A couple of problems. Firstly, the
aggregate
method you are using is method of theQgsVectorLayer
class, so you need to call it on aQgsVectorLayer
object. Then, the second argument needs to be a field name string, however thefield1
object is the value in the field you pass to your custom function, evaluated for each feature.Try the following:
If you want decimal places, change the last line e.g.
The first argument of this custom function is the field (double quoted) which will be evaluated for each feature. The second argument is the name of the field as a string (single quoted) which will be passed to the aggregate method.
Example usage:
Result:
Honestly, I don't really see a huge advantage of a custom function over the field calculator expression. Perhaps you would prefer a small script which you could run in the Python console which will add and fill all your percentage fields in one go.
Try:
Test result: