# -*- coding: utf-8 -*-
# /***************************************************************************
# Irmt
# A QGIS plugin
# OpenQuake Integrated Risk Modelling Toolkit
# -------------------
# begin : 2013-10-24
# copyright : (C) 2014 by GEM Foundation
# email : devops@openquake.org
# ***************************************************************************/
#
# OpenQuake is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# OpenQuake is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with OpenQuake. If not, see <http://www.gnu.org/licenses/>.
import os
from random import randrange
from osgeo import ogr
from qgis.core import (QgsVectorLayer,
QgsProject,
QgsStyle,
QgsSymbol,
QgsOuterGlowEffect,
QgsSingleSymbolRenderer,
QgsGradientColorRamp,
QgsGraduatedSymbolRenderer,
QgsRuleBasedRenderer,
QgsFillSymbol,
QgsWkbTypes,
QgsMapLayer,
QgsMarkerSymbol,
QgsSimpleFillSymbolLayer,
QgsRendererCategory,
QgsCategorizedSymbolRenderer,
QgsApplication,
QgsExpression,
NULL,
QgsSimpleMarkerSymbolLayerBase,
)
from qgis.gui import QgsSublayersDialog
from qgis.PyQt.QtCore import pyqtSignal, QDir, QSettings, QFileInfo, Qt
from qgis.PyQt.QtWidgets import (
QDialogButtonBox,
QDialog,
QFileDialog,
QComboBox,
QSpinBox,
QLabel,
QCheckBox,
QHBoxLayout,
QVBoxLayout,
QToolButton,
QGroupBox,
)
from qgis.PyQt.QtGui import QColor
from svir.calculations.process_layer import ProcessLayer
from svir.calculations.aggregate_loss_by_zone import (
calculate_zonal_stats)
from svir.utilities.shared import (OQ_CSV_TO_LAYER_TYPES,
OQ_COMPLEX_CSV_TO_LAYER_TYPES,
OQ_TO_LAYER_TYPES,
OQ_EXTRACT_TO_LAYER_TYPES,
RAMP_EXTREME_COLORS,
)
from svir.utilities.utils import (get_ui_class,
get_style,
log_msg,
tr,
get_file_size,
get_irmt_version,
write_metadata_to_layer,
)
from svir.tasks.extract_npz_task import TaskCanceled
FORM_CLASS = get_ui_class('ui_load_output_as_layer.ui')
[docs]class LoadOutputAsLayerDialog(QDialog, FORM_CLASS):
"""
Dialog to load an oq-engine output as layer
"""
init_done = pyqtSignal()
loading_completed = pyqtSignal()
loading_exception = pyqtSignal(Exception)
def __init__(self, drive_engine_dlg, iface, viewer_dock,
session, hostname, calc_id, output_type=None,
path=None, mode=None, zonal_layer_path=None,
engine_version=None):
# sanity check
if output_type not in OQ_TO_LAYER_TYPES:
raise NotImplementedError(output_type)
self.drive_engine_dlg = drive_engine_dlg
self.iface = iface
self.viewer_dock = viewer_dock
self.path = path
self.session = session
self.hostname = hostname
self.calc_id = calc_id
self.output_type = output_type
self.mode = mode # if 'testing' it will avoid some user interaction
self.zonal_layer_path = zonal_layer_path
self.engine_version = engine_version
QDialog.__init__(self)
# Set up the user interface from Designer.
self.setupUi(self)
# Disable ok_button until all user options are set
self.ok_button = self.buttonBox.button(QDialogButtonBox.Ok)
self.ok_button.setDisabled(True)
[docs] def finalize_init(self, extracted_npz):
self.npz_file = extracted_npz
self.populate_out_dep_widgets()
self.adjustSize()
self.set_ok_button()
self.show()
self.init_done.emit()
[docs] def create_num_sites_indicator(self):
self.num_sites_msg = 'Number of sites: %s'
self.num_sites_lbl = QLabel(self.num_sites_msg % '')
self.vlayout.addWidget(self.num_sites_lbl)
[docs] def create_file_size_indicator(self):
self.file_size_msg = 'File size: %s'
self.file_size_lbl = QLabel(self.file_size_msg % '')
self.vlayout.addWidget(self.file_size_lbl)
[docs] def create_load_multicol_ckb(self):
self.load_multicol_ckb = QCheckBox(
'Load one layer per realization or statistic')
self.vlayout.addWidget(self.load_multicol_ckb)
[docs] def create_rlz_or_stat_selector(self, all_ckb=False, label='Realization'):
self.rlz_or_stat_lbl = QLabel(label)
self.rlz_or_stat_cbx = QComboBox()
self.rlz_or_stat_cbx.setEnabled(False)
self.rlz_or_stat_cbx.currentIndexChanged['QString'].connect(
self.on_rlz_or_stat_changed)
if all_ckb:
self.load_all_rlzs_or_stats_chk = QCheckBox(
'Load all realizations')
self.load_all_rlzs_or_stats_chk.stateChanged[int].connect(
self.on_load_all_rlzs_or_stats_chk_stateChanged)
self.vlayout.addWidget(self.load_all_rlzs_or_stats_chk)
self.vlayout.addWidget(self.rlz_or_stat_lbl)
self.vlayout.addWidget(self.rlz_or_stat_cbx)
[docs] def on_load_all_rlzs_or_stats_chk_stateChanged(self, state):
self.rlz_or_stat_cbx.setEnabled(state == Qt.Unchecked)
[docs] def create_selector(
self, name, label_text, filter_ckb=False, add_to_layout=None,
on_text_changed=None):
if add_to_layout is not None:
layout = add_to_layout
else:
layout = self.vlayout
setattr(self, "%s_lbl" % name, QLabel(label_text))
setattr(self, "%s_cbx" % name, QComboBox())
lbl = getattr(self, "%s_lbl" % name)
cbx = getattr(self, "%s_cbx" % name)
cbx.setDisabled(filter_ckb)
if on_text_changed is not None:
cbx.currentTextChanged['QString'].connect(on_text_changed)
if filter_ckb:
setattr(self, "filter_by_%s_ckb" % name,
QCheckBox('Filter by %s' % name))
filter_ckb = getattr(self, "filter_by_%s_ckb" % name)
def on_load_all_ckb_changed():
cbx.setEnabled(filter_ckb.isChecked())
filter_ckb.stateChanged[int].connect(on_load_all_ckb_changed)
filter_ckb.setChecked(False)
layout.addWidget(filter_ckb)
layout.addWidget(lbl)
layout.addWidget(cbx)
[docs] def create_imt_selector(self, all_ckb=False):
self.imt_lbl = QLabel('Intensity Measure Type')
self.imt_cbx = QComboBox()
self.imt_cbx.setEnabled(False)
self.imt_cbx.currentIndexChanged['QString'].connect(
self.on_imt_changed)
if all_ckb:
self.load_all_imts_chk = QCheckBox('Load all IMTs')
self.load_all_imts_chk.stateChanged[int].connect(
self.on_load_all_imts_chk_stateChanged)
self.vlayout.addWidget(self.load_all_imts_chk)
self.vlayout.addWidget(self.imt_lbl)
self.vlayout.addWidget(self.imt_cbx)
[docs] def on_load_all_imts_chk_stateChanged(self, state):
self.imt_cbx.setEnabled(state == Qt.Unchecked)
[docs] def create_poe_selector(self, all_ckb=False):
self.poe_lbl = QLabel('Probability of Exceedance')
self.poe_cbx = QComboBox()
self.poe_cbx.setEnabled(False)
self.poe_cbx.currentIndexChanged['QString'].connect(
self.on_poe_changed)
if all_ckb:
self.load_all_poes_chk = QCheckBox('Load all PoEs')
self.load_all_poes_chk.stateChanged[int].connect(
self.on_load_all_poes_chk_stateChanged)
self.vlayout.addWidget(self.load_all_poes_chk)
self.vlayout.addWidget(self.poe_lbl)
self.vlayout.addWidget(self.poe_cbx)
[docs] def on_load_all_poes_chk_stateChanged(self, state):
self.poe_cbx.setEnabled(state == Qt.Unchecked)
[docs] def create_loss_type_selector(self):
self.loss_type_lbl = QLabel('Loss Type')
self.loss_type_cbx = QComboBox()
self.loss_type_cbx.setEnabled(False)
self.loss_type_cbx.currentIndexChanged['QString'].connect(
self.on_loss_type_changed)
self.vlayout.addWidget(self.loss_type_lbl)
self.vlayout.addWidget(self.loss_type_cbx)
[docs] def create_eid_selector(self):
self.eid_lbl = QLabel('Event ID')
self.eid_sbx = QSpinBox()
self.eid_sbx.setEnabled(False)
self.vlayout.addWidget(self.eid_lbl)
self.vlayout.addWidget(self.eid_sbx)
[docs] def create_dmg_state_selector(self):
self.dmg_state_lbl = QLabel('Damage state')
self.dmg_state_cbx = QComboBox()
self.dmg_state_cbx.setEnabled(False)
self.dmg_state_cbx.currentIndexChanged['QString'].connect(
self.on_dmg_state_changed)
self.vlayout.addWidget(self.dmg_state_lbl)
self.vlayout.addWidget(self.dmg_state_cbx)
[docs] def create_taxonomy_selector(self):
self.taxonomy_lbl = QLabel('Taxonomy')
self.taxonomy_cbx = QComboBox()
self.taxonomy_cbx.setEnabled(False)
self.vlayout.addWidget(self.taxonomy_lbl)
self.vlayout.addWidget(self.taxonomy_cbx)
[docs] def create_style_by_selector(self):
self.style_by_lbl = QLabel('Style by')
self.style_by_cbx = QComboBox()
self.vlayout.addWidget(self.style_by_lbl)
self.vlayout.addWidget(self.style_by_cbx)
[docs] def create_load_selected_only_ckb(self):
self.load_selected_only_ckb = QCheckBox("Load only the selected items")
self.load_selected_only_ckb.setChecked(True)
self.vlayout.addWidget(self.load_selected_only_ckb)
[docs] def create_show_return_period_ckb(self):
self.show_return_period_chk = QCheckBox(
"Show the return time in layer names")
self.show_return_period_chk.setChecked(False)
self.vlayout.addWidget(self.show_return_period_chk)
[docs] def create_save_as_shp_ckb(self):
self.save_as_shp_ckb = QCheckBox("Save the loaded layer as shapefile")
self.save_as_shp_ckb.setChecked(False)
self.vlayout.addWidget(self.save_as_shp_ckb)
[docs] def create_aggregate_by_site_ckb(self):
self.aggregate_by_site_ckb = QCheckBox("Aggregate by site")
self.aggregate_by_site_ckb.setChecked(True)
self.vlayout.addWidget(self.aggregate_by_site_ckb)
[docs] def create_zonal_layer_selector(self, discard_nonmatching=True):
self.added_zonal_layer = None
self.zonal_layer_gbx = QGroupBox()
self.zonal_layer_gbx.setTitle('Aggregate by zone')
self.zonal_layer_gbx.setCheckable(True)
self.zonal_layer_gbx.setChecked(False)
self.zonal_layer_gbx_v_layout = QVBoxLayout()
self.zonal_layer_gbx.setLayout(self.zonal_layer_gbx_v_layout)
self.zonal_layer_cbx = QComboBox()
self.zonal_layer_cbx.addItem('')
self.zonal_layer_lbl = QLabel('Zonal layer')
self.zonal_layer_tbn = QToolButton()
self.zonal_layer_tbn.setText('...')
self.discard_nonmatching_chk = QCheckBox(
'Discard zones with no points')
self.discard_nonmatching_chk.setChecked(discard_nonmatching)
self.zonal_layer_h_layout = QHBoxLayout()
self.zonal_layer_h_layout.addWidget(self.zonal_layer_cbx)
self.zonal_layer_h_layout.addWidget(self.zonal_layer_tbn)
self.zonal_layer_gbx_v_layout.addWidget(self.zonal_layer_lbl)
self.zonal_layer_gbx_v_layout.addLayout(self.zonal_layer_h_layout)
self.zonal_layer_gbx_v_layout.addWidget(self.discard_nonmatching_chk)
self.vlayout.addWidget(self.zonal_layer_gbx)
self.zonal_layer_tbn.clicked.connect(self.open_load_zonal_layer_dialog)
self.zonal_layer_cbx.currentIndexChanged[int].connect(
self.on_zonal_layer_cbx_currentIndexChanged)
self.zonal_layer_gbx.toggled[bool].connect(
self.on_zonal_layer_gbx_toggled)
self.iface.layerTreeView().currentLayerChanged.connect(
self.on_currentLayerChanged)
[docs] def on_currentLayerChanged(self):
self.pre_populate_zonal_layer_cbx()
[docs] def pre_populate_zonal_layer_cbx(self):
# populate cbx only with vector layers containing polygons
self.zonal_layer_cbx.clear()
for key, layer in QgsProject.instance().mapLayers().items():
if layer.type() != QgsMapLayer.VectorLayer:
continue
if layer.geometryType() == QgsWkbTypes.PolygonGeometry:
self.zonal_layer_cbx.addItem(layer.name())
self.zonal_layer_cbx.setItemData(
self.zonal_layer_cbx.count()-1, layer.id())
if self.added_zonal_layer is not None:
self.zonal_layer_cbx.setCurrentIndex(
self.zonal_layer_cbx.findData(self.added_zonal_layer.id()))
self.zonal_layer_gbx.setChecked(
self.zonal_layer_cbx.currentIndex() != -1)
[docs] def on_zonal_layer_cbx_currentIndexChanged(self, new_index):
self.zonal_layer = None
if not self.zonal_layer_cbx.currentText():
if self.zonal_layer_gbx.isChecked():
self.ok_button.setEnabled(False)
return
zonal_layer_id = self.zonal_layer_cbx.itemData(new_index)
self.zonal_layer = QgsProject.instance().mapLayer(zonal_layer_id)
self.set_ok_button()
[docs] def on_zonal_layer_gbx_toggled(self, is_checked):
if is_checked and not self.zonal_layer_cbx.currentText():
self.ok_button.setEnabled(False)
else:
self.set_ok_button()
[docs] def on_output_type_changed(self):
if self.output_type in OQ_TO_LAYER_TYPES:
self.create_load_selected_only_ckb()
elif self.output_type in OQ_COMPLEX_CSV_TO_LAYER_TYPES:
self.create_save_as_shp_ckb()
self.set_ok_button()
[docs] def on_rlz_or_stat_changed(self):
self.dataset = self.npz_file[self.rlz_or_stat_cbx.currentText()]
self.set_ok_button()
[docs] def on_loss_type_changed(self):
self.set_ok_button()
[docs] def on_imt_changed(self):
self.set_ok_button()
[docs] def on_poe_changed(self):
self.set_ok_button()
[docs] def on_eid_changed(self):
self.set_ok_button()
[docs] def on_dmg_state_changed(self):
self.set_ok_button()
[docs] def get_taxonomies(self):
raise NotImplementedError()
[docs] def populate_rlz_or_stat_cbx(self):
self.rlzs_or_stats = [key for key in sorted(self.npz_file)
if key not in ('imtls', 'array')]
self.rlz_or_stat_cbx.clear()
self.rlz_or_stat_cbx.setEnabled(True)
self.rlz_or_stat_cbx.addItems(self.rlzs_or_stats)
[docs] def populate_loss_type_cbx(self, loss_types):
self.loss_type_cbx.clear()
self.loss_type_cbx.setEnabled(True)
self.loss_type_cbx.addItems(loss_types)
[docs] def show_num_sites(self):
# NOTE: we are assuming all realizations have the same number of sites,
# which currently is always true.
# If different realizations have a different number of sites, we
# need to move this block of code inside on_rlz_or_stat_changed()
rlz_or_stat_data = self.npz_file[self.rlz_or_stat_cbx.currentText()]
self.num_sites_lbl.setText(
self.num_sites_msg % rlz_or_stat_data.shape)
[docs] def build_layer_name(self, *args, **kwargs):
raise NotImplementedError()
[docs] def get_field_names(self, **kwargs):
raise NotImplementedError()
[docs] def add_field_to_layer(self, field_name):
raise NotImplementedError()
[docs] def read_npz_into_layer(self, field_names, **kwargs):
raise NotImplementedError()
[docs] def load_from_npz(self):
raise NotImplementedError()
[docs] def get_investigation_time(self):
if self.output_type in ('hcurves', 'uhs', 'hmaps'):
try:
investigation_time = self.npz_file['investigation_time']
except KeyError as exc:
msg = ('investigation_time not found. It is mandatory for %s.'
' Please check if the ouptut was produced by an'
' obsolete version of the OpenQuake Engine'
' Server.') % self.output_type
log_msg(msg, level='C', message_bar=self.iface.messageBar(),
exception=exc)
else:
# We must cast to 'str' to keep numerical padding
# after saving the project
return str(investigation_time)
else:
# some outputs do not need the investigation time
return None
[docs] def build_layer(self, rlz_or_stat=None, taxonomy=None, poe=None,
loss_type=None, dmg_state=None, gsim=None, imt=None):
layer_name = self.build_layer_name(
rlz_or_stat=rlz_or_stat, taxonomy=taxonomy, poe=poe,
loss_type=loss_type, dmg_state=dmg_state, gsim=gsim, imt=imt)
field_names = self.get_field_names(
rlz_or_stat=rlz_or_stat, taxonomy=taxonomy, poe=poe,
loss_type=loss_type, dmg_state=dmg_state, imt=imt)
# create layer
self.layer = QgsVectorLayer(
"Point?crs=epsg:4326", layer_name, "memory")
for field_name in field_names:
if field_name in ['lon', 'lat']:
continue
added_field_name = self.add_field_to_layer(field_name)
if field_name != added_field_name:
if field_name == self.default_field_name:
self.default_field_name = added_field_name
# replace field_name with the actual added_field_name
field_name_idx = field_names.index(field_name)
field_names.remove(field_name)
field_names.insert(field_name_idx, added_field_name)
self.read_npz_into_layer(
field_names, rlz_or_stat=rlz_or_stat, taxonomy=taxonomy, poe=poe,
loss_type=loss_type, dmg_state=dmg_state, imt=imt)
if (self.output_type == 'dmg_by_asset' and
not self.aggregate_by_site_ckb.isChecked()):
self.layer.setCustomProperty('output_type', 'recovery_curves')
else:
self.layer.setCustomProperty('output_type', self.output_type)
investigation_time = self.get_investigation_time()
if investigation_time is not None:
self.layer.setCustomProperty('investigation_time',
investigation_time)
if self.engine_version is not None:
self.layer.setCustomProperty('engine_version', self.engine_version)
irmt_version = get_irmt_version()
self.layer.setCustomProperty('irmt_version', irmt_version)
self.layer.setCustomProperty('calc_id', self.calc_id)
if poe is not None:
self.layer.setCustomProperty('poe', poe)
user_params = {'rlz_or_stat': rlz_or_stat,
'taxonomy': taxonomy,
'poe': poe,
'loss_type': loss_type,
'dmg_state': dmg_state,
'gsim': gsim,
'imt': imt}
write_metadata_to_layer(
self.drive_engine_dlg, self.output_type, self.layer, user_params)
try:
if (self.zonal_layer_cbx.currentText()
and self.zonal_layer_gbx.isChecked()):
return
except AttributeError:
# the aggregation stuff might not exist for some loaders
pass
root = QgsProject.instance().layerTreeRoot()
QgsProject.instance().addMapLayer(self.layer, False)
root.insertLayer(0, self.layer)
self.iface.setActiveLayer(self.layer)
self.iface.zoomToActiveLayer()
log_msg('Layer %s was created successfully' % layer_name, level='S',
message_bar=self.iface.messageBar())
@staticmethod
[docs] def style_maps(layer, style_by, iface, output_type='dmg_by_asset',
perils=None, add_null_class=False,
render_higher_on_top=False):
symbol = QgsSymbol.defaultSymbol(layer.geometryType())
# see properties at:
# https://qgis.org/api/qgsmarkersymbollayerv2_8cpp_source.html#l01073
symbol.setOpacity(1)
if isinstance(symbol, QgsMarkerSymbol):
# do it only for the layer with points
symbol.symbolLayer(0).setStrokeStyle(Qt.PenStyle(Qt.NoPen))
style = get_style(layer, iface.messageBar())
# this is the default, as specified in the user settings
ramp = QgsGradientColorRamp(
style['color_from'], style['color_to'])
mode = style['mode']
# in most cases, we override the user-specified setting, and use
# instead a setting that was required by scientists
if output_type in OQ_TO_LAYER_TYPES:
default_qgs_style = QgsStyle().defaultStyle()
default_color_ramp_names = default_qgs_style.colorRampNames()
if output_type in ('dmg_by_asset',
'losses_by_asset',
'avg_losses-stats',):
# options are EqualInterval, Quantile, Jenks, StdDev, Pretty
# jenks = natural breaks
mode = QgsGraduatedSymbolRenderer.Jenks
ramp_type_idx = default_color_ramp_names.index('Reds')
symbol.setColor(QColor(RAMP_EXTREME_COLORS['Reds']['top']))
inverted = False
elif output_type in ('hmaps', 'gmf_data', 'ruptures'):
# options are EqualInterval, Quantile, Jenks, StdDev, Pretty
# jenks = natural breaks
if output_type == 'ruptures':
mode = QgsGraduatedSymbolRenderer.Pretty
else:
mode = QgsGraduatedSymbolRenderer.EqualInterval
ramp_type_idx = default_color_ramp_names.index('Spectral')
inverted = True
symbol.setColor(QColor(RAMP_EXTREME_COLORS['Reds']['top']))
elif output_type in ['asset_risk', 'input']:
# options are EqualInterval, Quantile, Jenks, StdDev, Pretty
# jenks = natural breaks
mode = QgsGraduatedSymbolRenderer.EqualInterval
# exposure_strings = ['number', 'occupants', 'value']
# setting exposure colors by default
colors = {'single': RAMP_EXTREME_COLORS['Blues']['top'],
'ramp_name': 'Blues'}
inverted = False
if output_type == 'asset_risk':
damage_strings = perils
for damage_string in damage_strings:
if damage_string in style_by:
colors = {'single': RAMP_EXTREME_COLORS[
'Spectral']['top'],
'ramp_name': 'Spectral'}
inverted = True
break
else: # 'input'
colors = {'single': RAMP_EXTREME_COLORS['Greens']['top'],
'ramp_name': 'Greens'}
symbol.symbolLayer(0).setShape(
QgsSimpleMarkerSymbolLayerBase.Square)
single_color = colors['single']
ramp_name = colors['ramp_name']
ramp_type_idx = default_color_ramp_names.index(ramp_name)
symbol.setColor(QColor(single_color))
else:
raise NotImplementedError(
'Undefined color ramp for output type %s' % output_type)
ramp = default_qgs_style.colorRamp(
default_color_ramp_names[ramp_type_idx])
if inverted:
ramp.invert()
# get unique values
fni = layer.fields().indexOf(style_by)
unique_values = layer.dataProvider().uniqueValues(fni)
num_unique_values = len(unique_values - {NULL})
if num_unique_values > 2:
renderer = QgsGraduatedSymbolRenderer.createRenderer(
layer,
QgsExpression.quotedColumnRef(style_by),
min(num_unique_values, style['classes']),
mode,
symbol.clone(),
ramp)
label_format = renderer.labelFormat()
# label_format.setTrimTrailingZeroes(True) # it might be useful
label_format.setPrecision(2)
renderer.setLabelFormat(label_format, updateRanges=True)
elif num_unique_values == 2:
categories = []
for unique_value in unique_values:
symbol = symbol.clone()
try:
symbol.setColor(QColor(RAMP_EXTREME_COLORS[ramp_name][
'bottom' if unique_value == min(unique_values)
else 'top']))
except Exception:
symbol.setColor(QColor(
style['color_from']
if unique_value == min(unique_values)
else style['color_to']))
category = QgsRendererCategory(
unique_value, symbol, str(unique_value))
# entry for the list of category items
categories.append(category)
renderer = QgsCategorizedSymbolRenderer(
QgsExpression.quotedColumnRef(style_by), categories)
else:
renderer = QgsSingleSymbolRenderer(symbol.clone())
if add_null_class and NULL in unique_values:
# add a class for NULL values
rule_renderer = QgsRuleBasedRenderer(symbol.clone())
root_rule = rule_renderer.rootRule()
not_null_rule = root_rule.children()[0].clone()
# strip parentheses from stringified color HSL
not_null_rule.setFilterExpression(
'%s IS NOT NULL' % QgsExpression.quotedColumnRef(style_by))
not_null_rule.setLabel('%s:' % style_by)
root_rule.appendChild(not_null_rule)
null_rule = root_rule.children()[0].clone()
null_rule.setSymbol(QgsFillSymbol.createSimple(
{'color': '160,160,160', 'style': 'diagonal_x'}))
null_rule.setFilterExpression(
'%s IS NULL' % QgsExpression.quotedColumnRef(style_by))
null_rule.setLabel(tr('No points'))
root_rule.appendChild(null_rule)
if isinstance(renderer, QgsGraduatedSymbolRenderer):
# create value ranges
rule_renderer.refineRuleRanges(not_null_rule, renderer)
# remove default rule
elif isinstance(renderer, QgsCategorizedSymbolRenderer):
rule_renderer.refineRuleCategoris(not_null_rule, renderer)
for rule in rule_renderer.rootRule().children()[1].children():
label = rule.label()
# by default, labels are like:
# ('"collapse-structural-ASH_DRY_sum" >= 0.0000 AND
# "collapse-structural-ASH_DRY_sum" <= 2.3949')
first, second = label.split(" AND ")
bottom = first.rsplit(" ", 1)[1]
top = second.rsplit(" ", 1)[1]
simplified = "%s - %s" % (bottom, top)
rule.setLabel(simplified)
root_rule.removeChildAt(0)
renderer = rule_renderer
if render_higher_on_top:
renderer.setUsingSymbolLevels(True)
symbol_items = [item for item in renderer.legendSymbolItems()]
for i in range(len(symbol_items)):
sym = symbol_items[i].symbol().clone()
key = symbol_items[i].ruleKey()
for lay in range(sym.symbolLayerCount()):
sym.symbolLayer(lay).setRenderingPass(i)
renderer.setLegendSymbolItem(key, sym)
layer.setRenderer(renderer)
layer.setOpacity(0.7)
layer.triggerRepaint()
iface.setActiveLayer(layer)
iface.zoomToActiveLayer()
log_msg('Layer %s was created successfully' % layer.name(), level='S',
message_bar=iface.messageBar())
# NOTE QGIS3: probably not needed
# iface.layerTreeView().refreshLayerSymbology(layer.id())
iface.mapCanvas().refresh()
[docs] def style_categorized(self, layer=None, style_by=None):
if layer is None:
layer = self.layer
if style_by is None:
style_by = self.default_field_name
# get unique values
fni = layer.fields().indexOf(style_by)
unique_values = layer.dataProvider().uniqueValues(fni)
# define categories
categories = []
for unique_value in unique_values:
# initialize the default symbol for this geometry type
symbol = QgsSymbol.defaultSymbol(layer.geometryType())
# configure a symbol layer
layer_style = {}
layer_style['color'] = '%d, %d, %d' % (
randrange(0, 256), randrange(0, 256), randrange(0, 256))
layer_style['outline'] = '#000000'
symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style)
# replace default symbol layer with the configured one
if symbol_layer is not None:
symbol.changeSymbolLayer(0, symbol_layer)
# create renderer object
category = QgsRendererCategory(
unique_value, symbol, str(unique_value))
# entry for the list of category items
categories.append(category)
# create renderer object
renderer = QgsCategorizedSymbolRenderer(
QgsExpression.quotedColumnRef(style_by), categories)
# assign the created renderer to the layer
if renderer is not None:
layer.setRenderer(renderer)
layer.triggerRepaint()
# NOTE QGIS3: probably not needed
# self.iface.layerTreeView().refreshLayerSymbology(layer.id())
self.iface.mapCanvas().refresh()
[docs] def style_curves(self):
registry = QgsApplication.symbolLayerRegistry()
cross = registry.symbolLayerMetadata("SimpleMarker").createSymbolLayer(
{'name': 'cross2', 'color': '0,0,0', 'color_border': '0,0,0',
'offset': '0,0', 'size': '1.5', 'angle': '0'})
symbol = QgsSymbol.defaultSymbol(self.layer.geometryType())
symbol.deleteSymbolLayer(0)
symbol.appendSymbolLayer(cross)
renderer = QgsSingleSymbolRenderer(symbol)
effect = QgsOuterGlowEffect()
effect.setSpread(0.5)
effect.setOpacity(1)
effect.setColor(QColor(255, 255, 255))
effect.setBlurLevel(1)
renderer.paintEffect().appendEffect(effect)
renderer.paintEffect().setEnabled(True)
self.layer.setRenderer(renderer)
self.layer.setOpacity(0.7)
self.layer.triggerRepaint()
# NOTE QGIS3: probably not needed
# self.iface.layerTreeView().refreshLayerSymbology(self.layer.id())
self.iface.mapCanvas().refresh()
[docs] def open_load_zonal_layer_dialog(self):
"""
Open a file dialog to select the zonal layer to be loaded
:returns: the zonal layer
"""
text = self.tr('Select zonal layer to import')
filters = self.tr('All files (*.*);;'
'GeoPackages (*.gpkg);;'
'Vector shapefiles (*.shp);;'
'SQLite (*.sqlite);;'
)
default_dir = QSettings().value('irmt/select_layer_dir',
QDir.homePath())
file_name, _ = QFileDialog.getOpenFileName(
self, text, default_dir, filters)
if not file_name:
return None
selected_dir = QFileInfo(file_name).dir().path()
QSettings().setValue('irmt/select_layer_dir', selected_dir)
zonal_layer = self.load_zonal_layer(file_name)
return zonal_layer
[docs] def load_zonal_layer(self, zonal_layer_path):
self.added_zonal_layer = zonal_layer = None
zonal_layer_basename, zonal_layer_ext = os.path.splitext(
os.path.basename(zonal_layer_path))
if zonal_layer_ext == '.gpkg':
dlg = QgsSublayersDialog(
QgsSublayersDialog.Ogr, 'Select zonal layer')
conn = ogr.Open(zonal_layer_path)
layer_defs = []
for idx, c in enumerate(conn):
ld = QgsSublayersDialog.LayerDefinition()
ld.layerId = idx
ld.layerName = c.GetDescription()
ld.count = c.GetFeatureCount()
ld.type = ogr.GeometryTypeToName(c.GetGeomType())
layer_defs.append(ld)
dlg.populateLayerTable(layer_defs)
dlg.exec_()
if not dlg.selection():
return None
for sel in dlg.selection():
# NOTE: the last one will be chosen as zonal layer
zonal_layer = QgsVectorLayer(
zonal_layer_path + "|layername=" + sel.layerName,
sel.layerName, 'ogr')
if zonal_layer.isValid():
root = QgsProject.instance().layerTreeRoot()
QgsProject.instance().addMapLayer(zonal_layer, False)
root.insertLayer(0, zonal_layer)
else:
msg = 'Invalid layer'
log_msg(msg, level='C',
message_bar=self.iface.messageBar())
return None
else:
zonal_layer = QgsVectorLayer(
zonal_layer_path, zonal_layer_basename, 'ogr')
if not zonal_layer.geometryType() == QgsWkbTypes.PolygonGeometry:
msg = 'Zonal layer must contain zone polygons'
log_msg(msg, level='C', message_bar=self.iface.messageBar())
return None
if zonal_layer_ext != '.gpkg':
# Add zonal layer to registry
if zonal_layer.isValid():
root = QgsProject.instance().layerTreeRoot()
QgsProject.instance().addMapLayer(zonal_layer, False)
root.insertLayer(0, zonal_layer)
else:
msg = 'Invalid zonal layer'
log_msg(msg, level='C', message_bar=self.iface.messageBar())
return None
self.added_zonal_layer = zonal_layer
self.pre_populate_zonal_layer_cbx()
return zonal_layer
[docs] def populate_zonal_layer_cbx(self, zonal_layer):
cbx = self.zonal_layer_cbx
cbx.addItem(zonal_layer.name())
last_index = cbx.count() - 1
cbx.setItemData(last_index, zonal_layer.id())
cbx.setCurrentIndex(last_index)
[docs] def show_file_size(self):
file_size = get_file_size(self.path)
self.file_size_lbl.setText(self.file_size_msg % file_size)
[docs] def accept(self):
try:
self.iface.layerTreeView().currentLayerChanged.disconnect(
self.on_currentLayerChanged)
except Exception:
# it's connected only for some loaders
pass
self.hide()
if self.output_type in OQ_EXTRACT_TO_LAYER_TYPES:
self.load_from_npz()
if self.output_type in ('losses_by_asset',
'dmg_by_asset',
'avg_losses-stats'):
# check if also aggregating by zone or not
if (not self.zonal_layer_cbx.currentText() or
not self.zonal_layer_gbx.isChecked()):
super().accept()
return
self.aggregate_by_zone()
else:
super().accept()
elif self.output_type in OQ_CSV_TO_LAYER_TYPES:
self.load_from_csv()
super().accept()
[docs] def aggregate_by_zone(self):
loss_layer = self.layer
zonal_layer_id = self.zonal_layer_cbx.itemData(
self.zonal_layer_cbx.currentIndex())
zonal_layer = QgsProject.instance().mapLayer(
zonal_layer_id)
QgsProject.instance().layerTreeRoot().findLayer(
zonal_layer.id()).setItemVisibilityChecked(False)
# if the two layers have different projections, display a
# warning, but try proceeding anyway
have_same_projection, check_projection_msg = ProcessLayer(
loss_layer).has_same_projection_as(zonal_layer)
if not have_same_projection:
log_msg(check_projection_msg, level='W',
message_bar=self.iface.messageBar())
try:
[self.loss_attr_name] = [
field.name() for field in loss_layer.fields()]
except ValueError:
self.loss_attr_name = self.default_field_name
zonal_layer_plus_sum_name = "%s: %s_sum" % (
zonal_layer.name(), self.loss_attr_name)
discard_nonmatching = self.discard_nonmatching_chk.isChecked()
try:
calculate_zonal_stats(
self.on_calculate_zonal_stats_completed,
zonal_layer, loss_layer, [self.loss_attr_name],
zonal_layer_plus_sum_name,
discard_nonmatching=discard_nonmatching,
predicates=('intersects',), summaries=('sum',))
except Exception as exc:
log_msg(str(exc), level='C',
message_bar=self.iface.messageBar(),
exception=exc)
[docs] def on_calculate_zonal_stats_completed(self, zonal_layer_plus_sum):
if zonal_layer_plus_sum is None:
msg = 'The calculation of zonal statistics was not completed'
log_msg(msg, level='C', message_bar=self.iface.messageBar())
return None
# Add zonal layer to registry
if zonal_layer_plus_sum.isValid():
root = QgsProject.instance().layerTreeRoot()
QgsProject.instance().addMapLayer(zonal_layer_plus_sum, False)
root.insertLayer(0, zonal_layer_plus_sum)
else:
msg = 'The layer aggregating data by zone is invalid.'
log_msg(msg, level='C', message_bar=self.iface.messageBar())
return None
# NOTE: in scenario damage, keys are like
# u'structural_no_damage_mean', and not just
# u'structural', therefore we can't just use the selected
# loss type, but we must use the actual only key in the
# dict
added_loss_attr = "%s_sum" % self.loss_attr_name
style_by = added_loss_attr
try:
perils = self.perils
except AttributeError:
perils = None
self.style_maps(zonal_layer_plus_sum, style_by,
self.iface, self.output_type,
perils=perils,
add_null_class=True)
super().accept()
[docs] def reject(self):
try:
self.iface.layerTreeView().currentLayerChanged.disconnect(
self.on_currentLayerChanged)
except Exception:
# it's connected only for some loaders
pass
if (hasattr(self, 'npz_file') and self.npz_file is not None
and self.output_type in OQ_TO_LAYER_TYPES):
self.npz_file.close()
super().reject()