Skip to content

Commit

Permalink
Fix conflicts (in docs) after mering subreceptors branch into master
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondnijssen committed Oct 2, 2024
2 parents b30e709 + abfdeaf commit 0d28778
Show file tree
Hide file tree
Showing 73 changed files with 8,010 additions and 3,623 deletions.
135 changes: 77 additions & 58 deletions ImaerPlugin/algs/relate.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,94 +23,113 @@
QgsFields,
QgsFeature
)
from qgis import processing
from qgis import processing, utils

from ImaerPlugin.gpkg import ImaerGpkgFieldFactory


class RelateAlgorithm(QgsProcessingAlgorithm):
# Abstract base class for all relate algorithms
dep_field_names = ['dep_NH3', 'dep_NOX']
dep_total_field_name = 'dep_TOTAL'

geometry_cache = {}
field_factory = ImaerGpkgFieldFactory()
output_field_names_dict = {}

def get_layer_type(self, layer):
plugin = utils.plugins['ImaerPlugin']
md = plugin.get_imaer_calc_metadata(layer)
if md['is_imaer_calc_layer']:
return md['imaer_layer_type']
elif md['imaer_contribution_layer'] is not None:
return md['imaer_contribution_layer']
else:
return None

def _is_dep_layer(self, layer):
'''Checks if layer contains all mandatory fields'''
mandatory_fields = ['fid'] + self.dep_field_names
layer_field_names = [fld.name() for fld in layer.fields()]

for mandatory_field in mandatory_fields:
if mandatory_field not in layer_field_names:
return False
return True

def _get_output_fields(self, with_totals=True):
fields = QgsFields()
fields.append(QgsField('fid', QVariant.LongLong, 'int8'))
for dep_field_name in self.dep_field_names:
fields.append(QgsField(dep_field_name, QVariant.Double))
if with_totals:
fields.append(QgsField(self.dep_total_field_name, QVariant.Double))
return fields

def _create_result_feature(self, receptor_id, dep_dict, max_decimals=None, with_totals=True):
def find_layer_type(self, layers, feedback=None):
result = []
for layer in layers:
result.append(self.get_layer_type(layer))
return list(set(result))

def _create_result_feature(self, layer_type, key, value_dict, max_decimals=None):
feat = QgsFeature()
geometry = self.geometry_cache[receptor_id]
geometry = self.geometry_cache[key]
feat.setGeometry(geometry)

attributes = [receptor_id]
dep_total = 0
for field_name in self.dep_field_names:
if field_name in dep_dict:
v = dep_dict[field_name]
if v is not None:
dep_total += v
if max_decimals is not None:
v = round(v, max_decimals)
else:
v = None
attributes.append(v)
if layer_type not in self.output_field_names_dict:
self.output_field_names_dict[layer_type] = self.field_factory.create_fields_for_layer_type(layer_type).names()

output_field_names = self.output_field_names_dict[layer_type]

attributes = []
if layer_type == 'sub_points':
parts = key.split('_')
for part in parts:
attributes.append(int(part))
else:
attributes.append(key)

if with_totals:
if max_decimals is not None:
dep_total = round(dep_total, max_decimals)
attributes.append(dep_total)
num_id_fields = len(attributes)

for output_field_name in output_field_names[num_id_fields:]:
attributes.append(value_dict.get(output_field_name, None))

feat.setAttributes(attributes)
return feat

def _create_receptor_dictionary(self, layer):
def _create_value_dictionary(self, layer, feedback=None):
'''
Returns a dictionary with receptor_id as key and a dictionary with
deposition_fields and values. Like this:
{288: {'dep_NH3': 0.0, 'dep_NOX': 0.02}, 816: {'dep_NH3': None, 'dep_NOX': 0.0}, ... }
Returns a dictionary with unique keys and a dictionary with
result field names and values. Like this:
{288: {'deposition_nh3': 0.0, 'deposition_nox': 0.02}, 816: {'deposition_nh3': None, 'deposition_nox': 0.3}, ... }
Also adds the feature geometry in the geometry_cache dictionary.
Also adds the feature geometry to the geometry_cache dictionary.
Returns None if not all deposition fields and an "fid" field exist,
which will abort the calculation.
'''
if not self._is_dep_layer(layer):

layer_type = self.get_layer_type(layer)
if layer_type is None:
return None
if feedback is not None:
feedback.pushInfo(repr(layer_type))

value_fields = self.field_factory.create_fields_for_layer_type(layer_type, value_fields_only=True)
value_field_names = value_fields.names()

if feedback is not None:
feedback.pushInfo(repr(value_field_names))

result = {}
for feat in layer.getFeatures():
receptor_id = feat['fid']
dep_dict = {}
for dep_field_name in self.dep_field_names:
v = feat[dep_field_name]
if layer_type in ['receptor_points', 'receptor_hexagons']:
key = feat['receptor_id']
elif layer_type == 'sub_points':
key = f'{feat["receptor_id"]}_{feat["sub_point_id"]}_{feat["level"]}'
elif layer_type == 'calculation_points':
key = feat['calculation_point_id']
else:
raise Exception('Invalid IMAER layer type')

