# coding=utf-8
"""
InaSAFE Disaster risk assessment tool by AusAid **GUI InaSAFE Wizard Dialog.**
Contact : [email protected]
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.. todo:: Check raster is single band
"""
__author__ = '[email protected]'
__revision__ = '$Format:%H$'
__date__ = '21/02/2011'
__copyright__ = ('Copyright 2012, Australia Indonesia Facility for '
'Disaster Reduction')
import os
import logging
import re
import json
from collections import OrderedDict
from sqlite3 import OperationalError
from osgeo import gdal
from osgeo.gdalconst import GA_ReadOnly
import numpy
from qgis.core import (
QgsCoordinateTransform,
QgsBrowserModel,
QgsDataItem,
QgsVectorLayer,
QgsRasterLayer,
QgsDataSourceURI,
QgsMapLayerRegistry)
# noinspection PyPackageRequirements
from PyQt4 import QtGui, QtCore
# noinspection PyPackageRequirements
from PyQt4.QtCore import pyqtSignature, QSettings, QPyNullVariant
# noinspection PyPackageRequirements
from PyQt4.QtGui import (
QDialog,
QListWidgetItem,
QPixmap,
QSortFilterProxyModel)
# pylint: disable=F0401
from db_manager.db_plugins.postgis.connector import PostGisDBConnector
# pylint: enable=F0401
# pylint: disable=unused-import
# TODO: to get rid of the following import,
# TODO: we need to get rid of all those evals...TS
from safe import definitions
# pylint: enable=unused-import
from safe.definitions import (
global_default_attribute,
do_not_use_attribute,
continuous_hazard_unit,
exposure_unit,
raster_hazard_classification,
vector_hazard_classification,
layer_purpose_hazard,
layer_purpose_exposure,
layer_purpose_aggregation,
hazard_category_single_event,
layer_geometry_point,
layer_geometry_line,
layer_geometry_polygon,
layer_geometry_raster,
layer_mode_continuous,
layer_mode_classified)
from safe.impact_functions.impact_function_manager import ImpactFunctionManager
from safe.utilities.keyword_io import KeywordIO
from safe.utilities.analysis_handler import AnalysisHandler
from safe.utilities.gis import (
is_raster_layer,
is_point_layer,
is_polygon_layer,
layer_attribute_names)
from safe.utilities.utilities import get_error_message, compare_version
from safe.defaults import get_defaults
from safe.common.exceptions import (
HashNotFoundError,
NoKeywordsFoundError,
KeywordNotFoundError,
InvalidParameterError,
UnsupportedProviderError,
InsufficientOverlapError,
InaSAFEError)
from safe.common.resource_parameter import ResourceParameter
from safe.common.version import get_version
from safe_extras.parameters.group_parameter import GroupParameter
from safe.utilities.resources import get_ui_class, resources_path
from safe.gui.tools.function_options_dialog import (
FunctionOptionsDialog)
from safe.utilities.unicode import get_unicode
from safe.utilities.i18n import tr
from safe.gui.tools.wizard_strings import (
category_question,
category_question_hazard,
category_question_exposure,
category_question_aggregation,
hazard_category_question,
layermode_raster_question,
layermode_vector_question,
unit_question,
allow_resampling_question,
field_question_subcategory_unit,
field_question_subcategory_classified,
field_question_aggregation,
classification_question,
classify_vector_question,
classify_raster_question,
select_function_constraints2_question,
select_function_question,
select_hazard_origin_question,
select_hazlayer_from_canvas_question,
select_hazlayer_from_browser_question,
select_exposure_origin_question,
select_explayer_from_canvas_question,
select_explayer_from_browser_question,
create_postGIS_connection_first)
# TODO(Ismail): We need a better way to import all of these string
# pylint: disable=unused-import
from safe.gui.tools.wizard_strings import (
earthquake_mmi_question,
exposure_question,
flood_feet_depth_question,
flood_metres_depth_question,
flood_wetdry_question,
hazard_question,
population_density_question,
population_number_question,
road_road_type_question,
structure_building_type_question,
tephra_kgm2_question,
tsunami_feet_depth_question,
tsunami_metres_depth_question,
tsunami_wetdry_question,
volcano_volcano_categorical_question
)
# pylint: enable=unused-import
LOGGER = logging.getLogger('InaSAFE')
FORM_CLASS = get_ui_class('wizard_dialog_base.ui')
# Constants: tab numbers for steps
step_kw_category = 1
step_kw_subcategory = 2
step_kw_hazard_category = 3
step_kw_layermode = 4
step_kw_unit = 5
step_kw_classification = 6
step_kw_field = 7
step_kw_resample = 8
step_kw_classify = 9
step_kw_extrakeywords = 10
step_kw_aggregation = 11
step_kw_source = 12
step_kw_title = 13
step_fc_function_1 = 14
step_fc_function_2 = 15
step_fc_function_3 = 16
step_fc_hazlayer_origin = 17
step_fc_hazlayer_from_canvas = 18
step_fc_hazlayer_from_browser = 19
step_fc_explayer_origin = 20
step_fc_explayer_from_canvas = 21
step_fc_explayer_from_browser = 22
step_fc_disjoint_layers = 23
step_fc_agglayer_origin = 24
step_fc_agglayer_from_canvas = 25
step_fc_agglayer_from_browser = 26
step_fc_agglayer_disjoint = 27
step_fc_extent = 28
step_fc_extent_disjoint = 29
step_fc_params = 30
step_fc_summary = 31
step_fc_analysis = 32
# Aggregations' keywords
DEFAULTS = get_defaults()
female_ratio_attribute_key = DEFAULTS['FEMALE_RATIO_ATTR_KEY']
female_ratio_default_key = DEFAULTS['FEMALE_RATIO_KEY']
youth_ratio_attribute_key = DEFAULTS['YOUTH_RATIO_ATTR_KEY']
youth_ratio_default_key = DEFAULTS['YOUTH_RATIO_KEY']
adult_ratio_attribute_key = DEFAULTS['ADULT_RATIO_ATTR_KEY']
adult_ratio_default_key = DEFAULTS['ADULT_RATIO_KEY']
elderly_ratio_attribute_key = DEFAULTS['ELDERLY_RATIO_ATTR_KEY']
elderly_ratio_default_key = DEFAULTS['ELDERLY_RATIO_KEY']
# Data roles
RoleFunctions = QtCore.Qt.UserRole
RoleHazard = QtCore.Qt.UserRole + 1
RoleExposure = QtCore.Qt.UserRole + 2
RoleHazardConstraint = QtCore.Qt.UserRole + 3
RoleExposureConstraint = QtCore.Qt.UserRole + 4
[docs]def get_question_text(constant):
"""Find a constant by name and return its value.
:param constant: The name of the constant to look for.
:type constant: string
:returns: The value of the constant or red error message.
:rtype: string
"""
try:
# TODO Eval = bad
return eval(constant) # pylint: disable=eval-used
except NameError:
return '<b>MISSING CONSTANT: %s</b>' % constant
[docs]class LayerBrowserProxyModel(QSortFilterProxyModel):
"""Proxy model for hiding unsupported branches in the layer browser."""
def __init__(self, parent):
"""Constructor for the model.
:param parent: Parent widget of this model.
:type parent: QWidget
"""
QSortFilterProxyModel.__init__(self, parent)
[docs] def filterAcceptsRow(self, source_row, source_parent):
"""The filter method
.. note:: This filter hides top-level items of unsupported branches
and also leaf items containing xml files.
Enabled root items: QgsDirectoryItem, QgsFavouritesItem,
QgsPGRootItem.
Disabled root items: QgsMssqlRootItem, QgsSLRootItem,
QgsOWSRootItem, QgsWCSRootItem, QgsWFSRootItem, QgsWMSRootItem.
Disabled leaf items: QgsLayerItem and QgsOgrLayerItem with path
ending with '.xml'
:param source_row: Parent widget of the model
:type source_row: int
:param source_parent: Parent item index
:type source_parent: QModelIndex
:returns: Item validation result
:rtype: bool
"""
source_index = self.sourceModel().index(source_row, 0, source_parent)
item = self.sourceModel().dataItem(source_index)
if item.metaObject().className() in [
'QgsMssqlRootItem',
'QgsSLRootItem',
'QgsOWSRootItem',
'QgsWCSRootItem',
'QgsWFSRootItem',
'QgsWMSRootItem']:
return False
if (item.metaObject().className() in [
'QgsLayerItem',
'QgsOgrLayerItem'] and
item.path().endswith('.xml')):
return False
return True
[docs]class WizardDialog(QDialog, FORM_CLASS):
"""Dialog implementation class for the InaSAFE wizard."""
def __init__(self, parent=None, iface=None, dock=None):
"""Constructor for the dialog.
.. note:: In QtDesigner the advanced editor's predefined keywords
list should be shown in english always, so when adding entries to
cboKeyword, be sure to choose :safe_qgis:`Properties<<` and untick
the :safe_qgis:`translatable` property.
:param parent: Parent widget of this dialog.
:type parent: QWidget
:param iface: QGIS QGisAppInterface instance.
:type iface: QGisAppInterface
:param dock: Dock widget instance that we can notify of changes to
the keywords. Optional.
:type dock: Dock
"""
QDialog.__init__(self, parent)
self.setupUi(self)
self.setWindowTitle('InaSAFE')
# Constants
self.keyword_creation_wizard_name = 'InaSAFE Keywords Creation Wizard'
self.ifcw_name = 'InaSAFE Impact Function Centric Wizard'
# Note the keys should remain untranslated as we need to write
# english to the keywords file.
# Save reference to the QGIS interface and parent
self.iface = iface
self.parent = parent
self.dock = dock
self.suppress_warning_dialog = False
self.set_tool_tip()
# Set icons
self.lblMainIcon.setPixmap(
QPixmap(resources_path('img', 'icons', 'icon-white.svg')))
self.lblIconDisjoint_1.setPixmap(
QPixmap(resources_path('img', 'wizard', 'icon-stop.svg')))
self.lblIconDisjoint_2.setPixmap(
QPixmap(resources_path('img', 'wizard', 'icon-stop.svg')))
self.lblIconDisjoint_3.setPixmap(
QPixmap(resources_path('img', 'wizard', 'icon-stop.svg')))
# Set models for browsers
browser_model = QgsBrowserModel()
proxy_model = LayerBrowserProxyModel(self)
proxy_model.setSourceModel(browser_model)
self.tvBrowserHazard.setModel(proxy_model)
browser_model = QgsBrowserModel()
proxy_model = LayerBrowserProxyModel(self)
proxy_model.setSourceModel(browser_model)
self.tvBrowserExposure.setModel(proxy_model)
browser_model = QgsBrowserModel()
proxy_model = LayerBrowserProxyModel(self)
proxy_model.setSourceModel(browser_model)
self.tvBrowserAggregation.setModel(proxy_model)
self.parameter_dialog = None
self.extent_dialog = None
self.keyword_io = KeywordIO()
self.twParams = None
self.swExtent = None
self.is_selected_layer_keywordless = False
self.parent_step = None
self.pbnBack.setEnabled(False)
self.pbnNext.setEnabled(False)
# Collect some serial widgets
self.extra_keywords_widgets = [
{'cbo': self.cboExtraKeyword1, 'lbl': self.lblExtraKeyword1},
{'cbo': self.cboExtraKeyword2, 'lbl': self.lblExtraKeyword2},
{'cbo': self.cboExtraKeyword3, 'lbl': self.lblExtraKeyword3},
{'cbo': self.cboExtraKeyword4, 'lbl': self.lblExtraKeyword4},
{'cbo': self.cboExtraKeyword5, 'lbl': self.lblExtraKeyword5},
{'cbo': self.cboExtraKeyword6, 'lbl': self.lblExtraKeyword6},
{'cbo': self.cboExtraKeyword7, 'lbl': self.lblExtraKeyword7},
{'cbo': self.cboExtraKeyword8, 'lbl': self.lblExtraKeyword8}
]
for ekw in self.extra_keywords_widgets:
ekw['key'] = None
ekw['slave_key'] = None
# noinspection PyUnresolvedReferences
self.tvBrowserHazard.selectionModel().selectionChanged.connect(
self.tvBrowserHazard_selection_changed)
self.tvBrowserExposure.selectionModel().selectionChanged.connect(
self.tvBrowserExposure_selection_changed)
self.tvBrowserAggregation.selectionModel().selectionChanged.connect(
self.tvBrowserAggregation_selection_changed)
self.treeClasses.itemChanged.connect(self.update_dragged_item_flags)
self.pbnCancel.released.connect(self.reject)
# string constants
self.global_default_string = global_default_attribute['name']
self.global_default_data = global_default_attribute['id']
self.do_not_use_string = do_not_use_attribute['name']
self.do_not_use_data = do_not_use_attribute['id']
self.defaults = get_defaults()
# Initialize attributes
self.impact_function_manager = ImpactFunctionManager()
self.existing_keywords = None
self.layer = None
self.hazard_layer = None
self.exposure_layer = None
self.aggregation_layer = None
self.if_params = None
self.analysis_handler = None
[docs] def set_mode_label_to_keywords_creation(self):
"""Set the mode label to the Keywords Creation/Update mode
"""
self.setWindowTitle(self.keyword_creation_wizard_name)
if self.get_existing_keyword('layer_purpose'):
mode_name = (self.tr(
'Keywords update wizard for layer <b>%s</b>'
) % self.layer.name())
else:
mode_name = (self.tr(
'Keywords creation wizard for layer <b>%s</b>'
) % self.layer.name())
self.lblSubtitle.setText(mode_name)
[docs] def set_mode_label_to_ifcw(self):
"""Set the mode label to the IFCW
"""
self.setWindowTitle(self.ifcw_name)
self.lblSubtitle.setText(self.tr(
'Use this wizard to run a guided impact assessment'))
[docs] def set_keywords_creation_mode(self, layer=None):
"""Set the Wizard to the Keywords Creation mode
:param layer: Layer to set the keywords for
:type layer: QgsMapLayer
"""
self.layer = layer or self.iface.mapCanvas().currentLayer()
try:
self.existing_keywords = self.keyword_io.read_keywords(self.layer)
# if 'layer_purpose' not in self.existing_keywords:
# self.existing_keywords = None
except (HashNotFoundError,
OperationalError,
NoKeywordsFoundError,
KeywordNotFoundError,
InvalidParameterError,
UnsupportedProviderError):
self.existing_keywords = None
self.set_mode_label_to_keywords_creation()
self.set_widgets_step_kw_category()
self.go_to_step(step_kw_category)
[docs] def set_function_centric_mode(self):
"""Set the Wizard to the Function Centric mode"""
self.set_mode_label_to_ifcw()
new_step = step_fc_function_1
self.set_widgets_step_fc_function_1()
self.pbnNext.setEnabled(self.is_ready_to_next_step(new_step))
self.go_to_step(new_step)
[docs] def update_MessageViewer_size(self):
"""Update maximumHeight size of the MessageViewer to fit its parent tab
This is a workaround for a bug that makes MessageViewer
flooding up to maximumHeight on Windows.
"""
self.wvResults.setMaximumHeight(self.pgF25Progress.height() - 90)
# pylint: disable=unused-argument
[docs] def resizeEvent(self, ev):
"""Trigger MessageViewer size update on window resize
.. note:: This is an automatic Qt slot
executed when the window size changes.
"""
pass
# self.update_MessageViewer_size()
# pylint: disable=unused-argument
[docs] def purposes_for_layer(self):
"""Return a list of valid purposes for the current layer.
:returns: A list where each value represents a valid purpose.
:rtype: list
"""
layer_geometry_id = self.get_layer_geometry_id()
return self.impact_function_manager.purposes_for_layer(
layer_geometry_id)
[docs] def subcategories_for_layer(self):
"""Return a list of valid subcategories for a layer.
Subcategory is hazard type or exposure type.
:returns: A list where each value represents a valid subcategory.
:rtype: list
"""
purpose = self.selected_category()
layer_geometry_id = self.get_layer_geometry_id()
if purpose == layer_purpose_hazard:
return self.impact_function_manager.hazards_for_layer(
layer_geometry_id)
elif purpose == layer_purpose_exposure:
return self.impact_function_manager.exposures_for_layer(
layer_geometry_id)
[docs] def hazard_categories_for_layer(self):
"""Return a list of valid hazard categories for a layer.
:returns: A list where each value represents a valid hazard category.
:rtype: list
"""
layer_geometry_id = self.get_layer_geometry_id()
if self.selected_category() != layer_purpose_hazard:
return []
hazard_type_id = self.selected_subcategory()['key']
return self.impact_function_manager.hazard_categories_for_layer(
layer_geometry_id, hazard_type_id)
[docs] def layermodes_for_layer(self):
"""Return a list of valid layer modes for a layer.
:returns: A list where each value represents a valid layer mode.
:rtype: list
"""
purpose = self.selected_category()
subcategory = self.selected_subcategory()
layer_geometry_id = self.get_layer_geometry_id()
if purpose == layer_purpose_hazard:
hazard_category = self.selected_hazard_category()
return self.impact_function_manager.available_hazard_layer_modes(
subcategory['key'], layer_geometry_id, hazard_category['key'])
elif purpose == layer_purpose_exposure:
return self.impact_function_manager.available_exposure_layer_modes(
subcategory['key'], layer_geometry_id)
[docs] def classifications_for_layer(self):
"""Return a list of valid classifications for a layer.
:returns: A list where each value represents a valid classification.
:rtype: list
"""
layer_geometry_id = self.get_layer_geometry_id()
layer_mode_id = self.selected_layermode()['key']
subcategory_id = self.selected_subcategory()['key']
if self.selected_category() == layer_purpose_hazard:
hazard_category_id = self.selected_hazard_category()['key']
if is_raster_layer(self.layer):
return self.impact_function_manager.\
raster_hazards_classifications_for_layer(
subcategory_id,
layer_geometry_id,
layer_mode_id,
hazard_category_id)
else:
return self.impact_function_manager\
.vector_hazards_classifications_for_layer(
subcategory_id,
layer_geometry_id,
layer_mode_id,
hazard_category_id)
else:
# There are no classifications for exposures defined yet
return []
[docs] def additional_keywords_for_the_layer(self):
"""Return a list of valid additional keywords for the current layer.
:returns: A list where each value represents a valid additional kw.
:rtype: list
"""
layer_geometry_key = self.get_layer_geometry_id()
layer_mode_key = self.selected_layermode()['key']
if self.selected_category() == layer_purpose_hazard:
hazard_category_key = self.selected_hazard_category()['key']
hazard_key = self.selected_subcategory()['key']
return self.impact_function_manager.hazard_additional_keywords(
layer_mode_key, layer_geometry_key,
hazard_category_key, hazard_key)
else:
exposure_key = self.selected_subcategory()['key']
return self.impact_function_manager.exposure_additional_keywords(
layer_mode_key, layer_geometry_key, exposure_key)
[docs] def field_keyword_for_the_layer(self):
"""Return the proper keyword for field for the current layer.
Expected values are: 'field', 'structure_class_field', road_class_field
:returns: the field keyword
:rtype: string
"""
if self.selected_category() == layer_purpose_aggregation:
# purpose: aggregation
return 'aggregation attribute'
elif self.selected_category() == layer_purpose_hazard:
# purpose: hazard
if (self.selected_layermode() == layer_mode_classified and
is_point_layer(self.layer)):
# No field for classified point hazards
return ''
else:
# purpose: exposure
layer_mode_key = self.selected_layermode()['key']
layer_geometry_key = self.get_layer_geometry_id()
exposure_key = self.selected_subcategory()['key']
exposure_class_fields = self.impact_function_manager.\
exposure_class_fields(
layer_mode_key, layer_geometry_key, exposure_key)
if exposure_class_fields and len(exposure_class_fields) == 1:
return exposure_class_fields[0]['key']
# Fallback to default
return 'field'
# ===========================
# STEP_KW_CATEGORY
# ===========================
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_lstCategories_itemSelectionChanged(self):
"""Update purpose description label.
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
# Clear all further steps in order to properly calculate the prev step
self.lstHazardCategories.clear()
self.lstSubcategories.clear()
self.lstLayerModes.clear()
self.lstUnits.clear()
self.lstFields.clear()
self.lstClassifications.clear()
# Set widgets
category = self.selected_category()
# Exit if no selection
if not category:
return
# Set description label
self.lblDescribeCategory.setText(category["description"])
self.lblIconCategory.setPixmap(QPixmap(
resources_path('img', 'wizard', 'keyword-category-%s.svg'
% (category['key'] or 'notset'))))
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_category(self):
"""Obtain the layer purpose selected by user.
:returns: Metadata of the selected layer purpose.
:rtype: dict, None
"""
item = self.lstCategories.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
[docs] def on_lstSubcategories_itemSelectionChanged(self):
"""Update subcategory description label.
.. note:: This is an automatic Qt slot
executed when the subcategory selection changes.
"""
# Clear all further steps in order to properly calculate the prev step
self.lstHazardCategories.clear()
self.lstLayerModes.clear()
self.lstUnits.clear()
self.lstFields.clear()
self.lstClassifications.clear()
# Set widgets
subcategory = self.selected_subcategory()
# Exit if no selection
if not subcategory:
return
# Set description label
self.lblDescribeSubcategory.setText(subcategory['description'])
icon_path = resources_path('img', 'wizard',
'keyword-subcategory-%s.svg'
% (subcategory['key'] or 'notset'))
if not os.path.exists(icon_path):
category = self.selected_category()
icon_path = resources_path('img', 'wizard',
'keyword-category-%s.svg'
% (category['key']))
self.lblIconSubcategory.setPixmap(QPixmap(icon_path))
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_subcategory(self):
"""Obtain the subcategory selected by user.
:returns: Metadata of the selected subcategory.
:rtype: dict, None
"""
item = self.lstSubcategories.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
@pyqtSignature('')
[docs] def on_lstHazardCategories_itemSelectionChanged(self):
"""Update hazard category description label.
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
# Clear all further steps in order to properly calculate the prev step
self.lstLayerModes.clear()
self.lstUnits.clear()
self.lstFields.clear()
self.lstClassifications.clear()
# Set widgets
hazard_category = self.selected_hazard_category()
# Exit if no selection
if not hazard_category:
return
# Set description label
self.lblDescribeHazardCategory.setText(hazard_category["description"])
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_hazard_category(self):
"""Obtain the hazard category selected by user.
:returns: Metadata of the selected hazard category.
:rtype: dict, None
"""
item = self.lstHazardCategories.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
[docs] def on_lstLayerModes_itemSelectionChanged(self):
"""Update layer mode description label and unit widgets.
.. note:: This is an automatic Qt slot
executed when the subcategory selection changes.
"""
# Clear all further steps in order to properly calculate the prev step
self.lstUnits.clear()
self.lstFields.clear()
self.lstClassifications.clear()
# Set widgets
layer_mode = self.selected_layermode()
# Exit if no selection
if not layer_mode:
self.lblDescribeLayerMode.setText('')
return
# Set description label
self.lblDescribeLayerMode.setText(layer_mode['description'])
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_layermode(self):
"""Obtain the layer mode selected by user.
:returns: selected layer mode.
:rtype: string, None
"""
item = self.lstLayerModes.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
[docs] def on_lstUnits_itemSelectionChanged(self):
"""Update unit description label and field widgets.
.. note:: This is an automatic Qt slot
executed when the unit selection changes.
"""
# Clear all further steps in order to properly calculate the prev step
self.lstFields.clear()
self.lstClassifications.clear()
# Set widgets
unit = self.selected_unit()
# Exit if no selection
if not unit:
return
self.lblDescribeUnit.setText(unit['description'])
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_unit(self):
"""Obtain the unit selected by user.
:returns: Metadata of the selected unit.
:rtype: dict, None
"""
item = self.lstUnits.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
[docs] def on_lstClassifications_itemSelectionChanged(self):
"""Update classification description label and unlock the Next button.
.. note:: This is an automatic Qt slot
executed when the field selection changes.
"""
self.lstFields.clear()
self.treeClasses.clear()
classification = self.selected_classification()
# Exit if no selection
if not classification:
return
# Set description label
self.lblDescribeClassification.setText(classification["description"])
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_classification(self):
"""Obtain the classification selected by user.
:returns: Metadata of the selected classification.
:rtype: dict, None
"""
item = self.lstClassifications.currentItem()
try:
# pylint: disable=eval-used
return eval(item.data(QtCore.Qt.UserRole))
# pylint: enable=eval-used
except (AttributeError, NameError):
return None
[docs] def on_lstFields_itemSelectionChanged(self):
"""Update field description label and unlock the Next button.
.. note:: This is an automatic Qt slot
executed when the field selection changes.
"""
self.treeClasses.clear()
field = self.selected_field()
# Exit if no selection
if not field:
return
fields = self.layer.dataProvider().fields()
field_type = fields.field(field).typeName()
field_index = fields.indexFromName(self.selected_field())
unique_values = self.layer.uniqueValues(field_index)[0:48]
unique_values_str = [i is not None and unicode(i) or 'NULL'
for i in unique_values]
if unique_values != self.layer.uniqueValues(field_index):
unique_values_str += ['...']
desc = '<br/>%s: %s<br/><br/>' % (self.tr('Field type'), field_type)
desc += self.tr('Unique values: %s') % ', '.join(unique_values_str)
self.lblDescribeField.setText(desc)
# Enable the next button
self.pbnNext.setEnabled(True)
[docs] def selected_field(self):
"""Obtain the field selected by user.
:returns: Keyword of the selected field.
:rtype: string, None
"""
item = self.lstFields.currentItem()
if item:
return item.text()
else:
return None
[docs] def selected_allowresampling(self):
"""Obtain the allow_resampling state selected by user.
.. note:: Returns none if not set or not relevant
:returns: Value of the allow_resampling or None for not-set.
:rtype: boolean or None
"""
if not is_raster_layer(self.layer):
return None
if self.selected_category() != layer_purpose_exposure:
return None
# Only return false if checked, otherwise None for not-set.
if self.chkAllowResample.isChecked():
return False
else:
return None
[docs] def update_dragged_item_flags(self, item, column):
"""Fix the drop flag after the item is dropped.
Check if it looks like an item dragged from QListWidget
to QTreeWidget and disable the drop flag.
For some reasons the flag is set when dragging.
:param item:
:param column:
.. note:: This is a slot executed when the item change.
"""
# Treat var as unused
_ = column
if int(item.flags() & QtCore.Qt.ItemIsDropEnabled) \
and int(item.flags() & QtCore.Qt.ItemIsDragEnabled):
item.setFlags(item.flags() & ~QtCore.Qt.ItemIsDropEnabled)
[docs] def selected_mapping(self):
"""Obtain the value-to-class mapping set by user.
:returns: The complete mapping as a dict of lists.
:rtype: dict
"""
value_map = {}
tree_clone = self.treeClasses.invisibleRootItem().clone()
for tree_branch in tree_clone.takeChildren():
value_list = []
for tree_leaf in tree_branch.takeChildren():
value_list += [tree_leaf.data(0, QtCore.Qt.UserRole)]
if value_list:
value_map[tree_branch.text(0)] = value_list
return value_map
[docs] def populate_classified_values(
self, unassigned_values, assigned_values, default_classes):
"""Populate lstUniqueValues and treeClasses.from the parameters.
:param unassigned_values: List of values that haven't been assigned
to a class. It will be put in self.lstUniqueValues.
:type unassigned_values: list
:param assigned_values: Dictionary with class as the key and list of
value as the value of the dictionary. It will be put in
self.treeClasses.
:type assigned_values: dict
:param default_classes: Default classes from unit.
:type default_classes: list
"""
# Populate the unique values list
self.lstUniqueValues.clear()
for value in unassigned_values:
value_as_string = value is not None and unicode(value) or 'NULL'
list_item = QtGui.QListWidgetItem(self.lstUniqueValues)
list_item.setFlags(QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsDragEnabled)
list_item.setData(QtCore.Qt.UserRole, value)
list_item.setText(value_as_string)
self.lstUniqueValues.addItem(list_item)
# Populate assigned values tree
self.treeClasses.clear()
bold_font = QtGui.QFont()
bold_font.setItalic(True)
bold_font.setBold(True)
bold_font.setWeight(75)
self.treeClasses.invisibleRootItem().setFlags(
QtCore.Qt.ItemIsEnabled)
for default_class in default_classes:
# Create branch for class
tree_branch = QtGui.QTreeWidgetItem(self.treeClasses)
tree_branch.setFlags(QtCore.Qt.ItemIsDropEnabled |
QtCore.Qt.ItemIsEnabled)
tree_branch.setExpanded(True)
tree_branch.setFont(0, bold_font)
tree_branch.setText(0, default_class['name'])
if 'description' in default_class:
tree_branch.setToolTip(0, default_class['description'])
# Assign known values
for value in assigned_values[default_class['name']]:
string_value = value is not None and unicode(value) or 'NULL'
tree_leaf = QtGui.QTreeWidgetItem(tree_branch)
tree_leaf.setFlags(QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsSelectable |
QtCore.Qt.ItemIsDragEnabled)
tree_leaf.setData(0, QtCore.Qt.UserRole, value)
tree_leaf.setText(0, string_value)
# ===========================
# STEP_KW_EXTRAKEYWORDS
# ===========================
# noinspection PyPep8Naming
[docs] def on_cboFemaleRatioAttribute_currentIndexChanged(self):
"""Automatic slot executed when the female ratio attribute is changed.
When the user changes the female ratio attribute
(cboFemaleRatioAttribute), it will change the enabled value of
dsbFemaleRatioDefault. If value is 'Use default', enable
dsbFemaleRatioDefault. Otherwise, disabled it.
"""
value = self.cboFemaleRatioAttribute.currentText()
if value == self.global_default_string:
self.dsbFemaleRatioDefault.setEnabled(True)
else:
self.dsbFemaleRatioDefault.setEnabled(False)
# noinspection PyPep8Naming,PyMethodMayBeStatic
[docs] def on_cboYouthRatioAttribute_currentIndexChanged(self):
"""Automatic slot executed when the youth ratio attribute is changed.
When the user changes the youth ratio attribute
(cboYouthRatioAttribute), it will change the enabled value of
dsbYouthRatioDefault. If value is 'Use default', enable
dsbYouthRatioDefault. Otherwise, disabled it.
"""
value = self.cboYouthRatioAttribute.currentText()
if value == self.global_default_string:
self.dsbYouthRatioDefault.setEnabled(True)
else:
self.dsbYouthRatioDefault.setEnabled(False)
# noinspection PyPep8Naming,PyMethodMayBeStatic
[docs] def on_cboAdultRatioAttribute_currentIndexChanged(self):
"""Automatic slot executed when the adult ratio attribute is changed.
When the user changes the adult ratio attribute
(cboAdultRatioAttribute), it will change the enabled value of
dsbAdultRatioDefault. If value is 'Use default', enable
dsbAdultRatioDefault. Otherwise, disabled it.
"""
value = self.cboAdultRatioAttribute.currentText()
if value == self.global_default_string:
self.dsbAdultRatioDefault.setEnabled(True)
else:
self.dsbAdultRatioDefault.setEnabled(False)
# noinspection PyPep8Naming,PyMethodMayBeStatic
[docs] def on_cboElderlyRatioAttribute_currentIndexChanged(self):
"""Automatic slot executed when the adult ratio attribute is changed.
When the user changes the elderly ratio attribute
(cboElderlyRatioAttribute), it will change the enabled value of
dsbElderlyRatioDefault. If value is 'Use default', enable
dsbElderlyRatioDefault. Otherwise, disabled it.
"""
value = self.cboElderlyRatioAttribute.currentText()
if value == self.global_default_string:
self.dsbElderlyRatioDefault.setEnabled(True)
else:
self.dsbElderlyRatioDefault.setEnabled(False)
[docs] def get_aggregation_attributes(self):
"""Obtain the value of aggregation attributes set by user.
:returns: The key and value of aggregation attributes.
:rtype: dict
"""
aggregation_attributes = dict()
current_index = self.cboFemaleRatioAttribute.currentIndex()
data = self.cboFemaleRatioAttribute.itemData(current_index)
aggregation_attributes[female_ratio_attribute_key] = data
value = self.dsbFemaleRatioDefault.value()
aggregation_attributes[female_ratio_default_key] = value
current_index = self.cboYouthRatioAttribute.currentIndex()
data = self.cboYouthRatioAttribute.itemData(current_index)
aggregation_attributes[youth_ratio_attribute_key] = data
value = self.dsbYouthRatioDefault.value()
aggregation_attributes[youth_ratio_default_key] = value
current_index = self.cboAdultRatioAttribute.currentIndex()
data = self.cboAdultRatioAttribute.itemData(current_index)
aggregation_attributes[adult_ratio_attribute_key] = data
value = self.dsbAdultRatioDefault.value()
aggregation_attributes[adult_ratio_default_key] = value
current_index = self.cboElderlyRatioAttribute.currentIndex()
data = self.cboElderlyRatioAttribute.itemData(current_index)
aggregation_attributes[elderly_ratio_attribute_key] = data
value = self.dsbElderlyRatioDefault.value()
aggregation_attributes[elderly_ratio_default_key] = value
return aggregation_attributes
[docs] def age_ratios_are_valid(self):
"""Return true if the sum of age ratios is good, otherwise False.
Good means their sum does not exceed 1.
:returns: Tuple of boolean and float. Boolean represent good or not
good, while float represent the summation of age ratio. If some
ratio do not use global default, the summation is set to 0.
:rtype: tuple
"""
youth_ratio_index = self.cboYouthRatioAttribute.currentIndex()
adult_ratio_index = self.cboAdultRatioAttribute.currentIndex()
elderly_ratio_index = self.cboElderlyRatioAttribute.currentIndex()
ratio_indexes = [
youth_ratio_index, adult_ratio_index, elderly_ratio_index]
if ratio_indexes.count(0) == len(ratio_indexes):
youth_ratio_default = self.dsbYouthRatioDefault.value()
adult_ratio_default = self.dsbAdultRatioDefault.value()
elderly_ratio_default = self.dsbElderlyRatioDefault.value()
sum_ratio_default = youth_ratio_default + adult_ratio_default
sum_ratio_default += elderly_ratio_default
if sum_ratio_default > 1:
return False, sum_ratio_default
else:
return True, sum_ratio_default
return True, 0
# noinspection PyUnresolvedReferences,PyStatementEffect
[docs] def populate_cbo_aggregation_attribute(
self, ratio_attribute_key, cbo_ratio_attribute):
"""Populate the combo box cbo_ratio_attribute for ratio_attribute_key.
:param ratio_attribute_key: A ratio attribute key that saved in
keywords.
:type ratio_attribute_key: str
:param cbo_ratio_attribute: A combo box that wants to be populated.
:type cbo_ratio_attribute: QComboBox
"""
cbo_ratio_attribute.clear()
ratio_attribute = self.get_existing_keyword(ratio_attribute_key)
fields, attribute_position = layer_attribute_names(
self.layer, [QtCore.QVariant.Double], ratio_attribute)
cbo_ratio_attribute.addItem(
self.global_default_string, self.global_default_data)
cbo_ratio_attribute.addItem(
self.do_not_use_string, self.do_not_use_data)
for field in fields:
cbo_ratio_attribute.addItem(field, field)
# For backward compatibility, still use Use default
if (ratio_attribute == self.global_default_data or
ratio_attribute == self.tr('Use default')):
cbo_ratio_attribute.setCurrentIndex(0)
elif ratio_attribute == self.do_not_use_data:
cbo_ratio_attribute.setCurrentIndex(1)
elif ratio_attribute is None or attribute_position is None:
# current_keyword was not found in the attribute table.
# Use default
cbo_ratio_attribute.setCurrentIndex(0)
else:
# + 2 is because we add use defaults and don't use
cbo_ratio_attribute.setCurrentIndex(attribute_position + 2)
[docs] def on_leTitle_textChanged(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the title value changes.
"""
self.pbnNext.setEnabled(bool(self.leTitle.text()))
[docs] def selected_functions_1(self):
"""Obtain functions available for hazard an exposure selected by user.
:returns: List of the available functions metadata.
:rtype: list, None
"""
selection = self.tblFunctions1.selectedItems()
if len(selection) != 1:
return []
try:
return selection[0].data(RoleFunctions)
except (AttributeError, NameError):
return None
[docs] def selected_impact_function_constraints(self):
"""Obtain impact function constraints selected by user.
:returns: Tuple of metadata of hazard, exposure,
hazard layer constraints and exposure layer constraints
:rtype: tuple
"""
selection = self.tblFunctions1.selectedItems()
if len(selection) != 1:
return None, None, None, None
h = selection[0].data(RoleHazard)
e = selection[0].data(RoleExposure)
selection = self.tblFunctions2.selectedItems()
if len(selection) != 1:
return h, e, None, None
hc = selection[0].data(RoleHazardConstraint)
ec = selection[0].data(RoleExposureConstraint)
return h, e, hc, ec
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_tblFunctions1_itemSelectionChanged(self):
"""Choose selected hazard x exposure combination.
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
functions = self.selected_functions_1()
if not functions:
self.lblAvailableFunctions1.clear()
else:
txt = self.tr('Available functions:') + ' ' + ', '.join(
[f['name'] for f in functions])
self.lblAvailableFunctions1.setText(txt)
# Clear the selection on the 2nd matrix
self.tblFunctions2.clearContents()
self.lblAvailableFunctions2.clear()
self.pbnNext.setEnabled(True)
# Put a dot to the selected cell - note there is no way
# to center an icon without using a custom ItemDelegate
selection = self.tblFunctions1.selectedItems()
selItem = (len(selection) == 1) and selection[0] or None
for row in range(self.tblFunctions1.rowCount()):
for col in range(self.tblFunctions1.columnCount()):
item = self.tblFunctions1.item(row, col)
item.setText((item == selItem) and u'\u2022' or '')
# pylint: disable=W0613
# noinspection PyPep8Naming
[docs] def on_tblFunctions1_cellDoubleClicked(self, row, column):
"""Choose selected hazard x exposure combination and go ahead.
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
self.pbnNext.click()
# pylint: enable=W0613
[docs] def populate_function_table_1(self):
"""Populate the tblFunctions1 table with available functions."""
# The hazard category radio buttons are now removed -
# make this parameter of IFM.available_hazards() optional
hazard_category = hazard_category_single_event
hazards = self.impact_function_manager\
.available_hazards(hazard_category['key'])
# Remove 'generic' from hazards
for h in hazards:
if h['key'] == 'generic':
hazards.remove(h)
exposures = self.impact_function_manager.available_exposures()
self.lblAvailableFunctions1.clear()
self.tblFunctions1.clear()
self.tblFunctions1.setColumnCount(len(hazards))
self.tblFunctions1.setRowCount(len(exposures))
for i in range(len(hazards)):
h = hazards[i]
item = QtGui.QTableWidgetItem()
item.setIcon(QtGui.QIcon(
resources_path('img', 'wizard', 'keyword-subcategory-%s.svg'
% (h['key'] or 'notset'))))
item.setText(h['name'].capitalize())
self.tblFunctions1.setHorizontalHeaderItem(i, item)
for i in range(len(exposures)):
e = exposures[i]
item = QtGui.QTableWidgetItem()
item.setIcon(QtGui.QIcon(
resources_path('img', 'wizard', 'keyword-subcategory-%s.svg'
% (e['key'] or 'notset'))))
item.setText(e['name'].capitalize())
self.tblFunctions1.setVerticalHeaderItem(i, item)
big_font = QtGui.QFont()
big_font.setPointSize(80)
for h in hazards:
for e in exposures:
item = QtGui.QTableWidgetItem()
functions = \
self.impact_function_manager.functions_for_constraint(
h['key'], e['key'])
if len(functions):
background_colour = QtGui.QColor(120, 255, 120)
else:
background_colour = QtGui.QColor(220, 220, 220)
item.setFlags(item.flags() & ~QtCore.Qt.ItemIsEnabled)
item.setFlags(item.flags() & ~QtCore.Qt.ItemIsSelectable)
item.setBackground(QtGui.QBrush(background_colour))
item.setFont(big_font)
item.setTextAlignment(QtCore.Qt.AlignCenter |
QtCore.Qt.AlignHCenter)
item.setData(RoleFunctions, functions)
item.setData(RoleHazard, h)
item.setData(RoleExposure, e)
self.tblFunctions1.setItem(
exposures.index(e), hazards.index(h), item)
self.pbnNext.setEnabled(False)
[docs] def selected_functions_2(self):
"""Obtain functions available for hazard and exposure selected by user.
:returns: List of the available functions metadata.
:rtype: list, None
"""
selection = self.tblFunctions2.selectedItems()
if len(selection) != 1:
return []
return selection[0].data(RoleFunctions)
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_tblFunctions2_itemSelectionChanged(self):
"""Choose selected hazard x exposure constraints combination.
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
functions = self.selected_functions_2()
if not functions:
self.lblAvailableFunctions2.clear()
else:
text = self.tr('Available functions:') + ' ' + ', '.join(
[f['name'] for f in functions])
self.lblAvailableFunctions2.setText(text)
self.pbnNext.setEnabled(True)
# Put a dot to the selected cell - note there is no way
# to center an icon without using a custom ItemDelegate
selection = self.tblFunctions2.selectedItems()
selItem = (len(selection) == 1) and selection[0] or None
for row in range(self.tblFunctions2.rowCount()):
for col in range(self.tblFunctions2.columnCount()):
item = self.tblFunctions2.item(row, col)
item.setText((item == selItem) and u'\u2022' or '')
# pylint: disable=W0613
# noinspection PyPep8Naming,PyUnusedLocal
[docs] def on_tblFunctions2_cellDoubleClicked(self, row, column):
"""Click handler for selecting hazard and exposure constraints.
:param row: The row that the user clicked on.
:type row: int
:param column: The column that the user clicked on.
:type column: int
.. note:: This is an automatic Qt slot executed when the category
selection changes.
"""
self.pbnNext.click()
# pylint: enable=W0613
[docs] def on_lstFunctions_itemSelectionChanged(self):
"""Update function description label
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
imfunc = self.selected_function()
# Exit if no selection
if not imfunc:
self.lblDescribeFunction.clear()
self.pbnNext.setEnabled(False)
# Set the branch description if selected
branch = self.selected_function_group()
if branch and "description" in branch.keys():
self.lblDescribeFunction.setText(branch['description'])
return
# Set description label
description = '<table border="0">'
if "name" in imfunc.keys():
description += '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (
self.tr('Function'), imfunc['name'])
if "overview" in imfunc.keys():
description += '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (
self.tr('Overview'), imfunc['overview'])
description += '</table>'
self.lblDescribeFunction.setText(description)
# Enable the next button if anything selected
self.pbnNext.setEnabled(bool(self.selected_function()))
[docs] def selected_function(self):
"""Obtain the impact function selected by user.
:returns: metadata of the selected function.
:rtype: dict, None
"""
item = self.lstFunctions.currentItem()
if not item:
return None
data = item.data(QtCore.Qt.UserRole)
if data:
return data
else:
return None
[docs] def on_rbHazLayerFromCanvas_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbHazLayerFromBrowser_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
[docs] def is_layer_compatible(self, layer, layer_purpose, keywords=None):
"""Validate if a given layer is compatible for selected IF
as a given layer_purpose
:param layer: The layer to be validated
:type layer: QgsVectorLayer | QgsRasterLayer
:param layer_purpose: The layer_purpose the layer is validated for
:type layer_purpose: string
:param keywords: The layer keywords
:type keywords: None, dict
:returns: True if layer is appropriate for the selected role
:rtype: boolean
"""
# Get allowed subcategory and layer_geometry from IF constraints
h, e, hc, ec = self.selected_impact_function_constraints()
if layer_purpose == 'hazard':
subcategory = h['key']
layer_geometry = hc['key']
elif layer_purpose == 'exposure':
subcategory = e['key']
layer_geometry = ec['key']
else:
# For aggregation layers, use a simplified test and return
if (keywords and 'layer_purpose' in keywords and
keywords['layer_purpose'] == layer_purpose):
return True
if not keywords and is_polygon_layer(layer):
return True
return False
# Compare layer properties with explicitly set constraints
# Reject if layer geometry doesn't match
if layer_geometry != self.get_layer_geometry_id(layer):
return False
# If no keywords, there's nothing more we can check.
# The same if the keywords version doesn't match
if not keywords or 'keyword_version' not in keywords:
return True
keyword_version = str(keywords['keyword_version'])
if compare_version(keyword_version, get_version()) != 0:
return True
# Compare layer keywords with explicitly set constraints
# Reject if layer purpose missing or doesn't match
if ('layer_purpose' not in keywords or
keywords['layer_purpose'] != layer_purpose):
return False
# Reject if layer subcategory doesn't match
if (layer_purpose in keywords and
keywords[layer_purpose] != subcategory):
return False
# Compare layer keywords with the chosen function's constraints
imfunc = self.selected_function()
lay_req = imfunc['layer_requirements'][layer_purpose]
# Reject if layer mode doesn't match
if ('layer_mode' in keywords and
lay_req['layer_mode']['key'] != keywords['layer_mode']):
return False
# Reject if classification doesn't match
classification_key = '%s_%s_classification' % (
'raster' if is_raster_layer(layer) else 'vector',
layer_purpose)
classification_keys = classification_key + 's'
if (lay_req['layer_mode'] == layer_mode_classified and
classification_key in keywords and
classification_keys in lay_req):
allowed_classifications = [
c['key'] for c in lay_req[classification_keys]]
if keywords[classification_key] not in allowed_classifications:
return False
# Reject if unit doesn't match
unit_key = ('continuous_hazard_unit'
if layer_purpose == layer_purpose_hazard['key']
else 'exposure_unit')
unit_keys = unit_key + 's'
if (lay_req['layer_mode'] == layer_mode_continuous and
unit_key in keywords and
unit_keys in lay_req):
allowed_units = [
c['key'] for c in lay_req[unit_keys]]
if keywords[unit_key] not in allowed_units:
return False
# Finally return True
return True
[docs] def get_compatible_layers_from_canvas(self, category):
"""Collect compatible layers from map canvas.
.. note:: Returns layers with keywords and layermode matching
the category and compatible with the selected impact function.
Also returns layers without keywords with layermode
compatible with the selected impact function.
:param category: The category to filter for.
:type category: string
:returns: Metadata of found layers.
:rtype: list of dicts
"""
# Collect compatible layers
layers = []
for layer in self.iface.mapCanvas().layers():
try:
keywords = self.keyword_io.read_keywords(layer)
if ('layer_purpose' not in keywords and
'impact_summary' not in keywords):
keywords = None
except (HashNotFoundError,
OperationalError,
NoKeywordsFoundError,
KeywordNotFoundError,
InvalidParameterError,
UnsupportedProviderError):
keywords = None
if self.is_layer_compatible(layer, category, keywords):
layers += [
{'id': layer.id(),
'name': layer.name(),
'keywords': keywords}]
# Move layers without keywords to the end
l1 = [l for l in layers if l['keywords']]
l2 = [l for l in layers if not l['keywords']]
layers = l1 + l2
return layers
[docs] def list_compatible_layers_from_canvas(self, category, list_widget):
"""Fill given list widget with compatible layers.
.. note:: Uses get_compatible_layers_from_canvas() to filter layers
:param category: The category to filter for.
:type category: string
:param list_widget: The list widget to be filled with layers.
:type list_widget: QListWidget
:returns: Metadata of found layers.
:rtype: list of dicts
"""
italic_font = QtGui.QFont()
italic_font.setItalic(True)
# Add compatible layers
list_widget.clear()
for layer in self.get_compatible_layers_from_canvas(category):
item = QListWidgetItem(layer['name'], list_widget)
item.setData(QtCore.Qt.UserRole, layer['id'])
if not layer['keywords']:
item.setFont(italic_font)
list_widget.addItem(item)
[docs] def get_layer_description_from_canvas(self, layer, purpose):
"""Obtain the description of a canvas layer selected by user.
:param layer: The QGIS layer.
:type layer: QgsMapLayer
:param category: The category of the layer to get the description.
:type category: string
:returns: description of the selected layer.
:rtype: string
"""
if not layer:
return ""
try:
keywords = self.keyword_io.read_keywords(layer)
if 'layer_purpose' not in keywords:
keywords = None
except (HashNotFoundError,
OperationalError,
NoKeywordsFoundError,
KeywordNotFoundError,
InvalidParameterError,
UnsupportedProviderError):
keywords = None
# set the current layer (e.g. for the keyword creation sub-thread)
self.layer = layer
if purpose == 'hazard':
self.hazard_layer = layer
elif purpose == 'exposure':
self.exposure_layer = layer
else:
self.aggregation_layer = layer
# Check if the layer is keywordless
if keywords and 'keyword_version' in keywords:
kw_ver = str(keywords['keyword_version'])
self.is_selected_layer_keywordless = bool(
compare_version(kw_ver, get_version()) != 0)
else:
self.is_selected_layer_keywordless = True
desc = self.layer_description_html(layer, keywords)
return desc
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_lstCanvasHazLayers_itemSelectionChanged(self):
"""Update layer description label
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
self.hazard_layer = self.selected_canvas_hazlayer()
lblText = self.get_layer_description_from_canvas(self.hazard_layer,
'hazard')
self.lblDescribeCanvasHazLayer.setText(lblText)
self.pbnNext.setEnabled(True)
[docs] def selected_canvas_hazlayer(self):
"""Obtain the canvas layer selected by user.
:returns: The currently selected map layer in the list.
:rtype: QgsMapLayer
"""
if self.lstCanvasHazLayers.selectedItems():
item = self.lstCanvasHazLayers.currentItem()
else:
return None
try:
layer_id = item.data(QtCore.Qt.UserRole)
except (AttributeError, NameError):
layer_id = None
layer = QgsMapLayerRegistry.instance().mapLayer(layer_id)
return layer
[docs] def pg_path_to_uri(self, path):
"""Convert layer path from QgsBrowserModel to full QgsDataSourceURI
:param path: The layer path from QgsBrowserModel
:type path: string
:returns: layer uri
:rtype: QgsDataSourceURI
"""
conn_name = path.split('/')[1]
schema = path.split('/')[2]
table = path.split('/')[3]
settings = QSettings()
key = "/PostgreSQL/connections/" + conn_name
service = settings.value(key + "/service")
host = settings.value(key + "/host")
port = settings.value(key + "/port")
if not port:
port = "5432"
db = settings.value(key + "/database")
use_estimated_metadata = settings.value(
key + "/estimatedMetadata", False, type=bool)
sslmode = settings.value(
key + "/sslmode", QgsDataSourceURI.SSLprefer, type=int)
username = ""
password = ""
if settings.value(key + "/saveUsername") == "true":
username = settings.value(key + "/username")
if settings.value(key + "/savePassword") == "true":
password = settings.value(key + "/password")
# Old save setting
if settings.contains(key + "/save"):
username = settings.value(key + "/username")
if settings.value(key + "/save") == "true":
password = settings.value(key + "/password")
uri = QgsDataSourceURI()
if service:
uri.setConnection(service, db, username, password, sslmode)
else:
uri.setConnection(host, port, db, username, password, sslmode)
uri.setUseEstimatedMetadata(use_estimated_metadata)
# Obtain the geometry column name
connector = PostGisDBConnector(uri)
tbls = connector.getVectorTables(schema)
tbls = [tbl for tbl in tbls if tbl[1] == table]
# if len(tbls) != 1:
# In the future, also look for raster layers?
# tbls = connector.getRasterTables(schema)
# tbls = [tbl for tbl in tbls if tbl[1]==table]
if not tbls:
return None
tbl = tbls[0]
geom_col = tbl[8]
uri.setDataSource(schema, table, geom_col)
return uri
[docs] def layer_description_html(self, layer, keywords=None):
"""Form a html description of a given layer based on the layer
parameters and keywords if provided
:param layer: The layer to get the description
:type layer: QgsMapLayer
:param keywords: The layer keywords
:type keywords: None, dict
:returns: The html description in tabular format,
ready to use in a label or tool tip.
:rtype: str
"""
if keywords and 'keyword_version' in keywords:
keyword_version = str(keywords['keyword_version'])
else:
keyword_version = None
if (keywords and keyword_version and
compare_version(keyword_version, get_version()) == 0):
# The layer has valid keywords
purpose = keywords.get('layer_purpose')
if purpose == layer_purpose_hazard['key']:
subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (
self.tr('Hazard'), keywords.get(purpose))
unit = keywords.get('continuous_hazard_unit')
elif purpose == layer_purpose_exposure['key']:
subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (
self.tr('Exposure'), keywords.get(purpose))
unit = keywords.get('exposure_unit')
else:
subcategory = ''
unit = None
if keywords.get('layer_mode') == layer_mode_classified['key']:
unit = self.tr('classified data')
if unit:
unit = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (
self.tr('Unit'), unit)
desc = """
<table border="0" width="100%%">
<tr><td><b>%s</b>: </td><td>%s</td></tr>
<tr><td><b>%s</b>: </td><td>%s</td></tr>
%s
%s
<tr><td><b>%s</b>: </td><td>%s</td></tr>
</table>
""" % (self.tr('Title'), keywords.get('title'),
self.tr('Purpose'), keywords.get('layer_purpose'),
subcategory,
unit,
self.tr('Source'), keywords.get('source'))
elif keywords:
# The layer has keywords, but the version is wrong
desc = self.tr(
'Your layer\'s keyword\'s version (%s) does not match with '
'your InaSAFE version (%s). If you wish to use it as an '
'exposure, hazard, or aggregation layer in an analysis, '
'please update the keywords. Click Next if you want to assign '
'keywords now.' % (keyword_version or 'No Version',
get_version()))
else:
# The layer is keywordless
if is_point_layer(layer):
geom_type = 'point'
elif is_polygon_layer(layer):
geom_type = 'polygon'
else:
geom_type = 'line'
# hide password in the layer source
source = re.sub(
r'password=\'.*\'', r'password=*****', layer.source())
desc = """
%s<br/><br/>
<b>%s</b>: %s<br/>
<b>%s</b>: %s<br/><br/>
%s
""" % (self.tr('This layer has no valid keywords assigned'),
self.tr('SOURCE'), source,
self.tr('TYPE'), is_raster_layer(layer) and 'raster' or
'vector (%s)' % geom_type,
self.tr('In the next step you will be able' +
' to assign keywords to this layer.'))
return desc
[docs] def unsuitable_layer_description_html(self, layer, layer_purpose,
keywords=None):
"""Form a html description of a given non-matching layer based on
the currently selected impact function requirements vs layer\'s
parameters and keywords if provided, as
:param layer: The layer to be validated
:type layer: QgsVectorLayer | QgsRasterLayer
:param layer_purpose: The layer_purpose the layer is validated for
:type layer_purpose: string
:param keywords: The layer keywords
:type keywords: None, dict
:returns: The html description in tabular format,
ready to use in a label or tool tip.
:rtype: str
"""
def emphasize(str1, str2):
''' Compare two strings and emphasize both if differ '''
if str1 != str2:
str1 = '<i>%s</i>' % str1
str2 = '<i>%s</i>' % str2
return (str1, str2)
# Get allowed subcategory and layer_geometry from IF constraints
h, e, hc, ec = self.selected_impact_function_constraints()
imfunc = self.selected_function()
lay_req = imfunc['layer_requirements'][layer_purpose]
if layer_purpose == 'hazard':
layer_purpose_key_name = layer_purpose_hazard['name']
req_subcategory = h['key']
req_geometry = hc['key']
elif layer_purpose == 'exposure':
layer_purpose_key_name = layer_purpose_exposure['name']
req_subcategory = e['key']
req_geometry = ec['key']
else:
layer_purpose_key_name = layer_purpose_aggregation['name']
req_subcategory = ''
# For aggregation layers, only accept polygons
req_geometry = 'polygon'
req_layer_mode = lay_req['layer_mode']['key']
lay_geometry = self.get_layer_geometry_id(layer)
lay_purpose = ' -'
lay_subcategory = ' -'
lay_layer_mode = ' -'
if keywords:
if 'layer_purpose' in keywords:
lay_purpose = keywords['layer_purpose']
if layer_purpose in keywords:
lay_subcategory = keywords[layer_purpose]
if 'layer_mode' in keywords:
lay_layer_mode = keywords['layer_mode']
lay_geometry, req_geometry = emphasize(lay_geometry, req_geometry)
lay_purpose, layer_purpose = emphasize(lay_purpose, layer_purpose)
lay_subcategory, req_subcategory = emphasize(lay_subcategory,
req_subcategory)
lay_layer_mode, req_layer_mode = emphasize(lay_layer_mode,
req_layer_mode)
# Classification
classification_row = ''
if (lay_req['layer_mode'] == layer_mode_classified and
layer_purpose == 'hazard'):
# Determine the keyword key for the classification
classification_obj = (raster_hazard_classification
if is_raster_layer(layer)
else vector_hazard_classification)
classification_key = classification_obj['key']
classification_key_name = classification_obj['name']
classification_keys = classification_key + 's'
if classification_keys in lay_req:
allowed_classifications = [
c['key'] for c in lay_req[classification_keys]]
req_classifications = ', '.join(allowed_classifications)
lay_classification = ' -'
if classification_key in keywords:
lay_classification = keywords[classification_key]
if lay_classification not in allowed_classifications:
# We already know we want to empasize them and the test
# inside the function will always pass.
lay_classification, req_classifications = emphasize(
lay_classification, req_classifications)
classification_row = (('<tr><td><b>%s</b></td>' +
'<td>%s</td><td>%s</td></tr>')
% (classification_key_name,
lay_classification,
req_classifications))
# Unit
units_row = ''
if lay_req['layer_mode'] == layer_mode_continuous:
# Determine the keyword key for the unit
unit_obj = (continuous_hazard_unit
if layer_purpose == layer_purpose_hazard['key']
else exposure_unit)
unit_key = unit_obj['key']
unit_key_name = unit_obj['name']
unit_keys = unit_key + 's'
if unit_keys in lay_req:
allowed_units = [c['key'] for c in lay_req[unit_keys]]
req_units = ', '.join(allowed_units)
lay_unit = ' -'
if unit_key in keywords:
lay_unit = keywords[unit_key]
if lay_unit not in allowed_units:
# We already know we want to empasize them and the test
# inside the function will always pass.
lay_unit, req_units = emphasize(lay_unit, req_units)
units_row = (('<tr><td><b>%s</b></td>' +
'<td>%s</td><td>%s</td></tr>')
% (unit_key_name, lay_unit, req_units))
html = '''
<table border="0" width="100%%" cellpadding="2">
<tr><td width="33%%"></td>
<td width="33%%"><b>%s</b></td>
<td width="33%%"><b>%s</b></td>
</tr>
<tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr>
<tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr>
<tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr>
<tr><td><b>%s</b></td><td>%s</td><td>%s</td></tr>
%s
%s
</table>
''' % (self.tr('Layer'), self.tr('Required'),
self.tr('Geometry'), lay_geometry, req_geometry,
self.tr('Purpose'), lay_purpose, layer_purpose,
layer_purpose_key_name, lay_subcategory, req_subcategory,
self.tr('Layer mode'), lay_layer_mode, req_layer_mode,
classification_row,
units_row)
return html
[docs] def get_layer_description_from_browser(self, category):
"""Obtain the description of the browser layer selected by user.
:param category: The category of the layer to get the description.
:type category: string
:returns: Tuple of boolean and string. Boolean is true if layer is
validated as compatible for current role (impact function and
category) and false otherwise. String contains a description
of the selected layer or an error message.
:rtype: tuple
"""
if category == 'hazard':
browser = self.tvBrowserHazard
elif category == 'exposure':
browser = self.tvBrowserExposure
elif category == 'aggregation':
browser = self.tvBrowserAggregation
else:
raise InaSAFEError
index = browser.selectionModel().currentIndex()
if not index:
return False, ''
# Map the proxy model index to the source model index
index = browser.model().mapToSource(index)
item = browser.model().sourceModel().dataItem(index)
if not item:
return False, ''
item_class_name = item.metaObject().className()
# if not itemClassName.endswith('LayerItem'):
if not item.type() == QgsDataItem.Layer:
if item_class_name == 'QgsPGRootItem' and not item.children():
return False, create_postGIS_connection_first
else:
return False, ''
if item_class_name not in [
'QgsOgrLayerItem', 'QgsLayerItem', 'QgsPGLayerItem']:
return False, ''
path = item.path()
if item_class_name in ['QgsOgrLayerItem',
'QgsLayerItem'] and not os.path.exists(path):
return False, ''
# try to create the layer
if item_class_name == 'QgsOgrLayerItem':
layer = QgsVectorLayer(path, '', 'ogr')
elif item_class_name == 'QgsPGLayerItem':
uri = self.pg_path_to_uri(path)
if uri:
layer = QgsVectorLayer(uri.uri(), uri.table(), 'postgres')
else:
layer = None
else:
layer = QgsRasterLayer(path, '', 'gdal')
if not layer or not layer.isValid():
return False, self.tr('Not a valid layer.')
try:
keywords = self.keyword_io.read_keywords(layer)
if ('layer_purpose' not in keywords and
'impact_summary' not in keywords):
keywords = None
except (HashNotFoundError,
OperationalError,
NoKeywordsFoundError,
KeywordNotFoundError,
InvalidParameterError,
UnsupportedProviderError):
keywords = None
# set the layer name for further use in the step_fc_summary
if keywords:
layer.setLayerName(keywords.get('title'))
if not self.is_layer_compatible(layer, category, keywords):
label_text = '%s<br/>%s' % (self.tr('This layer\'s keywords ' +
'or type are not suitable:'),
self.unsuitable_layer_description_html(
layer, category, keywords))
return False, label_text
# set the current layer (e.g. for the keyword creation sub-thread
# or for adding the layer to mapCanvas)
self.layer = layer
if category == 'hazard':
self.hazard_layer = layer
elif category == 'exposure':
self.exposure_layer = layer
else:
self.aggregation_layer = layer
# Check if the layer is keywordless
if keywords and 'keyword_version' in keywords:
kw_ver = str(keywords['keyword_version'])
self.is_selected_layer_keywordless = bool(
compare_version(kw_ver, get_version()) != 0)
else:
self.is_selected_layer_keywordless = True
desc = self.layer_description_html(layer, keywords)
return True, desc
# noinspection PyPep8Naming
[docs] def tvBrowserHazard_selection_changed(self):
"""Update layer description label"""
(is_compatible, desc) = self.get_layer_description_from_browser(
'hazard')
self.lblDescribeBrowserHazLayer.setText(desc)
self.lblDescribeBrowserHazLayer.setEnabled(is_compatible)
self.pbnNext.setEnabled(is_compatible)
[docs] def on_rbExpLayerFromCanvas_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbExpLayerFromBrowser_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
@pyqtSignature('')
[docs] def on_lstCanvasExpLayers_itemSelectionChanged(self):
"""Update layer description label
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
self.exposure_layer = self.selected_canvas_explayer()
lblText = self.get_layer_description_from_canvas(self.exposure_layer,
'exposure')
self.lblDescribeCanvasExpLayer.setText(lblText)
self.pbnNext.setEnabled(True)
[docs] def selected_canvas_explayer(self):
"""Obtain the canvas exposure layer selected by user.
:returns: The currently selected map layer in the list.
:rtype: QgsMapLayer
"""
if self.lstCanvasExpLayers.selectedItems():
item = self.lstCanvasExpLayers.currentItem()
else:
return None
try:
layer_id = item.data(QtCore.Qt.UserRole)
except (AttributeError, NameError):
layer_id = None
layer = QgsMapLayerRegistry.instance().mapLayer(layer_id)
return layer
[docs] def tvBrowserExposure_selection_changed(self):
"""Update layer description label"""
(is_compatible, desc) = self.get_layer_description_from_browser(
'exposure')
self.lblDescribeBrowserExpLayer.setText(desc)
self.pbnNext.setEnabled(is_compatible)
[docs] def layers_intersect(self, layer_a, layer_b):
"""Check if extents of two layers intersect.
:param layer_a: One of the two layers to test overlapping
:type layer_a: QgsMapLayer
:param layer_b: The second of the two layers to test overlapping
:type layer_b: QgsMapLayer
:returns: true if the layers intersect, false if they are disjoint
:rtype: boolean
"""
extent_a = layer_a.extent()
extent_b = layer_b.extent()
if layer_a.crs() != layer_b.crs():
coord_transform = QgsCoordinateTransform(
layer_a.crs(), layer_b.crs())
extent_b = (coord_transform.transform(
extent_b, QgsCoordinateTransform.ReverseTransform))
return extent_a.intersects(extent_b)
[docs] def on_rbAggLayerFromCanvas_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbAggLayerFromBrowser_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbAggLayerNoAggregation_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
@pyqtSignature('')
[docs] def on_lstCanvasAggLayers_itemSelectionChanged(self):
"""Update layer description label
.. note:: This is an automatic Qt slot
executed when the category selection changes.
"""
self.aggregation_layer = self.selected_canvas_agglayer()
lblText = self.get_layer_description_from_canvas(
self.aggregation_layer, 'aggregation')
self.lblDescribeCanvasAggLayer.setText(lblText)
self.pbnNext.setEnabled(True)
[docs] def selected_canvas_agglayer(self):
"""Obtain the canvas aggregation layer selected by user.
:returns: The currently selected map layer in the list.
:rtype: QgsMapLayer
"""
if self.lstCanvasAggLayers.selectedItems():
item = self.lstCanvasAggLayers.currentItem()
else:
return None
try:
layer_id = item.data(QtCore.Qt.UserRole)
except (AttributeError, NameError):
layer_id = None
layer = QgsMapLayerRegistry.instance().mapLayer(layer_id)
return layer
[docs] def tvBrowserAggregation_selection_changed(self):
"""Update layer description label"""
(is_compatible, desc) = self.get_layer_description_from_browser(
'aggregation')
self.lblDescribeBrowserAggLayer.setText(desc)
self.pbnNext.setEnabled(is_compatible)
[docs] def on_rbExtentUser_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbExtentLayer_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
# noinspection PyPep8Naming
[docs] def on_rbExtentScreen_toggled(self):
"""Unlock the Next button
.. note:: This is an automatic Qt slot
executed when the radiobutton is activated.
"""
self.pbnNext.setEnabled(True)
[docs] def start_capture_coordinates(self):
"""Enter the coordinate capture mode"""
self.hide()
[docs] def stop_capture_coordinates(self):
"""Exit the coordinate capture mode"""
self.extent_dialog._populate_coordinates()
self.extent_dialog.canvas.setMapTool(
self.extent_dialog.previous_map_tool)
self.show()
[docs] def write_extent(self):
""" After the extent selection,
save the extent and disconnect signals
"""
self.extent_dialog.accept()
self.extent_dialog.clear_extent.disconnect(
self.dock.extent.clear_user_analysis_extent)
self.extent_dialog.extent_defined.disconnect(
self.dock.define_user_analysis_extent)
self.extent_dialog.capture_button.clicked.disconnect(
self.start_capture_coordinates)
self.extent_dialog.tool.rectangle_created.disconnect(
self.stop_capture_coordinates)
# ===========================
# STEP_FC_EXTENT_DISJOINT
# ===========================
[docs] def validate_extent(self):
"""Check if the selected extent intersects source data.
:returns: true if extent intersects both layers, false if is disjoint
:rtype: boolean
"""
self.analysis_handler = AnalysisHandler(self)
self.analysis_handler.init_analysis()
try:
self.analysis_handler.analysis.setup_analysis()
except InsufficientOverlapError:
self.analysis_handler = None
return False
self.analysis_handler = None
return True
@pyqtSignature('')
[docs] def on_pbnReportWeb_released(self):
"""Handle the Open Report in Web Browser button release.
.. note:: This is an automatic Qt slot
executed when the Next button is released.
"""
self.wvResults.open_current_in_browser()
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_pbnReportPDF_released(self):
"""Handle the Generate PDF button release.
.. note:: This is an automatic Qt slot
executed when the Next button is released.
"""
self.analysis_handler.print_map('pdf')
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_pbnReportComposer_released(self):
"""Handle the Open Report in Web Broseer button release.
.. note:: This is an automatic Qt slot
executed when the Next button is released.
"""
self.analysis_handler.print_map('composer')
[docs] def setup_and_run_analysis(self):
"""Execute analysis after the tab is displayed"""
# noinspection PyTypeChecker
self.analysis_handler = AnalysisHandler(self)
self.analysis_handler.setup_and_run_analysis()
[docs] def go_to_step(self, step):
"""Set the stacked widget to the given step, set up the buttons,
and run all operations that should start immediately after
entering the new step.
:param step: The step number to be moved to.
:type step: int
"""
self.stackedWidget.setCurrentIndex(step - 1)
self.lblStep.clear()
# Disable the Next button unless new data already entered
self.pbnNext.setEnabled(self.is_ready_to_next_step(step))
# Enable the Back button unless it's not the first step
self.pbnBack.setEnabled(
step not in [step_kw_category, step_fc_function_1] or
self.parent_step is not None)
# Set Next button label
if (step in [step_kw_title, step_fc_analysis] and
self.parent_step is None):
self.pbnNext.setText(self.tr('Finish'))
elif step == step_fc_summary:
self.pbnNext.setText(self.tr('Run'))
else:
self.pbnNext.setText(self.tr('Next'))
# Run analysis after switching to the new step
if step == step_fc_analysis:
# self.update_MessageViewer_size()
self.setup_and_run_analysis()
# Set lblSelectCategory label if entering the kw mode
# from the ifcw mode
if step == step_kw_category and self.parent_step:
if self.parent_step in [step_fc_hazlayer_from_canvas,
step_fc_hazlayer_from_browser]:
text_label = category_question_hazard
elif self.parent_step in [step_fc_explayer_from_canvas,
step_fc_explayer_from_browser]:
text_label = category_question_exposure
else:
text_label = category_question_aggregation
self.lblSelectCategory.setText(text_label)
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_pbnNext_released(self):
"""Handle the Next button release.
.. note:: This is an automatic Qt slot
executed when the Next button is released.
"""
current_step = self.get_current_step()
# Save keywords if it's the end of the keyword creation mode
if current_step == step_kw_title:
self.save_current_keywords()
if current_step == step_kw_aggregation:
good_age_ratio, sum_age_ratios = self.age_ratios_are_valid()
if not good_age_ratio:
message = self.tr(
'The sum of age ratio default is %s and it is more '
'than 1. Please adjust the age ratio default so that they '
'will not more than 1.' % sum_age_ratios)
if not self.suppress_warning_dialog:
# noinspection PyCallByClass,PyTypeChecker,PyArgumentList
QtGui.QMessageBox.warning(
self, self.tr('InaSAFE'), message)
return
# After any step involving Browser, add selected layer to map canvas
if current_step in [step_fc_hazlayer_from_browser,
step_fc_explayer_from_browser,
step_fc_agglayer_from_browser]:
if not QgsMapLayerRegistry.instance().mapLayersByName(
self.layer.name()):
QgsMapLayerRegistry.instance().addMapLayers([self.layer])
# After the extent selection, save the extent and disconnect signals
if current_step == step_fc_extent:
self.write_extent()
# Determine the new step to be switched
new_step = self.compute_next_step(current_step)
# Prepare the next tab
if new_step == step_kw_category:
self.set_widgets_step_kw_category()
if new_step == step_kw_subcategory:
self.set_widgets_step_kw_subcategory()
if new_step == step_kw_hazard_category:
self.set_widgets_step_kw_hazard_category()
elif new_step == step_kw_layermode:
self.set_widgets_step_kw_layermode()
elif new_step == step_kw_unit:
self.set_widgets_step_kw_unit()
elif new_step == step_kw_classification:
self.set_widgets_step_kw_classification()
elif new_step == step_kw_field:
self.set_widgets_step_kw_field()
elif new_step == step_kw_resample:
self.set_widgets_step_kw_resample()
elif new_step == step_kw_classify:
self.set_widgets_step_kw_classify()
elif new_step == step_kw_extrakeywords:
self.set_widgets_step_kw_extrakeywords()
elif new_step == step_kw_aggregation:
self.set_widgets_step_kw_aggregation()
elif new_step == step_kw_source:
self.set_widgets_step_kw_source()
elif new_step == step_kw_title:
self.set_widgets_step_kw_title()
elif new_step == step_fc_function_1:
self.set_widgets_step_fc_function_1()
elif new_step == step_fc_function_2:
self.set_widgets_step_fc_function_2()
elif new_step == step_fc_function_3:
self.set_widgets_step_fc_function_3()
elif new_step == step_fc_hazlayer_origin:
self.set_widgets_step_fc_hazlayer_origin()
elif new_step == step_fc_hazlayer_from_canvas:
self.set_widgets_step_fc_hazlayer_from_canvas()
elif new_step == step_fc_hazlayer_from_browser:
self.set_widgets_step_fc_hazlayer_from_browser()
elif new_step == step_fc_explayer_origin:
self.set_widgets_step_fc_explayer_origin()
elif new_step == step_fc_explayer_from_canvas:
self.set_widgets_step_fc_explayer_from_canvas()
elif new_step == step_fc_explayer_from_browser:
self.set_widgets_step_fc_explayer_from_browser()
elif new_step == step_fc_disjoint_layers:
self.set_widgets_step_fc_disjoint_layers()
elif new_step == step_fc_agglayer_origin:
self.set_widgets_step_fc_agglayer_origin()
elif new_step == step_fc_agglayer_from_canvas:
self.set_widgets_step_fc_agglayer_from_canvas()
elif new_step == step_fc_agglayer_from_browser:
self.set_widgets_step_fc_agglayer_from_browser()
elif new_step == step_fc_agglayer_disjoint:
self.set_widgets_step_fc_agglayer_disjoint()
elif new_step == step_fc_extent:
self.set_widgets_step_fc_extent()
elif new_step == step_fc_extent_disjoint:
self.set_widgets_step_fc_extent_disjoint()
elif new_step == step_fc_params:
self.set_widgets_step_fc_params()
elif new_step == step_fc_summary:
self.set_widgets_step_fc_summary()
elif new_step == step_fc_analysis:
self.set_widgets_step_fc_analysis()
elif new_step is None:
# Wizard complete
self.accept()
return
else:
# unknown step
pass
self.go_to_step(new_step)
# prevents actions being handled twice
# noinspection PyPep8Naming
@pyqtSignature('')
[docs] def on_pbnBack_released(self):
"""Handle the Back button release.
.. note:: This is an automatic Qt slot
executed when the Back button is released.
"""
current_step = self.get_current_step()
new_step = self.compute_previous_step(current_step)
# set focus to table widgets, as the inactive selection style is gray
if new_step == step_fc_function_1:
self.tblFunctions1.setFocus()
if new_step == step_fc_function_2:
self.tblFunctions2.setFocus()
# Re-connect disconnected signals when coming back to the Extent step
if new_step == step_fc_extent:
self.set_widgets_step_fc_extent()
# Set Next button label
self.pbnNext.setText(self.tr('Next'))
self.pbnNext.setEnabled(True)
self.go_to_step(new_step)
[docs] def is_ready_to_next_step(self, step):
"""Check if the step we enter is initially complete. If so, there is
no reason to block the Next button.
:param step: The present step number.
:type step: int
:returns: True if new step may be enabled.
:rtype: bool
"""
if step == step_kw_category:
return bool(self.selected_category())
if step == step_kw_subcategory:
return bool(self.selected_subcategory())
if step == step_kw_hazard_category:
return bool(self.selected_hazard_category())
if step == step_kw_layermode:
return bool(self.selected_layermode())
if step == step_kw_unit:
return bool(self.selected_unit())
if step == step_kw_classification:
return bool(self.selected_classification())
if step == step_kw_field:
return bool(self.selected_field() or not self.lstFields.count())
if step == step_kw_resample:
return True
if step == step_kw_classify:
# Allow to not classify any values
return True
if step == step_kw_extrakeywords:
return self.are_all_extra_keywords_selected()
if step == step_kw_aggregation:
# Not required
return True
if step == step_kw_source:
# The source_* keywords are not required
return True
if step == step_kw_title:
return bool(self.leTitle.text())
if step == step_fc_function_1:
return bool(self.tblFunctions1.selectedItems())
if step == step_fc_function_2:
return bool(self.tblFunctions2.selectedItems())
if step == step_fc_function_3:
return bool(self.selected_function())
if step == step_fc_hazlayer_origin:
return (bool(self.rbHazLayerFromCanvas.isChecked() or
self.rbHazLayerFromBrowser.isChecked()))
if step == step_fc_hazlayer_from_canvas:
return bool(self.selected_canvas_hazlayer())
if step == step_fc_hazlayer_from_browser:
return self.get_layer_description_from_browser('hazard')[0]
if step == step_fc_explayer_origin:
return (bool(self.rbExpLayerFromCanvas.isChecked() or
self.rbExpLayerFromBrowser.isChecked()))
if step == step_fc_explayer_from_canvas:
return bool(self.selected_canvas_explayer())
if step == step_fc_explayer_from_browser:
return self.get_layer_description_from_browser('exposure')[0]
if step == step_fc_disjoint_layers:
# Never go further if layers disjoint
return False
if step == step_fc_agglayer_origin:
return (bool(self.rbAggLayerFromCanvas.isChecked() or
self.rbAggLayerFromBrowser.isChecked() or
self.rbAggLayerNoAggregation.isChecked()))
if step == step_fc_agglayer_from_canvas:
return bool(self.selected_canvas_agglayer())
if step == step_fc_agglayer_from_browser:
return self.get_layer_description_from_browser('aggregation')[0]
if step == step_fc_agglayer_disjoint:
# Never go further if layers disjoint
return False
if step == step_fc_extent:
return True
if step == step_fc_params:
return True
if step == step_fc_summary:
return True
if step == step_fc_analysis:
return True
return True
[docs] def compute_next_step(self, current_step):
"""Determine the next step to be switched to.
:param current_step: The present step number.
:type current_step: int
:returns: The next step number or None if finished.
:rtype: int
"""
if current_step == step_kw_category:
if self.selected_category() == layer_purpose_aggregation:
new_step = step_kw_field
else:
new_step = step_kw_subcategory
elif current_step == step_kw_subcategory:
if self.selected_category() == layer_purpose_hazard:
new_step = step_kw_hazard_category
else:
new_step = step_kw_layermode
elif current_step == step_kw_hazard_category:
new_step = step_kw_layermode
elif current_step == step_kw_layermode:
if self.selected_layermode() == layer_mode_classified:
if is_point_layer(self.layer) \
and self.selected_category() == layer_purpose_hazard:
# Skip FIELD and CLASSIFICATION for point volcanos
new_step = step_kw_extrakeywords
elif self.classifications_for_layer():
new_step = step_kw_classification
elif is_raster_layer(self.layer):
new_step = step_kw_extrakeywords
else:
new_step = step_kw_field
else:
# CONTINUOUS DATA, ALL GEOMETRIES
new_step = step_kw_unit
elif current_step == step_kw_unit:
if is_raster_layer(self.layer):
if self.selected_category() == layer_purpose_exposure:
# Only go to resample for continuous raster exposures
new_step = step_kw_resample
else:
new_step = step_kw_extrakeywords
else:
# Currently not used, as we don't have continuous vectors
new_step = step_kw_field
elif current_step == step_kw_classification:
if is_raster_layer(self.layer):
new_step = step_kw_classify
else:
new_step = step_kw_field
elif current_step == step_kw_field:
if self.selected_category() == layer_purpose_aggregation:
new_step = step_kw_aggregation
elif self.selected_layermode() == layer_mode_classified and \
self.classifications_for_layer():
new_step = step_kw_classify
else:
new_step = step_kw_extrakeywords
elif current_step == step_kw_resample:
new_step = step_kw_extrakeywords
elif current_step == step_kw_classify:
new_step = step_kw_extrakeywords
elif current_step == step_kw_extrakeywords:
new_step = step_kw_source
elif current_step == step_kw_aggregation:
new_step = step_kw_source
elif current_step == step_kw_source:
new_step = step_kw_title
elif current_step == step_kw_title:
if self.parent_step:
# Come back to the parent thread
new_step = self.parent_step
self.parent_step = None
self.is_selected_layer_keywordless = False
self.set_mode_label_to_ifcw()
else:
# Wizard complete
new_step = None
elif current_step == step_fc_hazlayer_origin:
if self.rbHazLayerFromCanvas.isChecked():
new_step = step_fc_hazlayer_from_canvas
else:
new_step = step_fc_hazlayer_from_browser
elif current_step in [step_fc_hazlayer_from_canvas,
step_fc_hazlayer_from_browser]:
if self.is_selected_layer_keywordless:
# insert keyword creation thread here
self.parent_step = current_step
self.existing_keywords = None
self.set_mode_label_to_keywords_creation()
new_step = step_kw_category
else:
new_step = step_fc_explayer_origin
elif current_step == step_fc_explayer_origin:
if self.rbExpLayerFromCanvas.isChecked():
new_step = step_fc_explayer_from_canvas
else:
new_step = step_fc_explayer_from_browser
elif current_step in [step_fc_explayer_from_canvas,
step_fc_explayer_from_browser]:
if self.is_selected_layer_keywordless:
# insert keyword creation thread here
self.parent_step = current_step
self.existing_keywords = None
self.set_mode_label_to_keywords_creation()
new_step = step_kw_category
else:
if not self.layers_intersect(self.hazard_layer,
self.exposure_layer):
new_step = step_fc_disjoint_layers
else:
new_step = step_fc_agglayer_origin
elif current_step == step_fc_disjoint_layers:
new_step = step_fc_agglayer_origin
elif current_step == step_fc_agglayer_origin:
if self.rbAggLayerFromCanvas.isChecked():
new_step = step_fc_agglayer_from_canvas
elif self.rbAggLayerFromBrowser.isChecked():
new_step = step_fc_agglayer_from_browser
else:
new_step = step_fc_extent
elif current_step in [step_fc_agglayer_from_canvas,
step_fc_agglayer_from_browser]:
if self.is_selected_layer_keywordless:
# insert keyword creation thread here
self.parent_step = current_step
self.existing_keywords = None
self.set_mode_label_to_keywords_creation()
new_step = step_kw_category
else:
flag = self.layers_intersect(
self.exposure_layer, self.aggregation_layer)
if not flag:
new_step = step_fc_agglayer_disjoint
else:
new_step = step_fc_extent
elif current_step == step_fc_agglayer_disjoint:
new_step = step_fc_extent
elif current_step == step_fc_extent:
if self.validate_extent():
new_step = step_fc_params
else:
new_step = step_fc_extent_disjoint
elif current_step in [step_fc_function_1, step_fc_function_2,
step_fc_function_3,
step_fc_params, step_fc_summary]:
new_step = current_step + 1
elif current_step == step_fc_analysis:
new_step = None # Wizard complete
elif current_step < self.stackedWidget.count():
raise Exception('Unhandled step')
else:
raise Exception('Unexpected number of steps')
# Skip the extra_keywords tab if no extra keywords available:
if (new_step == step_kw_extrakeywords and not
self.additional_keywords_for_the_layer()):
new_step = step_kw_source
return new_step
[docs] def compute_previous_step(self, current_step):
"""Determine the previous step to be switched to (by the Back button).
:param current_step: The present step number.
:type current_step: int
:returns: The previous step number.
:rtype: int
"""
if current_step == step_kw_category:
if self.parent_step:
# Come back to the parent thread
self.set_mode_label_to_ifcw()
new_step = self.parent_step
self.parent_step = None
else:
new_step = step_kw_category
elif current_step == step_kw_subcategory:
new_step = step_kw_category
elif current_step == step_kw_hazard_category:
new_step = step_kw_subcategory
elif current_step == step_kw_layermode:
if self.selected_category() == layer_purpose_hazard:
new_step = step_kw_hazard_category
else:
new_step = step_kw_subcategory
elif current_step == step_kw_unit:
new_step = step_kw_layermode
elif current_step == step_kw_classification:
new_step = step_kw_layermode
elif current_step == step_kw_field:
if self.selected_category() == layer_purpose_aggregation:
new_step = step_kw_category
elif self.selected_layermode() == layer_mode_continuous:
new_step = step_kw_unit
elif self.classifications_for_layer():
new_step = step_kw_classification
else:
new_step = step_kw_layermode
elif current_step == step_kw_resample:
new_step = step_kw_unit
elif current_step == step_kw_classify:
if is_raster_layer(self.layer):
new_step = step_kw_classification
else:
new_step = step_kw_field
elif current_step == step_kw_aggregation:
new_step = step_kw_field
elif current_step == step_kw_extrakeywords:
if self.selected_layermode() == layer_mode_classified:
if self.selected_classification():
new_step = step_kw_classify
elif self.selected_field():
new_step = step_kw_field
else:
new_step = step_kw_layermode
else:
if self.selected_allowresampling() is not None:
new_step = step_kw_resample
else:
new_step = step_kw_unit
elif current_step == step_kw_source:
if self.selected_category() == layer_purpose_aggregation:
new_step = step_kw_aggregation
elif self.selected_extra_keywords():
new_step = step_kw_extrakeywords
# otherwise behave like it was step_kw_extrakeywords
elif self.selected_layermode() == layer_mode_classified:
if self.selected_classification():
new_step = step_kw_classify
elif self.selected_field():
new_step = step_kw_field
else:
new_step = step_kw_layermode
else:
if self.selected_allowresampling() is not None:
new_step = step_kw_resample
else:
new_step = step_kw_unit
elif current_step == step_kw_title:
new_step = step_kw_source
elif current_step == step_fc_function_1:
new_step = step_fc_function_1
elif current_step == step_fc_hazlayer_from_browser:
new_step = step_fc_hazlayer_origin
elif current_step == step_fc_explayer_origin:
if self.rbHazLayerFromCanvas.isChecked():
new_step = step_fc_hazlayer_from_canvas
else:
new_step = step_fc_hazlayer_from_browser
elif current_step == step_fc_explayer_from_browser:
new_step = step_fc_explayer_origin
elif current_step == step_fc_disjoint_layers:
if self.rbExpLayerFromCanvas.isChecked():
new_step = step_fc_explayer_from_canvas
else:
new_step = step_fc_explayer_from_browser
elif current_step == step_fc_agglayer_origin:
if self.rbExpLayerFromCanvas.isChecked():
new_step = step_fc_explayer_from_canvas
else:
new_step = step_fc_explayer_from_browser
elif current_step == step_fc_agglayer_from_browser:
new_step = step_fc_agglayer_origin
elif current_step == step_fc_agglayer_disjoint:
if self.rbAggLayerFromCanvas.isChecked():
new_step = step_fc_agglayer_from_canvas
else:
new_step = step_fc_agglayer_from_browser
elif current_step == step_fc_extent:
if self.rbAggLayerFromCanvas.isChecked():
new_step = step_fc_agglayer_from_canvas
elif self.rbAggLayerFromBrowser.isChecked():
new_step = step_fc_agglayer_from_browser
else:
new_step = step_fc_agglayer_origin
elif current_step == step_fc_params:
new_step = step_fc_extent
else:
new_step = current_step - 1
return new_step
# ===========================
# COMMON METHODS
# ===========================
[docs] def get_current_step(self):
"""Return current step of the wizard.
:returns: Current step of the wizard.
:rtype: int
"""
return self.stackedWidget.currentIndex() + 1
[docs] def get_layer_geometry_id(self, layer=None):
"""Obtain layer mode of a given layer.
If no layer specified, the current layer is used
:param layer : layer to examine
:type layer: QgsMapLayer or None
:returns: The layer mode.
:rtype: str
"""
if not layer:
layer = self.layer
if is_raster_layer(layer):
return 'raster'
elif is_point_layer(layer):
return 'point'
elif is_polygon_layer(layer):
return 'polygon'
else:
return 'line'
[docs] def get_existing_keyword(self, keyword):
"""Obtain an existing keyword's value.
:param keyword: A keyword from keywords.
:type keyword: str
:returns: The value of the keyword.
:rtype: str
"""
if self.existing_keywords is None:
return None
if keyword is not None:
return self.existing_keywords.get(keyword, None)
else:
return None
[docs] def get_keywords(self):
"""Obtain the state of the dialog as a keywords dict.
:returns: Keywords reflecting the state of the dialog.
:rtype: dict
"""
keywords = {}
keywords['layer_geometry'] = self.get_layer_geometry_id()
if self.selected_category():
keywords['layer_purpose'] = self.selected_category()['key']
if keywords['layer_purpose'] == 'aggregation':
keywords.update(self.get_aggregation_attributes())
if self.selected_subcategory():
key = self.selected_category()['key']
keywords[key] = self.selected_subcategory()['key']
if self.selected_hazard_category():
keywords['hazard_category'] \
= self.selected_hazard_category()['key']
if self.selected_layermode():
keywords['layer_mode'] = self.selected_layermode()['key']
if self.selected_unit():
if self.selected_category() == layer_purpose_hazard:
key = continuous_hazard_unit['key']
else:
key = exposure_unit['key']
keywords[key] = self.selected_unit()['key']
if self.selected_allowresampling() is not None:
keywords['allow_resampling'] = (
self.selected_allowresampling() and 'true' or 'false')
if self.lstFields.currentItem():
field_keyword = self.field_keyword_for_the_layer()
keywords[field_keyword] = self.lstFields.currentItem().text()
if self.selected_classification():
geom = 'raster' if is_raster_layer(self.layer) else 'vector'
key = '%s_%s_classification' % (geom,
self.selected_category()['key'])
keywords[key] = self.selected_classification()['key']
value_map = self.selected_mapping()
if value_map:
keywords['value_map'] = json.dumps(value_map)
extra_keywords = self.selected_extra_keywords()
for key in extra_keywords:
keywords[key] = extra_keywords[key]
if self.leSource.text():
keywords['source'] = get_unicode(self.leSource.text())
if self.leSource_url.text():
keywords['url'] = get_unicode(self.leSource_url.text())
if self.leSource_scale.text():
keywords['scale'] = get_unicode(self.leSource_scale.text())
if self.leSource_date.text():
keywords['date'] = get_unicode(self.leSource_date.text())
if self.leSource_license.text():
keywords['license'] = get_unicode(self.leSource_license.text())
if self.leTitle.text():
keywords['title'] = get_unicode(self.leTitle.text())
return keywords
[docs] def save_current_keywords(self):
"""Save keywords to the layer.
It will write out the keywords for the current layer.
This method is based on the KeywordsDialog class.
"""
current_keywords = self.get_keywords()
try:
self.keyword_io.write_keywords(
layer=self.layer, keywords=current_keywords)
except InaSAFEError, e:
error_message = get_error_message(e)
# noinspection PyCallByClass,PyTypeChecker,PyArgumentList
QtGui.QMessageBox.warning(
self, self.tr('InaSAFE'),
((self.tr(
'An error was encountered when saving the following '
'keywords:\n %s') % error_message.to_html())))
if self.dock is not None:
# noinspection PyUnresolvedReferences
self.dock.get_layers()
# noinspection PyUnresolvedReferences,PyMethodMayBeStatic
[docs] def auto_select_one_item(self, list_widget):
"""Select item in the list in list_widget if it's the only item.
:param list_widget: The list widget that want to be checked.
:type list_widget: QListWidget
"""
if list_widget.count() == 1 and list_widget.currentRow() == -1:
list_widget.setCurrentRow(0)