patternpythonMinor
Calculating areas of map features
Viewed 0 times
areascalculatingmapfeatures
Problem
I am developing a plugin for QGIS (a Geographic Information Systems) software which uses Python. I have calculated four areas of interest, it does this by identifying each feature on the map (called
For example,
For each category, the geometric area is then calculated, totalled and shown in a QLineEdit box. This is then repeated for
My question is how could I make the following code more concise?
```
def land_area():
# Clear all area text boxes
self.dockwidget.area_Score1_lineEdit.clear()
self.dockwidget.area_Score2_lineEdit.clear()
self.dockwidget.area_Score3_lineEdit.clear()
self.dockwidget.area_Score4_lineEdit.clear()
##########################
######### AREA 1 #########
# NULL or Good and Excellent
# Set area to 0
area1_score = 0
area1_cat1 = QgsExpression( """ "Category_A" IS NULL OR (( "Category_A" = 'Good') AND ( "Category_B" = 'Excellent')) """ )
area1_cat1_feat = map_layer.getFeatures( QgsFeatureRequest( area1_cat1 ) )
area1_cat1_ids = [i for i in area1_cat1_feat]
for f in area1_cat1_ids:
area1_score += f.geometry().area()
# Good and Standard
area1_cat2 = QgsExpression( """ ("Category_A" = 'Good') AND ("Category_B" = 'Standard') """ )
area1_cat2_feat = map_layer.getFeatures( QgsFeatureRequest( area1_cat2 ) )
area1_cat2_ids = [i for i in area1_cat2_feat]
for f in area1_cat2_ids:
area1_score += f.geometry().area()
# Meh and Excellent
area1_cat3 = QgsE
map_layer) which falls into certain categories determined by an expression.For example,
Area 1 consists of three categories with the following expressions:Category Expression
NULL or Good and Excellent "Category_A" IS NULL OR (( "Category_A" = 'Good') AND ( "Category_B" = 'Excellent'))
Good and Standard ("Category_A" = 'Good') AND ("Category_B" = 'Standard')
Meh and Excellent ("Category_A" = 'Meh') AND ("Category_B" = 'Excellent')For each category, the geometric area is then calculated, totalled and shown in a QLineEdit box. This is then repeated for
Area 2, Area 3 and Area 4.My question is how could I make the following code more concise?
```
def land_area():
# Clear all area text boxes
self.dockwidget.area_Score1_lineEdit.clear()
self.dockwidget.area_Score2_lineEdit.clear()
self.dockwidget.area_Score3_lineEdit.clear()
self.dockwidget.area_Score4_lineEdit.clear()
##########################
######### AREA 1 #########
# NULL or Good and Excellent
# Set area to 0
area1_score = 0
area1_cat1 = QgsExpression( """ "Category_A" IS NULL OR (( "Category_A" = 'Good') AND ( "Category_B" = 'Excellent')) """ )
area1_cat1_feat = map_layer.getFeatures( QgsFeatureRequest( area1_cat1 ) )
area1_cat1_ids = [i for i in area1_cat1_feat]
for f in area1_cat1_ids:
area1_score += f.geometry().area()
# Good and Standard
area1_cat2 = QgsExpression( """ ("Category_A" = 'Good') AND ("Category_B" = 'Standard') """ )
area1_cat2_feat = map_layer.getFeatures( QgsFeatureRequest( area1_cat2 ) )
area1_cat2_ids = [i for i in area1_cat2_feat]
for f in area1_cat2_ids:
area1_score += f.geometry().area()
# Meh and Excellent
area1_cat3 = QgsE
Solution
You can greatly simplify this code by introducing a helper function that calculates the
Let's take the first category as an example. You do three times the same job, with a different expression each time. This can be put into a function that takes an expression and returns the total area for that expression:
Note that I used the fact that
With this done we can now loop over all expressions defining one category and output the total land area of multiple expressions:
Note the caveat that this does not remove double-counting. So if feature matches more than one expression it will be counted for more than once. To avoid this you would have to
The only thing left now, is to give this function the appropriate expressions. For this we loop over a list of list of expressions (one list for each category) and the text field belonging to that category:
These two lists can be set either in the above function or (which I opted for here) defined outside and passed to it, making it more re-usable.
Note that you will have to complete
area_score for one of the categories.Let's take the first category as an example. You do three times the same job, with a different expression each time. This can be put into a function that takes an expression and returns the total area for that expression:
def land_area(expression):
"""Returns the area of all features which match the given expression."""
qgs_expression = QgsExpression(expression)
features = map_layer.getFeatures(QgsFeatureRequest(qgs_expression))
return sum(feature.geometry().area() for feature in features)Note that I used the fact that
sum can take a generator expression. There is also no need for the intermediate index list. It also assumes map_layer is some global variable accessible. otherwise you will have to pass it as parameter, or make this function a method by adding self as a parameter.With this done we can now loop over all expressions defining one category and output the total land area of multiple expressions:
def land_area_total(expressions):
"""
Returns the sum of all areas matching any expression in expressions.
Does not remove double-counting.
"""
return sum(land_area(expression) for expression in expressions)Note the caveat that this does not remove double-counting. So if feature matches more than one expression it will be counted for more than once. To avoid this you would have to
OR them all together:def land_area_total(expressions):
"""
Returns the sum of all areas matching any expression in expressions.
Removes double-counting.
"""
expression = " OR ".join("({})".format(e) for e in expressions)
return land_area(expression)The only thing left now, is to give this function the appropriate expressions. For this we loop over a list of list of expressions (one list for each category) and the text field belonging to that category:
def set_land_areas(area_lines, area_expressions):
"""Update the text in all `area_lines` with the areas calculated using the `area_expressions`."""
for area_line, expressions in zip(area_lines, area_expressions):
area_line.clear()
area_line.setText("{:,.0f}".format(land_area_total(expressions)))These two lists can be set either in the above function or (which I opted for here) defined outside and passed to it, making it more re-usable.
def set_category_areas(self):
area_lines = (self.dockwidget.area_Score1_lineEdit,
self.dockwidget.area_Score2_lineEdit,
self.dockwidget.area_Score3_lineEdit,
self.dockwidget.area_Score4_lineEdit)
area_expressions = (['"Category_A" IS NULL OR(("Category_A"="Good") AND("Category_B"="Excellent"))',
'("Category_A" = "Good") AND ("Category_B" = "Standard")',
'("Category_A" = "Meh") AND ("Category_B" = "Excellent")'],
...)
set_land_areas(area_lines, area_expressions)Note that you will have to complete
area_expressions with the expressions for the other categories.Code Snippets
def land_area(expression):
"""Returns the area of all features which match the given expression."""
qgs_expression = QgsExpression(expression)
features = map_layer.getFeatures(QgsFeatureRequest(qgs_expression))
return sum(feature.geometry().area() for feature in features)def land_area_total(expressions):
"""
Returns the sum of all areas matching any expression in expressions.
Does not remove double-counting.
"""
return sum(land_area(expression) for expression in expressions)def land_area_total(expressions):
"""
Returns the sum of all areas matching any expression in expressions.
Removes double-counting.
"""
expression = " OR ".join("({})".format(e) for e in expressions)
return land_area(expression)def set_land_areas(area_lines, area_expressions):
"""Update the text in all `area_lines` with the areas calculated using the `area_expressions`."""
for area_line, expressions in zip(area_lines, area_expressions):
area_line.clear()
area_line.setText("{:,.0f}".format(land_area_total(expressions)))def set_category_areas(self):
area_lines = (self.dockwidget.area_Score1_lineEdit,
self.dockwidget.area_Score2_lineEdit,
self.dockwidget.area_Score3_lineEdit,
self.dockwidget.area_Score4_lineEdit)
area_expressions = (['"Category_A" IS NULL OR(("Category_A"="Good") AND("Category_B"="Excellent"))',
'("Category_A" = "Good") AND ("Category_B" = "Standard")',
'("Category_A" = "Meh") AND ("Category_B" = "Excellent")'],
...)
set_land_areas(area_lines, area_expressions)Context
StackExchange Code Review Q#154245, answer score: 6
Revisions (0)
No revisions yet.