value_dict = {}
for value_field_name in value_field_names:
v = feat[value_field_name]
if isinstance(v, QVariant) and str(v) == 'NULL':
v = None
dep_dict[dep_field_name] = v
result[receptor_id] = dep_dict
if receptor_id not in self.geometry_cache:
self.geometry_cache[receptor_id] = feat.geometry()
value_dict[value_field_name] = v
result[key] = value_dict
if key not in self.geometry_cache:
self.geometry_cache[key] = feat.geometry()
return result

def _get_receptor_value(self, receptor_dict, receptor_id, field_name, no_data=None):
def _get_receptor_value(self, receptor_dict, key, field_name, no_data=None):
'''Returns the value for the field_name if present, or otherwise the no_data value.'''
if receptor_id not in receptor_dict:
return no_data
if field_name not in receptor_dict[receptor_id]:
return no_data
v = receptor_dict[receptor_id][field_name]
if v is not None:
return v
return no_data
if v is None:
return no_data
return v
74 changes: 47 additions & 27 deletions ImaerPlugin/algs/relate_difference.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class RelateDifferenceAlgorithm(RelateAlgorithm):

INPUT1 = 'INPUT_1'
INPUT2 = 'INPUT_2'
ADD_TOTALS = 'ADD_TOTALS'
OUTPUT = 'OUTPUT'

def tr(self, string):
Expand Down Expand Up @@ -61,13 +60,6 @@ def initAlgorithm(self, config=None):
[QgsProcessing.TypeVectorPolygon]
)
)
self.addParameter(
QgsProcessingParameterBoolean(
self.ADD_TOTALS,
self.tr('Add field with total depositions'),
defaultValue=True
)
)
self.addParameter(
QgsProcessingParameterFeatureSink(
self.OUTPUT,
Expand All @@ -85,22 +77,33 @@ def processAlgorithm(self, parameters, context, feedback):
current = 1

self.geometry_cache = {}
result_value_dicts = []

layer_types = self.find_layer_type([layer_1, layer_2])
feedback.pushInfo(repr(layer_types))

if len(layer_types) == 0:
raise QgsProcessingException(f'No IMAER layer type found')
elif len(layer_types) > 1:
raise QgsProcessingException(f'Multiple IMAER layer types found')

layer_type = layer_types[0]

dep_dict_layers = []
for layer in [layer_1, layer_2]:
layer_name = layer.name()
if not self._is_dep_layer(layer):
raise QgsProcessingException(f'"{layer_name}" is not a valid deposition layer.')
dep_dict_layer = self._create_receptor_dictionary(layer)
if dep_dict_layer is None:

value_dict = self._create_value_dictionary(layer, feedback)

if value_dict is None:
raise QgsProcessingException(f'"{layer_name}" is not a valid deposition layer.')
dep_dict_layers.append(dep_dict_layer)

result_value_dicts.append(value_dict)

feedback.setProgress(int(current * step))
current += 1

add_totals = self.parameterAsBoolean(parameters, self.ADD_TOTALS, context)
output_fields = self._get_output_fields(with_totals=add_totals)
output_fields = self.field_factory.create_fields_for_layer_type(layer_type, value_fields_only=False)
feedback.pushInfo(repr(output_fields))

(sink, dest_id) = self.parameterAsSink(
parameters,
Expand All @@ -114,7 +117,7 @@ def processAlgorithm(self, parameters, context, feedback):
if sink is None:
raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))

calc_result_dict = self._calc_dict_difference(dep_dict_layers[0], dep_dict_layers[1])
calc_result_dict = self._calc_dict_difference(result_value_dicts[0], result_value_dicts[1])
feedback.setProgress(50)

if len(calc_result_dict) == 0:
Expand All @@ -123,25 +126,42 @@ def processAlgorithm(self, parameters, context, feedback):
step = 50 / len(calc_result_dict)
current = 1

for receptor_id in calc_result_dict:
for key in calc_result_dict:
if feedback.isCanceled():
break

feat = self._create_result_feature(receptor_id, calc_result_dict[receptor_id], max_decimals=6, with_totals=add_totals)
feat = self._create_result_feature(layer_type, key, calc_result_dict[key], max_decimals=6)
sink.addFeature(feat, QgsFeatureSink.FastInsert)

feedback.setProgress(50 + int(current * step))
current += 1

return {self.OUTPUT: dest_id}

def _calc_dict_difference(self, dep_dict_layer_1, dep_dict_layer_2):
def _calc_dict_difference(self, result_value_dict_1, result_value_dict_2):
result = {}
for receptor_id in self.geometry_cache:
dep_dict = {}
for dep_field_name in self.dep_field_names:
v1 = self._get_receptor_value(dep_dict_layer_1, receptor_id, dep_field_name, no_data=0)
v2 = self._get_receptor_value(dep_dict_layer_2, receptor_id, dep_field_name, no_data=0)
dep_dict[dep_field_name] = v1 - v2
result[receptor_id] = dep_dict

for id in result_value_dict_1:
for substance, value in result_value_dict_1[id].items():
if id not in result:
result[id] = {}
if substance not in result[id]:
result[id][substance] = value

for id in result_value_dict_2:
for substance, value in result_value_dict_2[id].items():
if id not in result:
result[id] = {}
if substance not in result[id]:
if value is None:
result[id][substance] = value
else:
result[id][substance] = -value
else:
if value is not None:
old_value = result[id].get(substance)
if old_value is None:
result[id][substance] = -value
else:
result[id][substance] = old_value - value
return result
Loading

0 comments on commit 0d28778

Please sign in to comment.