Exemplo n.º 1
0
    def save_hazard_layer(self, hazard_path):
        # download or copy hazard path/url
        # It is a single tif file
        if not hazard_path and not os.path.exists(self.hazard_path):
            raise IOError("Hazard file not specified")

        if hazard_path:
            temp_hazard = download_file(hazard_path)
            shutil.copy(temp_hazard, self.hazard_path)

        # copy qml and metadata
        shutil.copy(self.ash_fixtures_dir("hazard.qml"), self.working_dir_path("hazard.qml"))

        keyword_io = KeywordIO()

        keywords = {
            "hazard_category": u"single_event",
            "keyword_version": u"3.5",
            "title": u"Ash Fall",
            "hazard": u"volcanic_ash",
            "continuous_hazard_unit": u"centimetres",
            "layer_geometry": u"raster",
            "layer_purpose": u"hazard",
            "layer_mode": u"continuous",
        }

        hazard_layer = read_qgis_layer(self.hazard_path, "Ash Fall")
        keyword_io.write_keywords(hazard_layer, keywords)
Exemplo n.º 2
0
    def save_hazard_layer(self, hazard_path):
        # download or copy hazard path/url
        # It is a single tif file
        if not hazard_path and not os.path.exists(self.hazard_path):
            raise IOError('Hazard file not specified')

        if hazard_path:
            temp_hazard = download_file(hazard_path)
            shutil.copy(temp_hazard, self.hazard_path)

        # copy qml and metadata
        shutil.copy(self.ash_fixtures_dir('hazard.qml'),
                    self.working_dir_path('hazard.qml'))

        keyword_io = KeywordIO()

        keywords = {
            'hazard_category': u'single_event',
            'keyword_version': u'3.5',
            'title': u'Ash Fall',
            'hazard': u'volcanic_ash',
            'continuous_hazard_unit': u'centimetres',
            'layer_geometry': u'raster',
            'layer_purpose': u'hazard',
            'layer_mode': u'continuous'
        }

        hazard_layer = read_qgis_layer(self.hazard_path, 'Ash Fall')
        keyword_io.write_keywords(hazard_layer, keywords)
Exemplo n.º 3
0
    def create_keyword_file(self, algorithm):
        """Create keyword file for the raster file created.

        Basically copy a template from keyword file in converter data
        and add extra keyword (usually a title)

        :param algorithm: Which re-sampling algorithm to use.
            valid options are 'nearest' (for nearest neighbour), 'invdist'
            (for inverse distance), 'average' (for moving average). Defaults
            to 'nearest' if not specified. Note that passing re-sampling alg
            parameters is currently not supported. If None is passed it will
            be replaced with 'nearest'.
        :type algorithm: str
        """
        keyword_io = KeywordIO()

        classes = {}
        for item in earthquake_mmi_scale['classes']:
            classes[item['key']] = [
                item['numeric_default_min'], item['numeric_default_max']]

        keywords = {
            'hazard': hazard_earthquake['key'],
            'hazard_category': hazard_category_single_event['key'],
            'keyword_version': inasafe_keyword_version,
            'layer_geometry': layer_geometry_raster['key'],
            'layer_mode': layer_mode_continuous['key'],
            'layer_purpose': layer_purpose_hazard['key'],
            'continuous_hazard_unit': unit_mmi['key'],
            'classification': earthquake_mmi_scale['key'],
            'thresholds': classes
        }

        if self.algorithm_name:
            layer_path = os.path.join(
                self.output_dir, '%s-%s.tif' % (
                        self.output_basename, algorithm))
        else:
            layer_path = os.path.join(
                self.output_dir, '%s.tif' % self.output_basename)

        # append title and source to the keywords file
        if len(self.title.strip()) == 0:
            keyword_title = self.output_basename
        else:
            keyword_title = self.title

        keywords['title'] = keyword_title

        hazard_layer = QgsRasterLayer(layer_path, keyword_title)

        if not hazard_layer.isValid():
            raise InvalidLayerError()

        keyword_io.write_keywords(hazard_layer, keywords)
Exemplo n.º 4
0
def buffer_points(point_layer, radii, hazard_zone_attribute, output_crs):
    """Buffer points for each point with defined radii.

    This function is used for making buffer of volcano point hazard.

    :param point_layer: A point layer to buffer.
    :type point_layer: QgsVectorLayer

    :param radii: Desired approximate radii in kilometers (must be
        monotonically ascending). Can be either one number or list of numbers
    :type radii: int, list

    :param hazard_zone_attribute: The name of the attributes representing
        hazard zone.
    :type hazard_zone_attribute: str

    :param output_crs: The output CRS.
    :type output_crs: QgsCoordinateReferenceSystem

    :return: Vector polygon layer representing circle in point layer CRS.
    :rtype: QgsVectorLayer
    """
    if not isinstance(radii, list):
        radii = [radii]

    if not is_point_layer(point_layer):
        message = ('Input hazard must be a vector point layer. I got %s '
                   'with layer type %s' %
                   (point_layer.name(), point_layer.type()))
        raise Exception(message)

    # Check that radii are monotonically increasing
    monotonically_increasing_flag = all(x < y
                                        for x, y in zip(radii, radii[1:]))
    if not monotonically_increasing_flag:
        raise RadiiException(RadiiException.suggestion)

    hazard_file_path = unique_filename(suffix='-polygon-volcano.shp')
    fields = point_layer.pendingFields()
    fields.append(QgsField(hazard_zone_attribute, QVariant.Double))
    writer = QgsVectorFileWriter(hazard_file_path, 'utf-8', fields,
                                 QGis.WKBPolygon, output_crs, 'ESRI Shapefile')
    input_crs = point_layer.crs()

    center = point_layer.extent().center()
    utm = None
    if output_crs.authid() == 'EPSG:4326':
        utm = QgsCoordinateReferenceSystem(
            get_utm_epsg(center.x(), center.y(), input_crs))
        transform = QgsCoordinateTransform(point_layer.crs(), utm)

    else:
        transform = QgsCoordinateTransform(point_layer.crs(), output_crs)

    for point in point_layer.getFeatures():
        geom = point.geometry()
        geom.transform(transform)

        inner_rings = None
        for radius in radii:
            attributes = point.attributes()
            # Generate circle polygon

            circle = geom.buffer(radius * 1000.0, 30)

            if inner_rings:
                circle.addRing(inner_rings)
            inner_rings = circle.asPolygon()[0]

            new_buffer = QgsFeature()

            if output_crs.authid() == 'EPSG:4326':
                circle.transform(QgsCoordinateTransform(utm, output_crs))

            new_buffer.setGeometry(circle)
            attributes.append(radius)
            new_buffer.setAttributes(attributes)

            writer.addFeature(new_buffer)

    del writer
    vector_layer = QgsVectorLayer(hazard_file_path, 'Polygons', 'ogr')

    keyword_io = KeywordIO()
    try:
        keywords = keyword_io.read_keywords(point_layer)
        keyword_io.write_keywords(vector_layer, keywords)
    except NoKeywordsFoundError:
        pass
    return vector_layer
Exemplo n.º 5
0
    def create_keyword_file(self, algorithm):
        """Create keyword file for the raster file created.

        Basically copy a template from keyword file in converter data
        and add extra keyword (usually a title)

        :param algorithm: Which re-sampling algorithm to use.
            valid options are 'nearest' (for nearest neighbour), 'invdist'
            (for inverse distance), 'average' (for moving average). Defaults
            to 'nearest' if not specified. Note that passing re-sampling alg
            parameters is currently not supported. If None is passed it will
            be replaced with 'nearest'.
        :type algorithm: str
        """
        keyword_io = KeywordIO()

        # Set thresholds for each exposure
        mmi_default_classes = default_classification_thresholds(
            earthquake_mmi_scale)
        mmi_default_threshold = {
            earthquake_mmi_scale['key']: {
                'active': True,
                'classes': mmi_default_classes
            }
        }
        generic_default_classes = default_classification_thresholds(
            generic_hazard_classes)
        generic_default_threshold = {
            generic_hazard_classes['key']: {
                'active': True,
                'classes': generic_default_classes
            }
        }

        threshold_keyword = {}
        for exposure in exposure_all:
            # Not all exposure is supported by earthquake_mmi_scale
            if exposure in earthquake_mmi_scale['exposures']:
                threshold_keyword[exposure['key']] = mmi_default_threshold
            else:
                threshold_keyword[exposure['key']] = generic_default_threshold

        extra_keywords = {
            extra_keyword_earthquake_latitude['key']:
            self.latitude,
            extra_keyword_earthquake_longitude['key']:
            self.longitude,
            extra_keyword_earthquake_magnitude['key']:
            self.magnitude,
            extra_keyword_earthquake_depth['key']:
            self.depth,
            extra_keyword_earthquake_description['key']:
            self.description,
            extra_keyword_earthquake_location['key']:
            self.location,
            extra_keyword_earthquake_event_time['key']:
            self.time.strftime('%Y-%m-%dT%H:%M:%S'),
            extra_keyword_time_zone['key']:
            self.time_zone,
            extra_keyword_earthquake_x_minimum['key']:
            self.x_minimum,
            extra_keyword_earthquake_x_maximum['key']:
            self.x_maximum,
            extra_keyword_earthquake_y_minimum['key']:
            self.y_minimum,
            extra_keyword_earthquake_y_maximum['key']:
            self.y_maximum,
            extra_keyword_earthquake_event_id['key']:
            self.event_id
        }
        for key, value in self.extra_keywords.items():
            extra_keywords[key] = value

        # Delete empty element.
        empty_keys = []
        for key, value in extra_keywords.items():
            if value is None:
                empty_keys.append(key)
        for empty_key in empty_keys:
            extra_keywords.pop(empty_key)
        keywords = {
            'hazard': hazard_earthquake['key'],
            'hazard_category': hazard_category_single_event['key'],
            'keyword_version': inasafe_keyword_version,
            'layer_geometry': layer_geometry_raster['key'],
            'layer_mode': layer_mode_continuous['key'],
            'layer_purpose': layer_purpose_hazard['key'],
            'continuous_hazard_unit': unit_mmi['key'],
            'classification': earthquake_mmi_scale['key'],
            'thresholds': threshold_keyword,
            'extra_keywords': extra_keywords,
            'active_band': 1
        }

        if self.algorithm_name:
            layer_path = os.path.join(
                self.output_dir,
                '%s-%s.tif' % (self.output_basename, algorithm))
        else:
            layer_path = os.path.join(self.output_dir,
                                      '%s.tif' % self.output_basename)

        # append title and source to the keywords file
        if len(self.title.strip()) == 0:
            keyword_title = self.output_basename
        else:
            keyword_title = self.title

        keywords['title'] = keyword_title

        hazard_layer = QgsRasterLayer(layer_path, keyword_title)

        if not hazard_layer.isValid():
            raise InvalidLayerError()

        keyword_io.write_keywords(hazard_layer, keywords)
Exemplo n.º 6
0
class FieldMappingDialog(QDialog, FORM_CLASS):

    """Dialog implementation class for the InaSAFE field mapping tool."""

    def __init__(self, parent=None, iface=None, setting=None):
        """Constructor."""
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.setWindowTitle(self.tr('InaSAFE Field Mapping Tool'))
        icon = resources_path('img', 'icons', 'show-mapping-tool.svg')
        self.setWindowIcon(QIcon(icon))
        self.parent = parent
        self.iface = iface
        if setting is None:
            setting = QSettings()
        self.setting = setting

        self.keyword_io = KeywordIO()

        self.layer = None
        self.metadata = {}

        self.layer_input_layout = QHBoxLayout()
        self.layer_label = QLabel(tr('Layer'))
        self.layer_combo_box = QgsMapLayerComboBox()
        # Filter only for Polygon and Point
        self.layer_combo_box.setFilters(
            QgsMapLayerProxyModel.PolygonLayer |
            QgsMapLayerProxyModel.PointLayer)
        # Filter out a layer that don't have layer groups
        excepted_layers = []
        for i in range(self.layer_combo_box.count()):
            layer = self.layer_combo_box.layer(i)
            try:
                keywords = self.keyword_io.read_keywords(layer)
            except (KeywordNotFoundError, NoKeywordsFoundError):
                excepted_layers.append(layer)
                continue
            layer_purpose = keywords.get('layer_purpose')
            if not layer_purpose:
                excepted_layers.append(layer)
                continue
            if layer_purpose == layer_purpose_exposure['key']:
                layer_subcategory = keywords.get('exposure')
            elif layer_purpose == layer_purpose_hazard['key']:
                layer_subcategory = keywords.get('hazard')
            else:
                layer_subcategory = None

            field_groups = get_field_groups(layer_purpose, layer_subcategory)
            if len(field_groups) == 0:
                excepted_layers.append(layer)
                continue
        self.layer_combo_box.setExceptedLayerList(excepted_layers)

        # Select the active layer.
        if self.iface.activeLayer():
            found = self.layer_combo_box.findText(
                self.iface.activeLayer().name())
            if found > -1:
                self.layer_combo_box.setLayer(self.iface.activeLayer())

        self.field_mapping_widget = None
        self.main_stacked_widget.setCurrentIndex(1)

        # Input
        self.layer_input_layout.addWidget(self.layer_label)
        self.layer_input_layout.addWidget(self.layer_combo_box)

        self.header_label = QLabel()
        self.header_label.setWordWrap(True)
        self.main_layout.addWidget(self.header_label)
        self.main_layout.addLayout(self.layer_input_layout)

        # Signal
        self.layer_combo_box.layerChanged.connect(self.set_layer)

        if self.layer_combo_box.currentLayer():
            self.set_layer(self.layer_combo_box.currentLayer())

        # Set up things for context help
        self.help_button = self.button_box.button(QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)

        # Set up things for ok button
        self.ok_button = self.button_box.button(QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)

        # Set up things for cancel button
        self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel)
        self.cancel_button.clicked.connect(self.reject)

    def set_layer(self, layer=None, keywords=None):
        """Set layer and update UI accordingly.

        :param layer: A QgsVectorLayer.
        :type layer: QgsVectorLayer

        :param keywords: Keywords for the layer.
        :type keywords: dict, None
        """
        if self.field_mapping_widget is not None:
            self.field_mapping_widget.setParent(None)
            self.field_mapping_widget.close()
            self.field_mapping_widget.deleteLater()
            self.main_layout.removeWidget(self.field_mapping_widget)

        if layer:
            self.layer = layer
        else:
            self.layer = self.layer_combo_box.currentLayer()
        if not self.layer:
            return

        if keywords is not None:
            self.metadata = keywords
        else:
            # Always read from metadata file.
            try:
                self.metadata = self.keyword_io.read_keywords(self.layer)
            except (
                    NoKeywordsFoundError,
                    KeywordNotFoundError,
                    MetadataReadError) as e:
                raise e
        if 'inasafe_default_values' not in self.metadata:
            self.metadata['inasafe_default_values'] = {}
        if 'inasafe_fields' not in self.metadata:
            self.metadata['inasafe_fields'] = {}
        self.field_mapping_widget = FieldMappingWidget(
            parent=self, iface=self.iface)
        self.field_mapping_widget.set_layer(self.layer, self.metadata)
        self.field_mapping_widget.show()
        self.main_layout.addWidget(self.field_mapping_widget)

        # Set header label
        group_names = [
            self.field_mapping_widget.tabText(i) for i in range(
                self.field_mapping_widget.count())]
        if len(group_names) == 0:
            header_text = tr(
                'There is no field group for this layer. Please select '
                'another layer.')
            self.header_label.setText(header_text)
            return
        elif len(group_names) == 1:
            pretty_group_name = group_names[0]
        elif len(group_names) == 2:
            pretty_group_name = group_names[0] + tr(' and ') + group_names[1]
        else:
            pretty_group_name = ', '.join(group_names[:-1])
            pretty_group_name += tr(', and {0}').format(group_names[-1])
        header_text = tr(
            'Please fill the information for every tab to determine the '
            'attribute for {0} group.').format(pretty_group_name)
        self.header_label.setText(header_text)

    @pyqtSlot()
    @pyqtSignature('bool')  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the stacked widget.

        .. versionadded: 3.2.1

        :param flag: Flag indicating whether help should be shown or hidden.
        :type flag: bool
        """
        if flag:
            self.help_button.setText(self.tr('Hide Help'))
            self.show_help()
        else:
            self.help_button.setText(self.tr('Show Help'))
            self.hide_help()

    def hide_help(self):
        """Hide the usage info from the user.

        .. versionadded: 3.2.1
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = field_mapping_help()

        string += message.to_html()
        string += footer

        self.help_web_view.setHtml(string)

    def save_metadata(self):
        """Save metadata based on the field mapping state."""
        metadata = self.field_mapping_widget.get_field_mapping()
        for key, value in metadata['fields'].items():
            # Delete the key if it's set to None
            if key in self.metadata['inasafe_default_values']:
                self.metadata['inasafe_default_values'].pop(key)
            if value is None or value == []:
                if key in self.metadata['inasafe_fields']:
                    self.metadata['inasafe_fields'].pop(key)
            else:
                self.metadata['inasafe_fields'][key] = value

        for key, value in metadata['values'].items():
            # Delete the key if it's set to None
            if key in self.metadata['inasafe_fields']:
                self.metadata['inasafe_fields'].pop(key)
            if value is None:
                if key in self.metadata['inasafe_default_values']:
                    self.metadata['inasafe_default_values'].pop(key)
            else:
                self.metadata['inasafe_default_values'][key] = value

        # Save metadata
        try:
            self.keyword_io.write_keywords(
                layer=self.layer, keywords=self.metadata)
        except InaSAFEError, e:
            error_message = get_error_message(e)
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QMessageBox.warning(
                self, self.tr('InaSAFE'),
                ((self.tr(
                    'An error was encountered when saving the following '
                    'keywords:\n %s') % error_message.to_html())))

        # Update setting fir recent value
        if self.metadata.get('inasafe_default_values'):
            for key, value in \
                    self.metadata['inasafe_default_values'].items():
                set_inasafe_default_value_qsetting(
                    self.setting, key, RECENT, value)
Exemplo n.º 7
0
class KeywordsDialog(QtGui.QDialog, FORM_CLASS):
    """Dialog implementation class for the InaSAFE keywords editor."""

    def __init__(self, parent, iface, dock=None, layer=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: Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param dock: Dock widget instance that we can notify of changes to
            the keywords. Optional.
        :type dock: Dock
        """
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(self.tr(
            'InaSAFE %s Keywords Editor' % get_version()))
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = dock
        self.defaults = None

        # string constants
        self.global_default_string = metadata.global_default_attribute['name']
        self.do_not_use_string = metadata.do_not_use_attribute['name']
        self.global_default_data = metadata.global_default_attribute['id']
        self.do_not_use_data = metadata.do_not_use_attribute['id']

        if layer is None:
            self.layer = self.iface.activeLayer()
        else:
            self.layer = layer

        self.keyword_io = KeywordIO()

        # note the keys should remain untranslated as we need to write
        # english to the keywords file. The keys will be written as user data
        # in the combo entries.
        # .. seealso:: http://www.voidspace.org.uk/python/odict.html
        self.standard_exposure_list = OrderedDict(
            [('population', self.tr('population')),
             ('structure', self.tr('structure')),
             ('road', self.tr('road')),
             ('Not Set', self.tr('Not Set'))])
        self.standard_hazard_list = OrderedDict(
            [('earthquake [MMI]', self.tr('earthquake [MMI]')),
             ('tsunami [m]', self.tr('tsunami [m]')),
             ('tsunami [wet/dry]', self.tr('tsunami [wet/dry]')),
             ('tsunami [feet]', self.tr('tsunami [feet]')),
             ('flood [m]', self.tr('flood [m]')),
             ('flood [wet/dry]', self.tr('flood [wet/dry]')),
             ('flood [feet]', self.tr('flood [feet]')),
             ('tephra [kg2/m2]', self.tr('tephra [kg2/m2]')),
             ('volcano', self.tr('volcano')),
             ('generic [categorised]', self.tr('generic [categorised]')),
             ('Not Set', self.tr('Not Set'))])

        # noinspection PyUnresolvedReferences
        self.lstKeywords.itemClicked.connect(self.edit_key_value_pair)

        # Set up help dialog showing logic.
        help_button = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        if self.layer is not None and is_polygon_layer(self.layer):
            # set some initial ui state:
            self.defaults = get_defaults()
            self.radPredefined.setChecked(True)
            self.dsbFemaleRatioDefault.blockSignals(True)
            self.dsbFemaleRatioDefault.setValue(self.defaults['FEMALE_RATIO'])
            self.dsbFemaleRatioDefault.blockSignals(False)

            self.dsbYouthRatioDefault.blockSignals(True)
            self.dsbYouthRatioDefault.setValue(self.defaults['YOUTH_RATIO'])
            self.dsbYouthRatioDefault.blockSignals(False)

            self.dsbAdultRatioDefault.blockSignals(True)
            self.dsbAdultRatioDefault.setValue(self.defaults['ADULT_RATIO'])
            self.dsbAdultRatioDefault.blockSignals(False)

            self.dsbElderlyRatioDefault.blockSignals(True)
            self.dsbElderlyRatioDefault.setValue(
                self.defaults['ELDERLY_RATIO'])
            self.dsbElderlyRatioDefault.blockSignals(False)
        else:
            self.radPostprocessing.hide()
            self.tab_widget.removeTab(1)

        if self.layer:
            self.load_state_from_keywords()

        # add a reload from keywords button
        reload_button = self.buttonBox.addButton(
            self.tr('Reload'), QtGui.QDialogButtonBox.ActionRole)
        reload_button.clicked.connect(self.load_state_from_keywords)
        self.resize_dialog()
        self.tab_widget.setCurrentIndex(0)
        # TODO No we should not have test related stuff in prod code. TS
        self.test = False

    def set_layer(self, layer):
        """Set the layer associated with the keyword editor.

        :param layer: Layer whose keywords should be edited.
        :type layer: QgsMapLayer
        """
        self.layer = layer
        self.load_state_from_keywords()

    # noinspection PyMethodMayBeStatic
    def show_help(self):
        """Load the help text for the keywords dialog."""
        show_context_help(context='keywords_editor')

    def toggle_postprocessing_widgets(self):
        """Hide or show the post processing widgets depending on context."""
        LOGGER.debug('togglePostprocessingWidgets')
        # TODO Too much baggage here. Can't we just disable/enable the tab? TS
        postprocessing_flag = self.radPostprocessing.isChecked()
        self.cboSubcategory.setVisible(not postprocessing_flag)
        self.lblSubcategory.setVisible(not postprocessing_flag)
        self.show_aggregation_attribute()
        self.show_female_ratio_attribute()
        self.show_female_ratio_default()
        self.show_youth_ratio_attribute()
        self.show_youth_ratio_default()
        self.show_adult_ratio_attribute()
        self.show_adult_ratio_default()
        self.show_elderly_ratio_attribute()
        self.show_elderly_ratio_default()
        # Also enable/disable the aggregation tab
        self.aggregation_tab.setEnabled(postprocessing_flag)

    def show_aggregation_attribute(self):
        """Hide or show the aggregation attribute in the keyword editor dialog.
        """
        box = self.cboAggregationAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['AGGR_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer,
            [QtCore.QVariant.Int, QtCore.QVariant.String],
            current_keyword)
        box.addItems(fields)
        if attribute_position is None:
            box.setCurrentIndex(0)
        else:
            box.setCurrentIndex(attribute_position)

    def show_female_ratio_attribute(self):
        """Hide or show the female ratio attribute in the dialog.
        """
        box = self.cboFemaleRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['FEMALE_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_female_ratio_default(self):
        """Hide or show the female ratio default attribute in the dialog.
        """
        box = self.dsbFemaleRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['FEMALE_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['FEMALE_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_youth_ratio_attribute(self):
        """Hide or show the youth ratio attribute in the dialog.
        """
        box = self.cboYouthRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['YOUTH_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_youth_ratio_default(self):
        """Hide or show the youth ratio default attribute in the dialog.
        """
        box = self.dsbYouthRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['YOUTH_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['YOUTH_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_adult_ratio_attribute(self):
        """Hide or show the adult ratio attribute in the dialog.
        """
        box = self.cboAdultRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['ADULT_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_adult_ratio_default(self):
        """Hide or show the adult ratio default attribute in the dialog.
        """
        box = self.dsbAdultRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['ADULT_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['ADULT_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_elderly_ratio_attribute(self):
        """Show the elderly ratio attribute in the dialog.
        """
        box = self.cboElderlyRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['ELDERLY_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_elderly_ratio_default(self):
        """Show the elderly ratio default attribute in the dialog.
        """
        box = self.dsbElderlyRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['ELDERLY_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['ELDERLY_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboAggregationAttribute_currentIndexChanged(self, index=None):
        """Handler for aggregation attribute combo change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return
        self.add_list_entry(
            self.defaults['AGGR_ATTR_KEY'],
            self.cboAggregationAttribute.currentText())

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboFemaleRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for female ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboFemaleRatioAttribute.currentIndex()
        data = self.cboFemaleRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbFemaleRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['FEMALE_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(
                    self.defaults['FEMALE_RATIO_KEY'],
                    self.dsbFemaleRatioDefault.value())
        else:
            self.dsbFemaleRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY'])
        self.add_list_entry(self.defaults['FEMALE_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboYouthRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for youth ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboYouthRatioAttribute.currentIndex()
        data = self.cboYouthRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbYouthRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['YOUTH_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(
                    self.defaults['YOUTH_RATIO_KEY'],
                    self.dsbYouthRatioDefault.value())
        else:
            self.dsbYouthRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY'])
        self.add_list_entry(self.defaults['YOUTH_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboAdultRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for adult ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboAdultRatioAttribute.currentIndex()
        data = self.cboAdultRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbAdultRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['ADULT_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(
                    self.defaults['ADULT_RATIO_KEY'],
                    self.dsbAdultRatioDefault.value())
        else:
            self.dsbAdultRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY'])
        self.add_list_entry(self.defaults['ADULT_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboElderlyRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for elderly ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboElderlyRatioAttribute.currentIndex()
        data = self.cboElderlyRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbElderlyRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['ELDERLY_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(
                    self.defaults['ELDERLY_RATIO_KEY'],
                    self.dsbElderlyRatioDefault.value())
        else:
            self.dsbElderlyRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY'])
        self.add_list_entry(self.defaults['ELDERLY_RATIO_ATTR_KEY'], data)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('double')
    def on_dsbFemaleRatioDefault_valueChanged(self, value):
        """Handler for female ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbFemaleRatioDefault
        if box.isEnabled():
            self.add_list_entry(
                self.defaults['FEMALE_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbYouthRatioDefault_valueChanged(self, value):
        """Handler for youth ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbYouthRatioDefault
        if box.isEnabled():
            self.add_list_entry(
                self.defaults['YOUTH_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbAdultRatioDefault_valueChanged(self, value):
        """Handler for adult ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbAdultRatioDefault
        if box.isEnabled():
            self.add_list_entry(
                self.defaults['ADULT_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbElderlyRatioDefault_valueChanged(self, value):
        """Handler for elderly ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbElderlyRatioDefault
        if box.isEnabled():
            self.add_list_entry(
                self.defaults['ELDERLY_RATIO_KEY'], box.value())

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radHazard_toggled(self, flag):
        """Automatic slot executed when the hazard radio is toggled.

        :param flag: Flag indicating the new checked state of the button.
        :type flag: bool
        """
        if not flag:
            return
        self.set_category('hazard')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radExposure_toggled(self, theFlag):
        """Automatic slot executed when the hazard radio is toggled on.

        :param theFlag: Flag indicating the new checked state of the button.
        :type theFlag: bool
        """
        if not theFlag:
            return
        self.set_category('exposure')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radPostprocessing_toggled(self, flag):
        """Automatic slot executed when the hazard radio is toggled on.

        :param flag: Flag indicating the new checked state of the button.
        :type flag: bool
        """
        if flag:
            self.set_category('postprocessing')
            self.update_controls_from_list()
            return
        if self.defaults is not None:
            self.remove_item_by_key(self.defaults['AGGR_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['ADULT_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY'])

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboSubcategory_currentIndexChanged(self, index=None):
        """Automatic slot executed when the subcategory is changed.

        When the user changes the subcategory, we will extract the
        subcategory and dataype or unit (depending on if it is a hazard
        or exposure subcategory) from the [] after the name.

        :param index: Not used but required for Qt slot.
        """
        if index == -1:
            self.remove_item_by_key('subcategory')
            return

        text = self.cboSubcategory.itemData(
            self.cboSubcategory.currentIndex())

        # I found that myText is 'Not Set' for every language
        if text == self.tr('Not Set') or text == 'Not Set':
            self.remove_item_by_key('subcategory')
            return

        tokens = text.split(' ')
        if len(tokens) < 1:
            self.remove_item_by_key('subcategory')
            return

        subcategory = tokens[0]
        self.add_list_entry('subcategory', subcategory)

        # Some subcategories e.g. roads have no units or datatype
        if len(tokens) == 1:
            return
        if tokens[1].find('[') < 0:
            return
        category = self.get_value_for_key('category')
        if 'hazard' == category:
            units = tokens[1].replace('[', '').replace(']', '')
            self.add_list_entry('unit', units)
        if 'exposure' == category:
            data_type = tokens[1].replace('[', '').replace(']', '')
            self.add_list_entry('datatype', data_type)
            # prevents actions being handled twice

    def set_subcategory_list(self, entries, selected_item=None):
        """Helper to populate the subcategory list based on category context.

        :param entries: An OrderedDict of subcategories. The dict entries
             should be in the form ('earthquake', self.tr('earthquake')). See
             http://www.voidspace.org.uk/python/odict.html for info on
             OrderedDict.
        :type entries: OrderedDict

        :param selected_item: Which item should be selected in the combo. If
            the selected item is not in entries, it will be appended to it.
            This is optional.
        :type selected_item: str
        """
        # To avoid triggering on_cboSubcategory_currentIndexChanged
        # we block signals from the combo while updating it
        self.cboSubcategory.blockSignals(True)
        self.cboSubcategory.clear()
        item_selected_flag = selected_item is not None
        selected_item_values = selected_item not in entries.values()
        selected_item_keys = selected_item not in entries.keys()
        if (item_selected_flag and selected_item_values and
                selected_item_keys):
            # Add it to the OrderedList
            entries[selected_item] = selected_item
        index = 0
        selected_index = 0
        for key, value in entries.iteritems():
            if value == selected_item or key == selected_item:
                selected_index = index
            index += 1
            self.cboSubcategory.addItem(value, key)
        self.cboSubcategory.setCurrentIndex(selected_index)
        self.cboSubcategory.blockSignals(False)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnAddToList1_clicked(self):
        """Automatic slot executed when the pbnAddToList1 button is pressed.
        """
        if (self.lePredefinedValue.text() != "" and
                self.cboKeyword.currentText() != ""):
            current_key = self.tr(self.cboKeyword.currentText())
            current_value = self.lePredefinedValue.text()
            self.add_list_entry(current_key, current_value)
            self.lePredefinedValue.setText('')
            self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnAddToList2_clicked(self):
        """Automatic slot executed when the pbnAddToList2 button is pressed.
        """

        current_key = self.leKey.text()
        current_value = self.leValue.text()
        if current_key == 'category' and current_value == 'hazard':
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.set_subcategory_list(self.standard_hazard_list)
            self.radHazard.blockSignals(False)
        elif current_key == 'category' and current_value == 'exposure':
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.set_subcategory_list(self.standard_exposure_list)
            self.radExposure.blockSignals(False)
        elif current_key == 'category':
            # .. todo:: notify the user their category is invalid
            pass
        self.add_list_entry(current_key, current_value)
        self.leKey.setText('')
        self.leValue.setText('')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnRemove_clicked(self):
        """Automatic slot executed when the pbnRemove button is pressed.

        Any selected items in the keywords list will be removed.
        """
        for item in self.lstKeywords.selectedItems():
            self.lstKeywords.takeItem(self.lstKeywords.row(item))
        self.leKey.setText('')
        self.leValue.setText('')
        self.update_controls_from_list()

    def add_list_entry(self, key, value):
        """Add an item to the keywords list given its key/value.

        The key and value must both be valid, non empty strings
        or an InvalidKVPError will be raised.

        If an entry with the same key exists, it's value will be
        replaced with value.

        It will add the current key/value pair to the list if it is not
        already present. The kvp will also be stored in the data of the
        listwidgetitem as a simple string delimited with a bar ('|').

        :param key: The key part of the key value pair (kvp).
        :type key: str

        :param value: Value part of the key value pair (kvp).
        :type value: str
        """
        if key is None or key == '':
            return
        if value is None or value == '':
            return

        # make sure that both key and value is string
        key = str(key)
        value = str(value)
        message = ''
        if ':' in key:
            key = key.replace(':', '.')
            message = self.tr('Colons are not allowed, replaced with "."')
        if ':' in value:
            value = value.replace(':', '.')
            message = self.tr('Colons are not allowed, replaced with "."')
        if message == '':
            self.lblMessage.setText('')
            self.lblMessage.hide()
        else:
            self.lblMessage.setText(message)
            self.lblMessage.show()
        item = QtGui.QListWidgetItem(key + ':' + value)
        # We are going to replace, so remove it if it exists already
        self.remove_item_by_key(key)
        data = key + '|' + value
        item.setData(QtCore.Qt.UserRole, data)
        self.lstKeywords.insertItem(0, item)

    def set_category(self, category):
        """Set the category radio button based on category.

        :param category: Either 'hazard', 'exposure' or 'postprocessing'.
        :type category: str

        :returns: False if radio button could not be updated, otherwise True.
        :rtype: bool
        """
        # convert from QString if needed
        category = str(category)
        if self.get_value_for_key('category') == category:
            # nothing to do, go home
            return True
        if category not in ['hazard', 'exposure', 'postprocessing']:
            # .. todo:: report an error to the user
            return False
            # Special case when category changes, we start on a new slate!

        if category == 'hazard':
            # only cause a toggle if we actually changed the category
            # This will only really be apparent if user manually enters
            # category as a keyword
            self.reset()
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.radHazard.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.remove_item_by_key('datatype')
            self.add_list_entry('category', 'hazard')
            hazard_list = self.standard_hazard_list
            self.set_subcategory_list(hazard_list)

        elif category == 'exposure':
            self.reset()
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.radExposure.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.remove_item_by_key('unit')
            self.add_list_entry('category', 'exposure')
            exposure_list = self.standard_exposure_list
            self.set_subcategory_list(exposure_list)

        else:
            self.reset()
            self.radPostprocessing.blockSignals(True)
            self.radPostprocessing.setChecked(True)
            self.radPostprocessing.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.add_list_entry('category', 'postprocessing')

        return True

    def reset(self, primary_keywords_only=True):
        """Reset all controls to a blank state.

        :param primary_keywords_only: If True (the default), only reset
            Subcategory, datatype and units.
        :type primary_keywords_only: bool
        """

        self.cboSubcategory.clear()
        self.remove_item_by_key('subcategory')
        self.remove_item_by_key('datatype')
        self.remove_item_by_key('unit')
        self.remove_item_by_key('source')
        if not primary_keywords_only:
            # Clear everything else too
            self.lstKeywords.clear()
            self.leKey.clear()
            self.leValue.clear()
            self.lePredefinedValue.clear()
            self.leTitle.clear()
            self.leSource.clear()

    def remove_item_by_key(self, removal_key):
        """Remove an item from the kvp list given its key.

        :param removal_key: Key of item to be removed.
        :type removal_key: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            if len(tokens) < 2:
                break
            key = tokens[0]
            if removal_key == key:
                # remove it since the removal_key is already present
                self.blockSignals(True)
                self.lstKeywords.takeItem(counter)
                self.blockSignals(False)
                break

    def remove_item_by_value(self, removal_value):
        """Remove an item from the kvp list given its key.

        :param removal_value: Value of item to be removed.
        :type removal_value: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            value = tokens[1]
            if removal_value == value:
                # remove it since the key is already present
                self.blockSignals(True)
                self.lstKeywords.takeItem(counter)
                self.blockSignals(False)
                break

    def get_value_for_key(self, lookup_key):
        """If key list contains a specific key, return its value.

        :param lookup_key: The key to search for
        :type lookup_key: str

        :returns: Value of key if matched otherwise none.
        :rtype: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            key = str(tokens[0]).strip()
            value = str(tokens[1]).strip()
            if lookup_key == key:
                return value
        return None

    def load_state_from_keywords(self):
        """Set the ui state to match the keywords of the active layer.

        In case the layer has no keywords or any problem occurs reading them,
        start with a blank state so that subcategory gets populated nicely &
        we will assume exposure to start with.

        Also if only title is set we use similar logic (title is added by
        default in dock and other defaults need to be explicitly added
        when opening this dialog). See #751

        """
        keywords = {'category': 'exposure'}

        try:
            # Now read the layer with sub layer if needed
            keywords = self.keyword_io.read_keywords(self.layer)
        except (InvalidParameterError,
                HashNotFoundError,
                NoKeywordsFoundError):
            pass

        layer_name = self.layer.name()
        if 'title' not in keywords:
            self.leTitle.setText(layer_name)
        self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name))

        if 'source' in keywords:
            self.leSource.setText(keywords['source'])
        else:
            self.leSource.setText('')

        # if we have a category key, unpack it first
        # so radio button etc get set
        if 'category' in keywords:
            self.set_category(keywords['category'])
            keywords.pop('category')
        else:
            # assume exposure to match ui. See issue #751
            self.add_list_entry('category', 'exposure')

        aggregation_attributes = [
            DEFAULTS['FEMALE_RATIO_ATTR_KEY'],
            DEFAULTS['YOUTH_RATIO_ATTR_KEY'],
            DEFAULTS['ADULT_RATIO_ATTR_KEY'],
            DEFAULTS['ELDERLY_RATIO_ATTR_KEY'],
        ]

        for key in keywords.iterkeys():
            if key in aggregation_attributes:
                if str(keywords[key]) == 'Use default':
                    self.add_list_entry(key, self.global_default_data)
                    continue
            self.add_list_entry(key, str(keywords[key]))

        # now make the rest of the safe_qgis reflect the list entries
        self.update_controls_from_list()

    def update_controls_from_list(self):
        """Set the ui state to match the keywords of the active layer."""
        subcategory = self.get_value_for_key('subcategory')
        units = self.get_value_for_key('unit')
        data_type = self.get_value_for_key('datatype')
        title = self.get_value_for_key('title')
        if title is not None:
            self.leTitle.setText(title)
        elif self.layer is not None:
            layer_name = self.layer.name()
            self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name))
        else:
            self.lblLayerName.setText('')

        if not is_polygon_layer(self.layer):
            self.radPostprocessing.setEnabled(False)
        else:
            # adapt gui if we are in postprocessing category
            self.toggle_postprocessing_widgets()

        if self.radExposure.isChecked():
            if subcategory is not None and data_type is not None:
                self.set_subcategory_list(
                    self.standard_exposure_list,
                    subcategory + ' [' + data_type + ']')
            elif subcategory is not None:
                self.set_subcategory_list(
                    self.standard_exposure_list, subcategory)
            else:
                self.set_subcategory_list(
                    self.standard_exposure_list,
                    self.tr('Not Set'))
        elif self.radHazard.isChecked():
            if subcategory is not None and units is not None:
                self.set_subcategory_list(
                    self.standard_hazard_list,
                    subcategory + ' [' + units + ']')
            elif subcategory is not None:
                self.set_subcategory_list(
                    self.standard_hazard_list,
                    subcategory)
            else:
                self.set_subcategory_list(
                    self.standard_hazard_list,
                    self.tr('Not Set'))

        self.resize_dialog()

    def resize_dialog(self):
        """Resize the dialog to fit its contents."""
        # noinspection PyArgumentList
        QtCore.QCoreApplication.processEvents()
        LOGGER.debug('adjust ing dialog size')
        self.adjustSize()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('QString')
    def on_leTitle_textEdited(self, title):
        """Update the keywords list whenever the user changes the title.

        This slot is not called if the title is changed programmatically.

        :param title: New title keyword for the layer.
        :type title: str
        """
        self.add_list_entry('title', str(title))

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('QString')
    def on_leSource_textEdited(self, source):
        """Update the keywords list whenever the user changes the source.

        This slot is not called if the source is changed programmatically.

        :param source: New source keyword for the layer.
        :type source: str
        """
        if source is None or source == '':
            self.remove_item_by_key('source')
        else:
            self.add_list_entry('source', str(source))

    def get_keywords(self):
        """Obtain the state of the dialog as a keywords dict.

        :returns: Keywords reflecting the state of the dialog.
        :rtype: dict
        """
        # make sure title is listed
        if str(self.leTitle.text()) != '':
            self.add_list_entry('title', str(self.leTitle.text()))

        # make sure the source is listed too
        if str(self.leSource.text()) != '':
            self.add_list_entry('source', str(self.leSource.text()))

        keywords = {}
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            key = str(tokens[0]).strip()
            value = str(tokens[1]).strip()
            keywords[key] = value
        return keywords

    def accept(self):
        """Automatic slot executed when the ok button is pressed.

        It will write out the keywords for the layer that is active.
        """
        self.apply_changes()
        keywords = self.get_keywords()

        # If it's postprocessing layer, we need to check if age ratio is valid
        if self.radPostprocessing.isChecked():
            valid_age_ratio, sum_age_ratios = self.age_ratios_are_valid(
                keywords)
            if not valid_age_ratio:
                message = self.tr(
                    'The sum of age ratios is %s which exceeds 1. Please '
                    'adjust  the age ration defaults so that their cumulative '
                    'value is not greater than 1.' % sum_age_ratios)
                if not self.test:
                    # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
                    QtGui.QMessageBox.warning(
                        self, self.tr('InaSAFE'), message)
                return

        try:
            self.keyword_io.write_keywords(layer=self.layer, keywords=keywords)
        except InaSAFEError, e:
            error_message = get_error_message(e)
            message = self.tr(
                'An error was encountered when saving the keywords:\n'
                '%s' % error_message.to_html())
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message)
        if self.dock is not None:
            self.dock.get_layers()
        self.done(QtGui.QDialog.Accepted)
Exemplo n.º 8
0
def buffer_points(point_layer, radii, hazard_zone_attribute, output_crs):
    """Buffer points for each point with defined radii.

    This function is used for making buffer of volcano point hazard.

    :param point_layer: A point layer to buffer.
    :type point_layer: QgsVectorLayer

    :param radii: Desired approximate radii in kilometers (must be
        monotonically ascending). Can be either one number or list of numbers
    :type radii: int, list

    :param hazard_zone_attribute: The name of the attributes representing
        hazard zone.
    :type hazard_zone_attribute: str

    :param output_crs: The output CRS.
    :type output_crs: QgsCoordinateReferenceSystem

    :return: Vector polygon layer representing circle in point layer CRS.
    :rtype: QgsVectorLayer
    """
    if not isinstance(radii, list):
        radii = [radii]

    if not is_point_layer(point_layer):
        message = (
            'Input hazard must be a vector point layer. I got %s '
            'with layer type %s' % (point_layer.name(), point_layer.type()))
        raise Exception(message)

    # Check that radii are monotonically increasing
    monotonically_increasing_flag = all(
        x < y for x, y in zip(radii, radii[1:]))
    if not monotonically_increasing_flag:
        raise RadiiException(RadiiException.suggestion)

    hazard_file_path = unique_filename(suffix='-polygon-volcano.shp')
    fields = point_layer.pendingFields()
    fields.append(QgsField(hazard_zone_attribute, QVariant.Double))
    writer = QgsVectorFileWriter(
        hazard_file_path,
        'utf-8',
        fields,
        QGis.WKBPolygon,
        output_crs,
        'ESRI Shapefile')
    input_crs = point_layer.crs()

    center = point_layer.extent().center()
    utm = None
    if output_crs.authid() == 'EPSG:4326':
        utm = QgsCoordinateReferenceSystem(
            get_utm_epsg(center.x(), center.y(), input_crs))
        transform = QgsCoordinateTransform(point_layer.crs(), utm)

    else:
        transform = QgsCoordinateTransform(point_layer.crs(), output_crs)

    for point in point_layer.getFeatures():
        geom = point.geometry()
        geom.transform(transform)

        inner_rings = None
        for radius in radii:
            attributes = point.attributes()
            # Generate circle polygon

            circle = geom.buffer(radius * 1000.0, 30)

            if inner_rings:
                circle.addRing(inner_rings)
            inner_rings = circle.asPolygon()[0]

            new_buffer = QgsFeature()

            if output_crs.authid() == 'EPSG:4326':
                circle.transform(QgsCoordinateTransform(utm, output_crs))

            new_buffer.setGeometry(circle)
            attributes.append(radius)
            new_buffer.setAttributes(attributes)

            writer.addFeature(new_buffer)

    del writer
    vector_layer = QgsVectorLayer(hazard_file_path, 'Polygons', 'ogr')

    keyword_io = KeywordIO()
    try:
        keywords = keyword_io.read_keywords(point_layer)
        keyword_io.write_keywords(vector_layer, keywords)
    except NoKeywordsFoundError:
        pass
    return vector_layer
Exemplo n.º 9
0
class KeywordsDialog(QtGui.QDialog, FORM_CLASS):
    """Dialog implementation class for the InaSAFE keywords editor."""
    def __init__(self, parent, iface, dock=None, layer=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: Quantum GIS QGisAppInterface instance.
        :type iface: QGisAppInterface

        :param dock: Dock widget instance that we can notify of changes to
            the keywords. Optional.
        :type dock: Dock
        """
        QtGui.QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowTitle(
            self.tr('InaSAFE %s Keywords Editor' % get_version()))
        # Save reference to the QGIS interface and parent
        self.iface = iface
        self.parent = parent
        self.dock = dock
        self.defaults = None

        # string constants
        self.global_default_string = definitions.global_default_attribute[
            'name']
        self.do_not_use_string = definitions.do_not_use_attribute['name']
        self.global_default_data = definitions.global_default_attribute['id']
        self.do_not_use_data = definitions.do_not_use_attribute['id']

        if layer is None:
            self.layer = self.iface.activeLayer()
        else:
            self.layer = layer

        self.keyword_io = KeywordIO()

        # note the keys should remain untranslated as we need to write
        # english to the keywords file. The keys will be written as user data
        # in the combo entries.
        # .. seealso:: http://www.voidspace.org.uk/python/odict.html
        self.standard_exposure_list = OrderedDict([
            ('population', self.tr('population')),
            ('structure', self.tr('structure')), ('road', self.tr('road')),
            ('Not Set', self.tr('Not Set'))
        ])
        self.standard_hazard_list = OrderedDict([
            ('earthquake [MMI]', self.tr('earthquake [MMI]')),
            ('tsunami [m]', self.tr('tsunami [m]')),
            ('tsunami [wet/dry]', self.tr('tsunami [wet/dry]')),
            ('tsunami [feet]', self.tr('tsunami [feet]')),
            ('flood [m]', self.tr('flood [m]')),
            ('flood [wet/dry]', self.tr('flood [wet/dry]')),
            ('flood [feet]', self.tr('flood [feet]')),
            ('tephra [kg2/m2]', self.tr('tephra [kg2/m2]')),
            ('volcano', self.tr('volcano')),
            ('generic [categorised]', self.tr('generic [categorised]')),
            ('Not Set', self.tr('Not Set'))
        ])

        # noinspection PyUnresolvedReferences
        self.lstKeywords.itemClicked.connect(self.edit_key_value_pair)

        # Set up help dialog showing logic.
        help_button = self.buttonBox.button(QtGui.QDialogButtonBox.Help)
        help_button.clicked.connect(self.show_help)

        if self.layer is not None and is_polygon_layer(self.layer):
            # set some initial ui state:
            self.defaults = get_defaults()
            self.radPredefined.setChecked(True)
            self.dsbFemaleRatioDefault.blockSignals(True)
            self.dsbFemaleRatioDefault.setValue(self.defaults['FEMALE_RATIO'])
            self.dsbFemaleRatioDefault.blockSignals(False)

            self.dsbYouthRatioDefault.blockSignals(True)
            self.dsbYouthRatioDefault.setValue(self.defaults['YOUTH_RATIO'])
            self.dsbYouthRatioDefault.blockSignals(False)

            self.dsbAdultRatioDefault.blockSignals(True)
            self.dsbAdultRatioDefault.setValue(self.defaults['ADULT_RATIO'])
            self.dsbAdultRatioDefault.blockSignals(False)

            self.dsbElderlyRatioDefault.blockSignals(True)
            self.dsbElderlyRatioDefault.setValue(
                self.defaults['ELDERLY_RATIO'])
            self.dsbElderlyRatioDefault.blockSignals(False)
        else:
            self.radPostprocessing.hide()
            self.tab_widget.removeTab(1)

        if self.layer:
            self.load_state_from_keywords()

        # add a reload from keywords button
        reload_button = self.buttonBox.addButton(
            self.tr('Reload'), QtGui.QDialogButtonBox.ActionRole)
        reload_button.clicked.connect(self.load_state_from_keywords)
        self.resize_dialog()
        self.tab_widget.setCurrentIndex(0)
        # TODO No we should not have test related stuff in prod code. TS
        self.test = False

    def set_layer(self, layer):
        """Set the layer associated with the keyword editor.

        :param layer: Layer whose keywords should be edited.
        :type layer: QgsMapLayer
        """
        self.layer = layer
        self.load_state_from_keywords()

    # noinspection PyMethodMayBeStatic
    def show_help(self):
        """Load the help text for the keywords dialog."""
        show_context_help(context='keywords_editor')

    def toggle_postprocessing_widgets(self):
        """Hide or show the post processing widgets depending on context."""
        LOGGER.debug('togglePostprocessingWidgets')
        # TODO Too much baggage here. Can't we just disable/enable the tab? TS
        postprocessing_flag = self.radPostprocessing.isChecked()
        self.cboSubcategory.setVisible(not postprocessing_flag)
        self.lblSubcategory.setVisible(not postprocessing_flag)
        self.show_aggregation_attribute()
        self.show_female_ratio_attribute()
        self.show_female_ratio_default()
        self.show_youth_ratio_attribute()
        self.show_youth_ratio_default()
        self.show_adult_ratio_attribute()
        self.show_adult_ratio_default()
        self.show_elderly_ratio_attribute()
        self.show_elderly_ratio_default()
        # Also enable/disable the aggregation tab
        self.aggregation_tab.setEnabled(postprocessing_flag)

    def show_aggregation_attribute(self):
        """Hide or show the aggregation attribute in the keyword editor dialog.
        """
        box = self.cboAggregationAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['AGGR_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Int, QtCore.QVariant.String],
            current_keyword)
        box.addItems(fields)
        if attribute_position is None:
            box.setCurrentIndex(0)
        else:
            box.setCurrentIndex(attribute_position)

    def show_female_ratio_attribute(self):
        """Hide or show the female ratio attribute in the dialog.
        """
        box = self.cboFemaleRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['FEMALE_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_female_ratio_default(self):
        """Hide or show the female ratio default attribute in the dialog.
        """
        box = self.dsbFemaleRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['FEMALE_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['FEMALE_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_youth_ratio_attribute(self):
        """Hide or show the youth ratio attribute in the dialog.
        """
        box = self.cboYouthRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['YOUTH_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_youth_ratio_default(self):
        """Hide or show the youth ratio default attribute in the dialog.
        """
        box = self.dsbYouthRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['YOUTH_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['YOUTH_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_adult_ratio_attribute(self):
        """Hide or show the adult ratio attribute in the dialog.
        """
        box = self.cboAdultRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['ADULT_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_adult_ratio_default(self):
        """Hide or show the adult ratio default attribute in the dialog.
        """
        box = self.dsbAdultRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['ADULT_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['ADULT_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    def show_elderly_ratio_attribute(self):
        """Show the elderly ratio attribute in the dialog.
        """
        box = self.cboElderlyRatioAttribute
        box.blockSignals(True)
        box.clear()
        box.blockSignals(False)
        current_keyword = self.get_value_for_key(
            self.defaults['ELDERLY_RATIO_ATTR_KEY'])
        fields, attribute_position = layer_attribute_names(
            self.layer, [QtCore.QVariant.Double], current_keyword)

        box.addItem(self.global_default_string, self.global_default_data)
        box.addItem(self.do_not_use_string, self.do_not_use_data)
        for field in fields:
            box.addItem(field, field)

        if current_keyword == self.global_default_data:
            box.setCurrentIndex(0)
        elif current_keyword == self.do_not_use_data:
            box.setCurrentIndex(1)
        elif attribute_position is None:
            # current_keyword was not found in the attribute table.
            # Use default
            box.setCurrentIndex(0)
        else:
            # + 2 is because we add use defaults and don't use
            box.setCurrentIndex(attribute_position + 2)

    def show_elderly_ratio_default(self):
        """Show the elderly ratio default attribute in the dialog.
        """
        box = self.dsbElderlyRatioDefault
        current_value = self.get_value_for_key(
            self.defaults['ELDERLY_RATIO_KEY'])
        if current_value is None:
            val = self.defaults['ELDERLY_RATIO']
        else:
            val = float(current_value)
        box.setValue(val)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboAggregationAttribute_currentIndexChanged(self, index=None):
        """Handler for aggregation attribute combo change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return
        self.add_list_entry(self.defaults['AGGR_ATTR_KEY'],
                            self.cboAggregationAttribute.currentText())

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboFemaleRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for female ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboFemaleRatioAttribute.currentIndex()
        data = self.cboFemaleRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbFemaleRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['FEMALE_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(self.defaults['FEMALE_RATIO_KEY'],
                                    self.dsbFemaleRatioDefault.value())
        else:
            self.dsbFemaleRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY'])
        self.add_list_entry(self.defaults['FEMALE_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboYouthRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for youth ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboYouthRatioAttribute.currentIndex()
        data = self.cboYouthRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbYouthRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['YOUTH_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(self.defaults['YOUTH_RATIO_KEY'],
                                    self.dsbYouthRatioDefault.value())
        else:
            self.dsbYouthRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY'])
        self.add_list_entry(self.defaults['YOUTH_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboAdultRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for adult ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboAdultRatioAttribute.currentIndex()
        data = self.cboAdultRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbAdultRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['ADULT_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(self.defaults['ADULT_RATIO_KEY'],
                                    self.dsbAdultRatioDefault.value())
        else:
            self.dsbAdultRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY'])
        self.add_list_entry(self.defaults['ADULT_RATIO_ATTR_KEY'], data)

    # noinspection PyPep8Naming
    def on_cboElderlyRatioAttribute_currentIndexChanged(self, index=None):
        """Handler for elderly ratio attribute change.

        :param index: Not used but required for slot.
        """
        del index
        if not self.radPostprocessing.isChecked():
            return

        current_index = self.cboElderlyRatioAttribute.currentIndex()
        data = self.cboElderlyRatioAttribute.itemData(current_index)

        if data == self.global_default_data:
            self.dsbElderlyRatioDefault.setEnabled(True)
            current_default = self.get_value_for_key(
                self.defaults['ELDERLY_RATIO_KEY'])
            if current_default is None:
                self.add_list_entry(self.defaults['ELDERLY_RATIO_KEY'],
                                    self.dsbElderlyRatioDefault.value())
        else:
            self.dsbElderlyRatioDefault.setEnabled(False)
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY'])
        self.add_list_entry(self.defaults['ELDERLY_RATIO_ATTR_KEY'], data)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('double')
    def on_dsbFemaleRatioDefault_valueChanged(self, value):
        """Handler for female ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbFemaleRatioDefault
        if box.isEnabled():
            self.add_list_entry(self.defaults['FEMALE_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbYouthRatioDefault_valueChanged(self, value):
        """Handler for youth ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbYouthRatioDefault
        if box.isEnabled():
            self.add_list_entry(self.defaults['YOUTH_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbAdultRatioDefault_valueChanged(self, value):
        """Handler for adult ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbAdultRatioDefault
        if box.isEnabled():
            self.add_list_entry(self.defaults['ADULT_RATIO_KEY'], box.value())

    # noinspection PyPep8Naming
    def on_dsbElderlyRatioDefault_valueChanged(self, value):
        """Handler for elderly ration default value changing.

        :param value: Not used but required for slot.
        """
        del value
        if not self.radPostprocessing.isChecked():
            return
        box = self.dsbElderlyRatioDefault
        if box.isEnabled():
            self.add_list_entry(self.defaults['ELDERLY_RATIO_KEY'],
                                box.value())

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radHazard_toggled(self, flag):
        """Automatic slot executed when the hazard radio is toggled.

        :param flag: Flag indicating the new checked state of the button.
        :type flag: bool
        """
        if not flag:
            return
        self.set_category('hazard')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radExposure_toggled(self, theFlag):
        """Automatic slot executed when the hazard radio is toggled on.

        :param theFlag: Flag indicating the new checked state of the button.
        :type theFlag: bool
        """
        if not theFlag:
            return
        self.set_category('exposure')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('bool')
    def on_radPostprocessing_toggled(self, flag):
        """Automatic slot executed when the hazard radio is toggled on.

        :param flag: Flag indicating the new checked state of the button.
        :type flag: bool
        """
        if flag:
            self.set_category('postprocessing')
            self.update_controls_from_list()
            return
        if self.defaults is not None:
            self.remove_item_by_key(self.defaults['AGGR_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['FEMALE_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['YOUTH_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['ADULT_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['ADULT_RATIO_KEY'])
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_ATTR_KEY'])
            self.remove_item_by_key(self.defaults['ELDERLY_RATIO_KEY'])

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('int')
    def on_cboSubcategory_currentIndexChanged(self, index=None):
        """Automatic slot executed when the subcategory is changed.

        When the user changes the subcategory, we will extract the
        subcategory and dataype or unit (depending on if it is a hazard
        or exposure subcategory) from the [] after the name.

        :param index: Not used but required for Qt slot.
        """
        if index == -1:
            self.remove_item_by_key('subcategory')
            return

        text = self.cboSubcategory.itemData(self.cboSubcategory.currentIndex())

        # I found that myText is 'Not Set' for every language
        if text == self.tr('Not Set') or text == 'Not Set':
            self.remove_item_by_key('subcategory')
            return

        tokens = text.split(' ')
        if len(tokens) < 1:
            self.remove_item_by_key('subcategory')
            return

        subcategory = tokens[0]
        self.add_list_entry('subcategory', subcategory)

        # Some subcategories e.g. roads have no units or datatype
        if len(tokens) == 1:
            return
        if tokens[1].find('[') < 0:
            return
        category = self.get_value_for_key('category')
        if 'hazard' == category:
            units = tokens[1].replace('[', '').replace(']', '')
            self.add_list_entry('unit', units)
        if 'exposure' == category:
            data_type = tokens[1].replace('[', '').replace(']', '')
            self.add_list_entry('datatype', data_type)
            # prevents actions being handled twice

    def set_subcategory_list(self, entries, selected_item=None):
        """Helper to populate the subcategory list based on category context.

        :param entries: An OrderedDict of subcategories. The dict entries
             should be in the form ('earthquake', self.tr('earthquake')). See
             http://www.voidspace.org.uk/python/odict.html for info on
             OrderedDict.
        :type entries: OrderedDict

        :param selected_item: Which item should be selected in the combo. If
            the selected item is not in entries, it will be appended to it.
            This is optional.
        :type selected_item: str
        """
        # To avoid triggering on_cboSubcategory_currentIndexChanged
        # we block signals from the combo while updating it
        self.cboSubcategory.blockSignals(True)
        self.cboSubcategory.clear()
        item_selected_flag = selected_item is not None
        selected_item_values = selected_item not in entries.values()
        selected_item_keys = selected_item not in entries.keys()
        if (item_selected_flag and selected_item_values
                and selected_item_keys):
            # Add it to the OrderedList
            entries[selected_item] = selected_item
        index = 0
        selected_index = 0
        for key, value in entries.iteritems():
            if value == selected_item or key == selected_item:
                selected_index = index
            index += 1
            self.cboSubcategory.addItem(value, key)
        self.cboSubcategory.setCurrentIndex(selected_index)
        self.cboSubcategory.blockSignals(False)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnAddToList1_clicked(self):
        """Automatic slot executed when the pbnAddToList1 button is pressed.
        """
        if (self.lePredefinedValue.text() != ""
                and self.cboKeyword.currentText() != ""):
            current_key = self.tr(self.cboKeyword.currentText())
            current_value = self.lePredefinedValue.text()
            self.add_list_entry(current_key, current_value)
            self.lePredefinedValue.setText('')
            self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnAddToList2_clicked(self):
        """Automatic slot executed when the pbnAddToList2 button is pressed.
        """

        current_key = self.leKey.text()
        current_value = self.leValue.text()
        if current_key == 'category' and current_value == 'hazard':
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.set_subcategory_list(self.standard_hazard_list)
            self.radHazard.blockSignals(False)
        elif current_key == 'category' and current_value == 'exposure':
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.set_subcategory_list(self.standard_exposure_list)
            self.radExposure.blockSignals(False)
        elif current_key == 'category':
            # .. todo:: notify the user their category is invalid
            pass
        self.add_list_entry(current_key, current_value)
        self.leKey.setText('')
        self.leValue.setText('')
        self.update_controls_from_list()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnRemove_clicked(self):
        """Automatic slot executed when the pbnRemove button is pressed.

        Any selected items in the keywords list will be removed.
        """
        for item in self.lstKeywords.selectedItems():
            self.lstKeywords.takeItem(self.lstKeywords.row(item))
        self.leKey.setText('')
        self.leValue.setText('')
        self.update_controls_from_list()

    def add_list_entry(self, key, value):
        """Add an item to the keywords list given its key/value.

        The key and value must both be valid, non empty strings
        or an InvalidKVPError will be raised.

        If an entry with the same key exists, it's value will be
        replaced with value.

        It will add the current key/value pair to the list if it is not
        already present. The kvp will also be stored in the data of the
        listwidgetitem as a simple string delimited with a bar ('|').

        :param key: The key part of the key value pair (kvp).
        :type key: str, unicode

        :param value: Value part of the key value pair (kvp).
        :type value: str, unicode
        """
        if key is None or key == '':
            return

        # make sure that both key and value is unicode
        key = get_unicode(key)
        value = get_unicode(value)

        message = ''
        if ':' in key:
            key = key.replace(':', '.')
            message = self.tr('Colons are not allowed, replaced with "."')
        if ':' in value:
            value = value.replace(':', '.')
            message = self.tr('Colons are not allowed, replaced with "."')
        if message == '':
            self.lblMessage.setText('')
            self.lblMessage.hide()
        else:
            self.lblMessage.setText(message)
            self.lblMessage.show()
        item = QtGui.QListWidgetItem(key + ':' + value)
        # We are going to replace, so remove it if it exists already
        self.remove_item_by_key(key)
        data = key + '|' + value
        item.setData(QtCore.Qt.UserRole, data)
        self.lstKeywords.insertItem(0, item)

    def set_category(self, category):
        """Set the category radio button based on category.

        :param category: Either 'hazard', 'exposure' or 'postprocessing'.
        :type category: str

        :returns: False if radio button could not be updated, otherwise True.
        :rtype: bool
        """
        if self.get_value_for_key('category') == category:
            # nothing to do, go home
            return True
        if category not in ['hazard', 'exposure', 'postprocessing']:
            # .. todo:: report an error to the user
            return False
            # Special case when category changes, we start on a new slate!

        if category == 'hazard':
            # only cause a toggle if we actually changed the category
            # This will only really be apparent if user manually enters
            # category as a keyword
            self.reset()
            self.radHazard.blockSignals(True)
            self.radHazard.setChecked(True)
            self.radHazard.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.remove_item_by_key('datatype')
            self.add_list_entry('category', 'hazard')
            hazard_list = self.standard_hazard_list
            self.set_subcategory_list(hazard_list)

        elif category == 'exposure':
            self.reset()
            self.radExposure.blockSignals(True)
            self.radExposure.setChecked(True)
            self.radExposure.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.remove_item_by_key('unit')
            self.add_list_entry('category', 'exposure')
            exposure_list = self.standard_exposure_list
            self.set_subcategory_list(exposure_list)

        else:
            self.reset()
            self.radPostprocessing.blockSignals(True)
            self.radPostprocessing.setChecked(True)
            self.radPostprocessing.blockSignals(False)
            self.remove_item_by_key('subcategory')
            self.add_list_entry('category', 'postprocessing')

        return True

    def reset(self, primary_keywords_only=True):
        """Reset all controls to a blank state.

        :param primary_keywords_only: If True (the default), only reset
            Subcategory, datatype and units.
        :type primary_keywords_only: bool
        """

        self.cboSubcategory.clear()
        self.remove_item_by_key('subcategory')
        self.remove_item_by_key('datatype')
        self.remove_item_by_key('unit')
        self.remove_item_by_key('source')
        if not primary_keywords_only:
            # Clear everything else too
            self.lstKeywords.clear()
            self.leKey.clear()
            self.leValue.clear()
            self.lePredefinedValue.clear()
            self.leTitle.clear()
            self.leSource.clear()

    def remove_item_by_key(self, removal_key):
        """Remove an item from the kvp list given its key.

        :param removal_key: Key of item to be removed.
        :type removal_key: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            if len(tokens) < 2:
                break
            key = tokens[0]
            if removal_key == key:
                # remove it since the removal_key is already present
                self.blockSignals(True)
                self.lstKeywords.takeItem(counter)
                self.blockSignals(False)
                break

    def remove_item_by_value(self, removal_value):
        """Remove an item from the kvp list given its key.

        :param removal_value: Value of item to be removed.
        :type removal_value: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            value = tokens[1]
            if removal_value == value:
                # remove it since the key is already present
                self.blockSignals(True)
                self.lstKeywords.takeItem(counter)
                self.blockSignals(False)
                break

    def get_value_for_key(self, lookup_key):
        """If key list contains a specific key, return its value.

        :param lookup_key: The key to search for
        :type lookup_key: str

        :returns: Value of key if matched otherwise none.
        :rtype: str
        """
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            key = tokens[0].strip()
            value = tokens[1].strip()
            if lookup_key == key:
                return value
        return None

    def load_state_from_keywords(self):
        """Set the ui state to match the keywords of the active layer.

        In case the layer has no keywords or any problem occurs reading them,
        start with a blank state so that subcategory gets populated nicely &
        we will assume exposure to start with.

        Also if only title is set we use similar logic (title is added by
        default in dock and other defaults need to be explicitly added
        when opening this dialog). See #751

        """
        keywords = {'category': 'exposure'}

        try:
            # Now read the layer with sub layer if needed
            keywords = self.keyword_io.read_keywords(self.layer)
        except (InvalidParameterError, HashNotFoundError,
                NoKeywordsFoundError):
            pass

        layer_name = self.layer.name()
        if 'title' not in keywords:
            self.leTitle.setText(layer_name)
        self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name))

        if 'source' in keywords:
            self.leSource.setText(keywords['source'])
        else:
            self.leSource.setText('')

        # if we have a category key, unpack it first
        # so radio button etc get set
        if 'category' in keywords:
            self.set_category(keywords['category'])
            keywords.pop('category')
        else:
            # assume exposure to match ui. See issue #751
            self.add_list_entry('category', 'exposure')

        aggregation_attributes = [
            DEFAULTS['FEMALE_RATIO_ATTR_KEY'],
            DEFAULTS['YOUTH_RATIO_ATTR_KEY'],
            DEFAULTS['ADULT_RATIO_ATTR_KEY'],
            DEFAULTS['ELDERLY_RATIO_ATTR_KEY'],
        ]

        for key in keywords.iterkeys():
            if key in aggregation_attributes:
                if keywords[key] == 'Use default':
                    self.add_list_entry(key, self.global_default_data)
                    continue
            self.add_list_entry(key, keywords[key])

        # now make the rest of the safe_qgis reflect the list entries
        self.update_controls_from_list()

    def update_controls_from_list(self):
        """Set the ui state to match the keywords of the active layer."""
        subcategory = self.get_value_for_key('subcategory')
        units = self.get_value_for_key('unit')
        data_type = self.get_value_for_key('datatype')
        title = self.get_value_for_key('title')
        if title is not None:
            self.leTitle.setText(title)
        elif self.layer is not None:
            layer_name = self.layer.name()
            self.lblLayerName.setText(self.tr('Keywords for %s' % layer_name))
        else:
            self.lblLayerName.setText('')

        if not is_polygon_layer(self.layer):
            self.radPostprocessing.setEnabled(False)
        else:
            # adapt gui if we are in postprocessing category
            self.toggle_postprocessing_widgets()

        if self.radExposure.isChecked():
            if subcategory is not None and data_type is not None:
                self.set_subcategory_list(self.standard_exposure_list,
                                          subcategory + ' [' + data_type + ']')
            elif subcategory is not None:
                self.set_subcategory_list(self.standard_exposure_list,
                                          subcategory)
            else:
                self.set_subcategory_list(self.standard_exposure_list,
                                          self.tr('Not Set'))
        elif self.radHazard.isChecked():
            if subcategory is not None and units is not None:
                self.set_subcategory_list(self.standard_hazard_list,
                                          subcategory + ' [' + units + ']')
            elif subcategory is not None:
                self.set_subcategory_list(self.standard_hazard_list,
                                          subcategory)
            else:
                self.set_subcategory_list(self.standard_hazard_list,
                                          self.tr('Not Set'))

        self.resize_dialog()

    def resize_dialog(self):
        """Resize the dialog to fit its contents."""
        # noinspection PyArgumentList
        QtCore.QCoreApplication.processEvents()
        LOGGER.debug('adjust ing dialog size')
        self.adjustSize()

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('QString')
    def on_leTitle_textEdited(self, title):
        """Update the keywords list whenever the user changes the title.

        This slot is not called if the title is changed programmatically.

        :param title: New title keyword for the layer.
        :type title: str
        """
        self.add_list_entry('title', title)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('QString')
    def on_leSource_textEdited(self, source):
        """Update the keywords list whenever the user changes the source.

        This slot is not called if the source is changed programmatically.

        :param source: New source keyword for the layer.
        :type source: str
        """
        if source is None or source == '':
            self.remove_item_by_key('source')
        else:
            self.add_list_entry('source', source)

    def get_keywords(self):
        """Obtain the state of the dialog as a keywords dict.

        :returns: Keywords reflecting the state of the dialog.
        :rtype: dict
        """
        # make sure title is listed
        if self.leTitle.text() != '':
            self.add_list_entry('title', self.leTitle.text())

        # make sure the source is listed too
        if self.leSource.text() != '':
            self.add_list_entry('source', self.leSource.text())

        keywords = {}
        for counter in range(self.lstKeywords.count()):
            existing_item = self.lstKeywords.item(counter)
            text = existing_item.text()
            tokens = text.split(':')
            key = tokens[0].strip()
            value = tokens[1].strip()
            keywords[key] = value
        return keywords

    def accept(self):
        """Automatic slot executed when the ok button is pressed.

        It will write out the keywords for the layer that is active.
        """
        self.apply_changes()
        keywords = self.get_keywords()

        # If it's postprocessing layer, we need to check if age ratio is valid
        if self.radPostprocessing.isChecked():
            valid_age_ratio, sum_age_ratios = self.age_ratios_are_valid(
                keywords)
            if not valid_age_ratio:
                message = self.tr(
                    'The sum of age ratios is %s which exceeds 1. Please '
                    'adjust  the age ration defaults so that their cumulative '
                    'value is not greater than 1.' % sum_age_ratios)
                if not self.test:
                    # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
                    QtGui.QMessageBox.warning(self, self.tr('InaSAFE'),
                                              message)
                return

        try:
            self.keyword_io.write_keywords(layer=self.layer, keywords=keywords)
        except InaSAFEError, e:
            error_message = get_error_message(e)
            message = self.tr(
                'An error was encountered when saving the keywords:\n'
                '%s' % error_message.to_html())
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QtGui.QMessageBox.warning(self, self.tr('InaSAFE'), message)
        if self.dock is not None:
            self.dock.get_layers()
        self.done(QtGui.QDialog.Accepted)
Exemplo n.º 10
0
class FieldMappingDialog(QDialog, FORM_CLASS):

    """Dialog implementation class for the InaSAFE field mapping tool."""

    def __init__(self, parent=None, iface=None, setting=None):
        """Constructor."""
        QDialog.__init__(self, parent)
        self.setupUi(self)

        self.setWindowTitle(self.tr('InaSAFE Field Mapping Tool'))
        icon = resources_path('img', 'icons', 'show-mapping-tool.svg')
        self.setWindowIcon(QIcon(icon))
        self.parent = parent
        self.iface = iface
        if setting is None:
            setting = QSettings()
        self.setting = setting

        self.keyword_io = KeywordIO()

        self.layer = None
        self.metadata = {}

        self.layer_input_layout = QHBoxLayout()
        self.layer_label = QLabel(tr('Layer'))
        self.layer_combo_box = QgsMapLayerComboBox()
        # Filter only for Polygon and Point
        self.layer_combo_box.setFilters(
            QgsMapLayerProxyModel.PolygonLayer
            | QgsMapLayerProxyModel.PointLayer)
        # Filter out a layer that don't have layer groups
        excepted_layers = []
        for i in range(self.layer_combo_box.count()):
            layer = self.layer_combo_box.layer(i)
            try:
                keywords = self.keyword_io.read_keywords(layer)
            except (KeywordNotFoundError, NoKeywordsFoundError):
                excepted_layers.append(layer)
                continue
            layer_purpose = keywords.get('layer_purpose')
            if not layer_purpose:
                excepted_layers.append(layer)
                continue
            if layer_purpose == layer_purpose_exposure['key']:
                layer_subcategory = keywords.get('exposure')
            elif layer_purpose == layer_purpose_hazard['key']:
                layer_subcategory = keywords.get('hazard')
            else:
                layer_subcategory = None

            field_groups = get_field_groups(layer_purpose, layer_subcategory)
            if len(field_groups) == 0:
                excepted_layers.append(layer)
                continue
        self.layer_combo_box.setExceptedLayerList(excepted_layers)

        # Select the active layer.
        if self.iface.activeLayer():
            found = self.layer_combo_box.findText(
                self.iface.activeLayer().name())
            if found > -1:
                self.layer_combo_box.setLayer(self.iface.activeLayer())

        self.field_mapping_widget = None
        self.main_stacked_widget.setCurrentIndex(1)

        # Input
        self.layer_input_layout.addWidget(self.layer_label)
        self.layer_input_layout.addWidget(self.layer_combo_box)

        self.header_label = QLabel()
        self.header_label.setWordWrap(True)
        self.main_layout.addWidget(self.header_label)
        self.main_layout.addLayout(self.layer_input_layout)

        # Signal
        self.layer_combo_box.layerChanged.connect(self.set_layer)

        if self.layer_combo_box.currentLayer():
            self.set_layer(self.layer_combo_box.currentLayer())

        # Set up things for context help
        self.help_button = self.button_box.button(QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)

        # Set up things for ok button
        self.ok_button = self.button_box.button(QDialogButtonBox.Ok)
        self.ok_button.clicked.connect(self.accept)

        # Set up things for cancel button
        self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel)
        self.cancel_button.clicked.connect(self.reject)

    def set_layer(self, layer=None, keywords=None):
        """Set layer and update UI accordingly.

        :param layer: A QgsVectorLayer.
        :type layer: QgsVectorLayer

        :param keywords: Keywords for the layer.
        :type keywords: dict, None
        """
        if self.field_mapping_widget is not None:
            self.field_mapping_widget.setParent(None)
            self.field_mapping_widget.close()
            self.field_mapping_widget.deleteLater()
            self.main_layout.removeWidget(self.field_mapping_widget)
            self.field_mapping_widget = None

        if layer:
            self.layer = layer
        else:
            self.layer = self.layer_combo_box.currentLayer()
        if not self.layer:
            return

        if keywords is not None:
            self.metadata = keywords
        else:
            # Always read from metadata file.
            try:
                self.metadata = self.keyword_io.read_keywords(self.layer)
            except (
                    NoKeywordsFoundError,
                    KeywordNotFoundError,
                    MetadataReadError) as e:
                raise e
        if 'inasafe_default_values' not in self.metadata:
            self.metadata['inasafe_default_values'] = {}
        if 'inasafe_fields' not in self.metadata:
            self.metadata['inasafe_fields'] = {}
        self.field_mapping_widget = FieldMappingWidget(
            parent=self, iface=self.iface)
        self.field_mapping_widget.set_layer(self.layer, self.metadata)
        self.field_mapping_widget.show()
        self.main_layout.addWidget(self.field_mapping_widget)

        # Set header label
        group_names = [
            self.field_mapping_widget.tabText(i) for i in range(
                self.field_mapping_widget.count())]
        if len(group_names) == 0:
            header_text = tr(
                'There is no field group for this layer. Please select '
                'another layer.')
            self.header_label.setText(header_text)
            return
        elif len(group_names) == 1:
            pretty_group_name = group_names[0]
        elif len(group_names) == 2:
            pretty_group_name = group_names[0] + tr(' and ') + group_names[1]
        else:
            pretty_group_name = ', '.join(group_names[:-1])
            pretty_group_name += tr(', and {0}').format(group_names[-1])
        header_text = tr(
            'Please fill the information for every tab to determine the '
            'attribute for {0} group.').format(pretty_group_name)
        self.header_label.setText(header_text)

    @pyqtSlot(bool)  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the stacked widget.

        .. versionadded: 3.2.1

        :param flag: Flag indicating whether help should be shown or hidden.
        :type flag: bool
        """
        if flag:
            self.help_button.setText(self.tr('Hide Help'))
            self.show_help()
        else:
            self.help_button.setText(self.tr('Show Help'))
            self.hide_help()

    def hide_help(self):
        """Hide the usage info from the user.

        .. versionadded: 3.2.1
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = field_mapping_help()

        string += message.to_html()
        string += footer

        self.help_web_view.setHtml(string)

    def save_metadata(self):
        """Save metadata based on the field mapping state."""
        metadata = self.field_mapping_widget.get_field_mapping()
        for key, value in list(metadata['fields'].items()):
            # Delete the key if it's set to None
            if key in self.metadata['inasafe_default_values']:
                self.metadata['inasafe_default_values'].pop(key)
            if value is None or value == []:
                if key in self.metadata['inasafe_fields']:
                    self.metadata['inasafe_fields'].pop(key)
            else:
                self.metadata['inasafe_fields'][key] = value

        for key, value in list(metadata['values'].items()):
            # Delete the key if it's set to None
            if key in self.metadata['inasafe_fields']:
                self.metadata['inasafe_fields'].pop(key)
            if value is None:
                if key in self.metadata['inasafe_default_values']:
                    self.metadata['inasafe_default_values'].pop(key)
            else:
                self.metadata['inasafe_default_values'][key] = value

        # Save metadata
        try:
            self.keyword_io.write_keywords(
                layer=self.layer, keywords=self.metadata)
        except InaSAFEError as e:
            error_message = get_error_message(e)
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QMessageBox.warning(
                self, self.tr('InaSAFE'),
                ((self.tr(
                    'An error was encountered when saving the following '
                    'keywords:\n %s') % error_message.to_html())))

        # Update setting fir recent value
        if self.metadata.get('inasafe_default_values'):
            for key, value in \
                    list(self.metadata['inasafe_default_values'].items()):
                set_inasafe_default_value_qsetting(
                    self.setting, key, RECENT, value)

    def accept(self):
        """Method invoked when OK button is clicked."""
        try:
            self.save_metadata()
        except InvalidValidationException as e:
            display_warning_message_box(
                self, tr('Invalid Field Mapping'), str(e))
            return
        super(FieldMappingDialog, self).accept()
Exemplo n.º 11
0
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 = tr(
            'InaSAFE Keywords Creation Wizard')
        self.ifcw_name = tr('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.lblStep.clear()
        # Set icons
        self.lblMainIcon.setPixmap(
            QPixmap(resources_path('img', 'icons', 'icon-white.svg')))

        self.keyword_io = KeywordIO()

        self.is_selected_layer_keywordless = False
        self.parent_step = None

        self.pbnBack.setEnabled(False)
        self.pbnNext.setEnabled(False)
        self.pbnCancel.released.connect(self.reject)

        # Initialize attributes
        self.existing_keywords = None
        self.layer = None
        self.hazard_layer = None
        self.exposure_layer = None
        self.aggregation_layer = None

        self.step_kw_purpose = StepKwPurpose(self)
        self.step_kw_subcategory = StepKwSubcategory(self)
        self.step_kw_hazard_category = StepKwHazardCategory(self)
        self.step_kw_band_selector = StepKwBandSelector(self)
        self.step_kw_layermode = StepKwLayerMode(self)
        self.step_kw_unit = StepKwUnit(self)
        self.step_kw_classification = StepKwClassification(self)
        self.step_kw_field = StepKwField(self)
        self.step_kw_multi_classifications = StepKwMultiClassifications(self)
        self.step_kw_classify = StepKwClassify(self)
        self.step_kw_threshold = StepKwThreshold(self)
        self.step_kw_fields_mapping = StepKwFieldsMapping(self)
        self.step_kw_inasafe_fields = StepKwInaSAFEFields(self)
        self.step_kw_default_inasafe_fields = StepKwDefaultInaSAFEFields(self)
        self.step_kw_inasafe_raster_default_values = \
            StepKwInaSAFERasterDefaultValues(self)
        self.step_kw_source = StepKwSource(self)
        self.step_kw_title = StepKwTitle(self)
        self.step_kw_summary = StepKwSummary(self)

        self.step_fc_functions1 = StepFcFunctions1(self)
        self.step_fc_functions2 = StepFcFunctions2(self)
        self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self)
        self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self)
        self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self)
        self.step_fc_explayer_origin = StepFcExpLayerOrigin(self)
        self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self)
        self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self)
        self.step_fc_disjoint_layers = StepFcDisjointLayers(self)
        self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self)
        self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self)
        self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self)
        self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self)
        self.step_fc_extent = StepFcExtent(self)
        self.step_fc_extent_disjoint = StepFcExtentDisjoint(self)
        self.step_fc_summary = StepFcSummary(self)
        self.step_fc_analysis = StepFcAnalysis(self)

        self.wizard_help = WizardHelp(self)

        self.stackedWidget.addWidget(self.step_kw_purpose)
        self.stackedWidget.addWidget(self.step_kw_subcategory)
        self.stackedWidget.addWidget(self.step_kw_hazard_category)
        self.stackedWidget.addWidget(self.step_kw_band_selector)
        self.stackedWidget.addWidget(self.step_kw_layermode)
        self.stackedWidget.addWidget(self.step_kw_unit)
        self.stackedWidget.addWidget(self.step_kw_classification)
        self.stackedWidget.addWidget(self.step_kw_field)
        self.stackedWidget.addWidget(self.step_kw_multi_classifications)
        self.stackedWidget.addWidget(self.step_kw_classify)
        self.stackedWidget.addWidget(self.step_kw_threshold)
        self.stackedWidget.addWidget(self.step_kw_fields_mapping)
        self.stackedWidget.addWidget(self.step_kw_inasafe_fields)
        self.stackedWidget.addWidget(self.step_kw_default_inasafe_fields)
        self.stackedWidget.addWidget(
            self.step_kw_inasafe_raster_default_values)
        self.stackedWidget.addWidget(self.step_kw_source)
        self.stackedWidget.addWidget(self.step_kw_title)
        self.stackedWidget.addWidget(self.step_kw_summary)
        self.stackedWidget.addWidget(self.step_fc_functions1)
        self.stackedWidget.addWidget(self.step_fc_functions2)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_origin)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_explayer_origin)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_disjoint_layers)
        self.stackedWidget.addWidget(self.step_fc_agglayer_origin)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint)
        self.stackedWidget.addWidget(self.step_fc_extent)
        self.stackedWidget.addWidget(self.step_fc_extent_disjoint)
        self.stackedWidget.addWidget(self.step_fc_summary)
        self.stackedWidget.addWidget(self.step_fc_analysis)

        self.stackedWidget.addWidget(self.wizard_help)

        # QSetting
        self.setting = QSettings()

        # Wizard Steps
        self.impact_function_steps = []
        self.keyword_steps = []
        self.on_help = False

    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 = tr(
                'Keywords update wizard for layer <b>{layer_name}</b>').format(
                layer_name=self.layer.name())
        else:
            mode_name = tr(
                'Keywords creation wizard for layer <b>%s</b>').format(
                layer_name=self.layer.name())
        self.lblSubtitle.setText(mode_name)

    def set_mode_label_to_ifcw(self):
        """Set the mode label to the IFCW."""
        self.setWindowTitle(self.ifcw_name)
        self.lblSubtitle.setText(tr(
            'Use this wizard to run a guided impact assessment'))

    def set_keywords_creation_mode(self, layer=None, keywords=None):
        """Set the Wizard to the Keywords Creation mode.

        :param layer: Layer to set the keywords for
        :type layer: QgsMapLayer

        :param keywords: Keywords for the layer.
        :type keywords: dict, None
        """
        self.layer = layer or self.iface.mapCanvas().currentLayer()

        if keywords is not None:
            self.existing_keywords = keywords
        else:
            # Always read from metadata file.
            try:
                self.existing_keywords = self.keyword_io.read_keywords(
                    self.layer)
            except (HashNotFoundError,
                    OperationalError,
                    NoKeywordsFoundError,
                    KeywordNotFoundError,
                    InvalidParameterError,
                    UnsupportedProviderError,
                    MetadataReadError):
                self.existing_keywords = None
        self.set_mode_label_to_keywords_creation()

        step = self.step_kw_purpose
        step.set_widgets()
        self.go_to_step(step)

    def set_function_centric_mode(self):
        """Set the Wizard to the Function Centric mode."""
        self.set_mode_label_to_ifcw()

        step = self.step_fc_functions1
        step.set_widgets()
        self.go_to_step(step)

    def field_keyword_for_the_layer(self):
        """Return the proper keyword for field for the current layer.

        :returns: the field keyword
        :rtype: str
        """
        layer_purpose_key = self.step_kw_purpose.selected_purpose()['key']
        if layer_purpose_key == layer_purpose_aggregation['key']:
            return get_compulsory_fields(layer_purpose_key)['key']
        elif layer_purpose_key in [
                layer_purpose_exposure['key'], layer_purpose_hazard['key']]:
            layer_subcategory_key = \
                self.step_kw_subcategory.selected_subcategory()['key']
            return get_compulsory_fields(
                layer_purpose_key, layer_subcategory_key)['key']
        else:
            raise InvalidParameterError

    def get_parent_mode_constraints(self):
        """Return the category and subcategory keys to be set in the
        subordinate mode.

        :returns: (the category definition, the hazard/exposure definition)
        :rtype: (dict, dict)
        """
        h, e, _hc, _ec = self.selected_impact_function_constraints()
        if self.parent_step in [self.step_fc_hazlayer_from_canvas,
                                self.step_fc_hazlayer_from_browser]:
            category = layer_purpose_hazard
            subcategory = h
        elif self.parent_step in [self.step_fc_explayer_from_canvas,
                                  self.step_fc_explayer_from_browser]:
            category = layer_purpose_exposure
            subcategory = e
        elif self.parent_step:
            category = layer_purpose_aggregation
            subcategory = None
        else:
            category = None
            subcategory = None
        return category, subcategory

    def selected_impact_function_constraints(self):
        """Obtain impact function constraints selected by user.

        :returns: Tuple of metadata of hazard, exposure,
            hazard layer geometry and exposure layer geometry
        :rtype: tuple
        """

        hazard = self.step_fc_functions1.selected_value(
            layer_purpose_hazard['key'])
        exposure = self.step_fc_functions1.selected_value(
            layer_purpose_exposure['key'])

        hazard_geometry = self.step_fc_functions2.selected_value(
            layer_purpose_hazard['key'])
        exposure_geometry = self.step_fc_functions2.selected_value(
            layer_purpose_exposure['key'])
        return hazard, exposure, hazard_geometry, exposure_geometry

    def is_layer_compatible(self, layer, layer_purpose=None, 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: None, string

        :param keywords: The layer keywords
        :type keywords: None, dict

        :returns: True if layer is appropriate for the selected role
        :rtype: boolean
        """

        # If not explicitly stated, find the desired purpose
        # from the parent step
        if not layer_purpose:
            layer_purpose = self.get_parent_mode_constraints()[0]['key']

        # If not explicitly stated, read the layer's keywords
        if not keywords:
            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

        # 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_key(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 not is_keyword_version_supported(keyword_version):
            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

        return True

    def get_compatible_canvas_layers(self, category):
        """Collect layers from map canvas, compatible for the given category
           and selected impact function

        .. 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

    def get_layer_geometry_key(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 layer_geometry_raster['key']
        elif is_point_layer(layer):
            return layer_geometry_point['key']
        elif is_polygon_layer(layer):
            return layer_geometry_polygon['key']
        else:
            return layer_geometry_line['key']

    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, QUrl
        """
        if self.existing_keywords is None:
            return {}
        if keyword is not None:
            return self.existing_keywords.get(keyword, {})
        else:
            return {}

    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 purpose: The layer purpose of the layer to get the description.
        :type purpose: 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 == layer_purpose_hazard['key']:
            self.hazard_layer = layer
        elif purpose == layer_purpose_exposure['key']:
            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 = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.is_selected_layer_keywordless = True

        description = layer_description_html(layer, keywords)
        return description

    # ===========================
    # NAVIGATION
    # ===========================

    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 widget to be moved to.
        :type step: WizardStep
        """
        self.stackedWidget.setCurrentWidget(step)

        # Disable the Next button unless new data already entered
        self.pbnNext.setEnabled(step.is_ready_to_next_step())

        # Enable the Back button unless it's not the first step
        self.pbnBack.setEnabled(
            step not in [self.step_kw_purpose, self.step_fc_functions1] or
            self.parent_step is not None)

        # Set Next button label
        if (step in [self.step_kw_summary, self.step_fc_analysis] and
                self.parent_step is None):
            self.pbnNext.setText(tr('Finish'))
        elif step == self.step_fc_summary:
            self.pbnNext.setText(tr('Run'))
        else:
            self.pbnNext.setText(tr('Next'))

        # Run analysis after switching to the new step
        if step == self.step_fc_analysis:
            self.step_fc_analysis.setup_and_run_analysis()

        # Set lblSelectCategory label if entering the kw mode
        # from the ifcw mode
        if step == self.step_kw_purpose and self.parent_step:
            if self.parent_step in [self.step_fc_hazlayer_from_canvas,
                                    self.step_fc_hazlayer_from_browser]:
                text_label = category_question_hazard
            elif self.parent_step in [self.step_fc_explayer_from_canvas,
                                      self.step_fc_explayer_from_browser]:
                text_label = category_question_exposure
            else:
                text_label = category_question_aggregation
            self.step_kw_purpose.lblSelectCategory.setText(text_label)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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()

        if current_step == self.step_kw_fields_mapping:
            try:
                self.step_kw_fields_mapping.get_field_mapping()
            except InvalidValidationException as e:
                display_warning_message_box(
                    self, tr('Invalid Field Mapping'), get_string(e.message))
                return

        if current_step.step_type == STEP_FC:
            self.impact_function_steps.append(current_step)
        elif current_step.step_type == STEP_KW:
            self.keyword_steps.append(current_step)
        else:
            LOGGER.debug(current_step.step_type)
            raise InvalidWizardStep

        # Save keywords if it's the end of the keyword creation mode
        if current_step == self.step_kw_summary:
            self.save_current_keywords()

        # After any step involving Browser, add selected layer to map canvas
        if current_step in [self.step_fc_hazlayer_from_browser,
                            self.step_fc_explayer_from_browser,
                            self.step_fc_agglayer_from_browser]:
            if not QgsMapLayerRegistry.instance().mapLayersByName(
                    self.layer.name()):
                QgsMapLayerRegistry.instance().addMapLayers([self.layer])

                # Make the layer visible. Might be hidden by default. See #2925
                legend = self.iface.legendInterface()
                legend.setLayerVisible(self.layer, True)

        # After the extent selection, save the extent and disconnect signals
        if current_step == self.step_fc_extent:
            self.step_fc_extent.write_extent()

        # Determine the new step to be switched
        new_step = current_step.get_next_step()

        if new_step is not None:
            # Prepare the next tab
            new_step.set_widgets()
        else:
            # Wizard complete
            self.accept()
            return

        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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()
        if current_step.step_type == STEP_FC:
            new_step = self.impact_function_steps.pop()
        elif current_step.step_type == STEP_KW:
            try:
                new_step = self.keyword_steps.pop()
            except IndexError:
                new_step = self.impact_function_steps.pop()
        else:
            raise InvalidWizardStep

        # set focus to table widgets, as the inactive selection style is gray
        if new_step == self.step_fc_functions1:
            self.step_fc_functions1.tblFunctions1.setFocus()
        if new_step == self.step_fc_functions2:
            self.step_fc_functions2.tblFunctions2.setFocus()
        # Re-connect disconnected signals when coming back to the Extent step
        if new_step == self.step_fc_extent:
            self.step_fc_extent.set_widgets()
        # Set Next button label
        self.pbnNext.setText(tr('Next'))
        self.pbnNext.setEnabled(True)
        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnHelp_released(self):
        if self.on_help:
            self.pbnHelp.setText(tr('Show help'))
            self.wizard_help.restore_button_state()
            self.stackedWidget.setCurrentWidget(self.wizard_help.wizard_step)
        else:
            self.pbnHelp.setText(tr('Hide help'))
            self.wizard_help.show_help(self.get_current_step())
            self.stackedWidget.setCurrentWidget(self.wizard_help)

        self.on_help = not self.on_help

    def get_current_step(self):
        """Return current step of the wizard.

        :returns: Current step of the wizard.
        :rtype: WizardStep instance
        """
        return self.stackedWidget.currentWidget()

    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 = {}
        inasafe_fields = {}
        keywords['layer_geometry'] = self.get_layer_geometry_key()
        if self.step_kw_purpose.selected_purpose():
            keywords['layer_purpose'] = self.step_kw_purpose.\
                selected_purpose()['key']
        if self.step_kw_subcategory.selected_subcategory():
            key = self.step_kw_purpose.selected_purpose()['key']
            keywords[key] = self.step_kw_subcategory.\
                selected_subcategory()['key']
        if self.get_layer_geometry_key() == layer_geometry_raster['key']:
            if self.step_kw_band_selector.selected_band():
                keywords['active_band'] = self.step_kw_band_selector.\
                    selected_band()
        if keywords['layer_purpose'] == layer_purpose_hazard['key']:
            if self.step_kw_hazard_category.selected_hazard_category():
                keywords['hazard_category'] \
                    = self.step_kw_hazard_category.\
                    selected_hazard_category()['key']
        if self.step_kw_layermode.selected_layermode():
            keywords['layer_mode'] = self.step_kw_layermode.\
                selected_layermode()['key']
        if self.step_kw_unit.selected_unit():
            if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
                key = continuous_hazard_unit['key']
            else:
                key = exposure_unit['key']
            keywords[key] = self.step_kw_unit.selected_unit()['key']
        if self.step_kw_field.selected_fields():
            field_key = self.field_keyword_for_the_layer()
            inasafe_fields[field_key] = self.step_kw_field.selected_fields()
        if self.step_kw_classification.selected_classification():
            keywords['classification'] = self.step_kw_classification.\
                selected_classification()['key']

        if keywords['layer_purpose'] == layer_purpose_hazard['key']:
            multi_classifications = self.step_kw_multi_classifications.\
                get_current_state()
            value_maps = multi_classifications.get('value_maps')
            if value_maps is not None:
                keywords['value_maps'] = value_maps
            thresholds = multi_classifications.get('thresholds')
            if thresholds is not None:
                keywords['thresholds'] = thresholds
        else:
            if self.step_kw_layermode.selected_layermode():
                layer_mode = self.step_kw_layermode.selected_layermode()
                if layer_mode == layer_mode_continuous:
                    thresholds = self.step_kw_threshold.get_threshold()
                    if thresholds:
                        keywords['thresholds'] = thresholds
                elif layer_mode == layer_mode_classified:
                    value_map = self.step_kw_classify.selected_mapping()
                    if value_map:
                        keywords['value_map'] = value_map

        if self.step_kw_source.leSource.text():
            keywords['source'] = get_unicode(
                self.step_kw_source.leSource.text())
        if self.step_kw_source.leSource_url.text():
            keywords['url'] = get_unicode(
                self.step_kw_source.leSource_url.text())
        if self.step_kw_source.leSource_scale.text():
            keywords['scale'] = get_unicode(
                self.step_kw_source.leSource_scale.text())
        if self.step_kw_source.ckbSource_date.isChecked():
            keywords['date'] = self.step_kw_source.dtSource_date.dateTime()
        if self.step_kw_source.leSource_license.text():
            keywords['license'] = get_unicode(
                self.step_kw_source.leSource_license.text())
        if self.step_kw_title.leTitle.text():
            keywords['title'] = get_unicode(self.step_kw_title.leTitle.text())

        inasafe_fields.update(self.step_kw_inasafe_fields.get_inasafe_fields())
        inasafe_fields.update(
            self.step_kw_default_inasafe_fields.get_inasafe_fields())
        inasafe_fields.update(
            self.step_kw_fields_mapping.get_field_mapping()['fields'])

        if inasafe_fields:
            keywords['inasafe_fields'] = inasafe_fields

        inasafe_default_values = {}
        if keywords['layer_geometry'] == layer_geometry_raster['key']:
            pass
            # Notes(IS): Skipped assigning raster inasafe default value for
            # now.
            # inasafe_default_values = self.\
            #     step_kw_inasafe_raster_default_values.\
            #     get_inasafe_default_values()
        else:
            inasafe_default_values.update(
                self.step_kw_default_inasafe_fields.get_inasafe_default_values(
                ))
            inasafe_default_values.update(
                self.step_kw_fields_mapping.get_field_mapping()['values'])

        if inasafe_default_values:
            keywords['inasafe_default_values'] = inasafe_default_values

        return keywords

    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,
                tr('InaSAFE'),
                tr('An error was encountered when saving the following '
                   'keywords:\n {error_message}').format(
                    error_message=error_message.to_html()))
        if self.dock is not None:
            # noinspection PyUnresolvedReferences
            self.dock.get_layers()

        # Save default value to QSetting
        if current_keywords.get('inasafe_default_values'):
            for key, value in (
                    current_keywords['inasafe_default_values'].items()):
                set_inasafe_default_value_qsetting(
                    self.setting, RECENT, key, value)
Exemplo n.º 12
0
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.lblStep.clear()
        # Set icons
        self.lblMainIcon.setPixmap(
            QPixmap(resources_path('img', 'icons', 'icon-white.svg')))

        self.keyword_io = KeywordIO()
        self.impact_function_manager = ImpactFunctionManager()

        self.is_selected_layer_keywordless = False
        self.parent_step = None

        self.pbnBack.setEnabled(False)
        self.pbnNext.setEnabled(False)
        self.pbnCancel.released.connect(self.reject)

        # Initialize attributes
        self.existing_keywords = None
        self.layer = None
        self.hazard_layer = None
        self.exposure_layer = None
        self.aggregation_layer = None
        self.analysis_handler = None

        self.step_kw_purpose = StepKwPurpose(self)
        self.step_kw_subcategory = StepKwSubcategory(self)
        self.step_kw_hazard_category = StepKwHazardCategory(self)
        self.step_kw_layermode = StepKwLayerMode(self)
        self.step_kw_unit = StepKwUnit(self)
        self.step_kw_classification = StepKwClassification(self)
        self.step_kw_field = StepKwField(self)
        self.step_kw_resample = StepKwResample(self)
        self.step_kw_classify = StepKwClassify(self)
        self.step_kw_name_field = StepKwNameField(self)
        self.step_kw_population_field = StepKwPopulationField(self)
        self.step_kw_extrakeywords = StepKwExtraKeywords(self)
        self.step_kw_aggregation = StepKwAggregation(self)
        self.step_kw_source = StepKwSource(self)
        self.step_kw_title = StepKwTitle(self)
        self.step_kw_summary = StepKwSummary(self)
        self.step_fc_functions1 = StepFcFunctions1(self)
        self.step_fc_functions2 = StepFcFunctions2(self)
        self.step_fc_function = StepFcFunction(self)
        self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self)
        self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self)
        self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self)
        self.step_fc_explayer_origin = StepFcExpLayerOrigin(self)
        self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self)
        self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self)
        self.step_fc_disjoint_layers = StepFcDisjointLayers(self)
        self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self)
        self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self)
        self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self)
        self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self)
        self.step_fc_extent = StepFcExtent(self)
        self.step_fc_extent_disjoint = StepFcExtentDisjoint(self)
        self.step_fc_params = StepFcParams(self)
        self.step_fc_summary = StepFcSummary(self)
        self.step_fc_analysis = StepFcAnalysis(self)
        self.stackedWidget.addWidget(self.step_kw_purpose)
        self.stackedWidget.addWidget(self.step_kw_subcategory)
        self.stackedWidget.addWidget(self.step_kw_hazard_category)
        self.stackedWidget.addWidget(self.step_kw_layermode)
        self.stackedWidget.addWidget(self.step_kw_unit)
        self.stackedWidget.addWidget(self.step_kw_classification)
        self.stackedWidget.addWidget(self.step_kw_field)
        self.stackedWidget.addWidget(self.step_kw_resample)
        self.stackedWidget.addWidget(self.step_kw_classify)
        self.stackedWidget.addWidget(self.step_kw_name_field)
        self.stackedWidget.addWidget(self.step_kw_population_field)
        self.stackedWidget.addWidget(self.step_kw_extrakeywords)
        self.stackedWidget.addWidget(self.step_kw_aggregation)
        self.stackedWidget.addWidget(self.step_kw_source)
        self.stackedWidget.addWidget(self.step_kw_title)
        self.stackedWidget.addWidget(self.step_kw_summary)
        self.stackedWidget.addWidget(self.step_fc_functions1)
        self.stackedWidget.addWidget(self.step_fc_functions2)
        self.stackedWidget.addWidget(self.step_fc_function)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_origin)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_explayer_origin)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_disjoint_layers)
        self.stackedWidget.addWidget(self.step_fc_agglayer_origin)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint)
        self.stackedWidget.addWidget(self.step_fc_extent)
        self.stackedWidget.addWidget(self.step_fc_extent_disjoint)
        self.stackedWidget.addWidget(self.step_fc_params)
        self.stackedWidget.addWidget(self.step_fc_summary)
        self.stackedWidget.addWidget(self.step_fc_analysis)

    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)

    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'))

    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, MetadataReadError):
            self.existing_keywords = None
        self.set_mode_label_to_keywords_creation()

        step = self.step_kw_purpose
        step.set_widgets()
        self.go_to_step(step)

    def set_function_centric_mode(self):
        """Set the Wizard to the Function Centric mode"""
        self.set_mode_label_to_ifcw()

        step = self.step_fc_functions1
        step.set_widgets()
        self.go_to_step(step)

    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.step_kw_purpose.selected_purpose() == \
                layer_purpose_aggregation:
            # purpose: aggregation
            return 'aggregation attribute'
        elif self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
            # purpose: hazard
            if (self.step_kw_layermode.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.step_kw_layermode.selected_layermode()['key']
            layer_geometry_key = self.get_layer_geometry_id()
            exposure_key = self.step_kw_subcategory.\
                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'

    def get_parent_mode_constraints(self):
        """Return the category and subcategory keys to be set in the
        subordinate mode.

        :returns: (the category definition, the hazard/exposure definition)
        :rtype: (dict, dict)
        """
        h, e, _hc, _ec = self.selected_impact_function_constraints()
        if self.parent_step in [
                self.step_fc_hazlayer_from_canvas,
                self.step_fc_hazlayer_from_browser
        ]:
            category = layer_purpose_hazard
            subcategory = h
        elif self.parent_step in [
                self.step_fc_explayer_from_canvas,
                self.step_fc_explayer_from_browser
        ]:
            category = layer_purpose_exposure
            subcategory = e
        elif self.parent_step:
            category = layer_purpose_aggregation
            subcategory = None
        else:
            category = None
            subcategory = None
        return category, subcategory

    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.step_fc_functions1.tblFunctions1.selectedItems()
        if len(selection) != 1:
            return None, None, None, None

        h = selection[0].data(RoleHazard)
        e = selection[0].data(RoleExposure)

        selection = self.step_fc_functions2.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

    def is_layer_compatible(self, layer, layer_purpose=None, 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: None, string

        :param keywords: The layer keywords
        :type keywords: None, dict

        :returns: True if layer is appropriate for the selected role
        :rtype: boolean
        """

        # If not explicitly stated, find the desired purpose
        # from the parent step
        if not layer_purpose:
            layer_purpose = self.get_parent_mode_constraints()[0]['key']

        # If not explicitly stated, read the layer's keywords
        if not keywords:
            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

        # 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 not is_keyword_version_supported(keyword_version):
            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.step_fc_function.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

    def get_compatible_canvas_layers(self, category):
        """Collect layers from map canvas, compatible for the given category
           and selected impact function

        .. 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

    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'

    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, QUrl
        """
        if self.existing_keywords is None:
            return None
        if keyword is not None:
            return self.existing_keywords.get(keyword, None)
        else:
            return None

    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 = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.is_selected_layer_keywordless = True

        desc = layer_description_html(layer, keywords)
        return desc

    # ===========================
    # NAVIGATION
    # ===========================

    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 widget to be moved to.
        :type step: QWidget
        """
        self.stackedWidget.setCurrentWidget(step)

        # Disable the Next button unless new data already entered
        self.pbnNext.setEnabled(step.is_ready_to_next_step())

        # Enable the Back button unless it's not the first step
        self.pbnBack.setEnabled(
            step not in [self.step_kw_purpose, self.step_fc_functions1]
            or self.parent_step is not None)

        # Set Next button label
        if (step in [self.step_kw_summary, self.step_fc_analysis]
                and self.parent_step is None):
            self.pbnNext.setText(self.tr('Finish'))
        elif step == self.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 == self.step_fc_analysis:
            self.step_fc_analysis.setup_and_run_analysis()

        # Set lblSelectCategory label if entering the kw mode
        # from the ifcw mode
        if step == self.step_kw_purpose and self.parent_step:
            if self.parent_step in [
                    self.step_fc_hazlayer_from_canvas,
                    self.step_fc_hazlayer_from_browser
            ]:
                text_label = category_question_hazard
            elif self.parent_step in [
                    self.step_fc_explayer_from_canvas,
                    self.step_fc_explayer_from_browser
            ]:
                text_label = category_question_exposure
            else:
                text_label = category_question_aggregation
            self.step_kw_purpose.lblSelectCategory.setText(text_label)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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 == self.step_kw_summary:
            self.save_current_keywords()

        if current_step == self.step_kw_aggregation:
            good_age_ratio, sum_age_ratios = self.step_kw_aggregation.\
                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 [
                self.step_fc_hazlayer_from_browser,
                self.step_fc_explayer_from_browser,
                self.step_fc_agglayer_from_browser
        ]:
            if not QgsMapLayerRegistry.instance().mapLayersByName(
                    self.layer.name()):
                QgsMapLayerRegistry.instance().addMapLayers([self.layer])

                # Make the layer visible. Might be hidden by default. See #2925
                legend = self.iface.legendInterface()
                legend.setLayerVisible(self.layer, True)

        # After the extent selection, save the extent and disconnect signals
        if current_step == self.step_fc_extent:
            self.step_fc_extent.write_extent()

        # Determine the new step to be switched
        new_step = current_step.get_next_step()
        if (new_step == self.step_kw_extrakeywords and not self.
                step_kw_extrakeywords.additional_keywords_for_the_layer()):
            # Skip the extra_keywords tab if no extra keywords available:
            new_step = self.step_kw_source

        if new_step is not None:
            # Prepare the next tab
            new_step.set_widgets()
        else:
            # Wizard complete
            self.accept()
            return

        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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 = current_step.get_previous_step()
        # set focus to table widgets, as the inactive selection style is gray
        if new_step == self.step_fc_functions1:
            self.step_fc_functions1.tblFunctions1.setFocus()
        if new_step == self.step_fc_functions2:
            self.step_fc_functions2.tblFunctions2.setFocus()
        # Re-connect disconnected signals when coming back to the Extent step
        if new_step == self.step_fc_extent:
            self.step_fc_extent.set_widgets()
        # Set Next button label
        self.pbnNext.setText(self.tr('Next'))
        self.pbnNext.setEnabled(True)
        self.go_to_step(new_step)

    def get_current_step(self):
        """Return current step of the wizard.

        :returns: Current step of the wizard.
        :rtype: WizardStep instance
        """
        return self.stackedWidget.currentWidget()

    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.step_kw_purpose.selected_purpose():
            keywords['layer_purpose'] = self.step_kw_purpose.\
                selected_purpose()['key']
            if keywords['layer_purpose'] == 'aggregation':
                keywords.update(
                    self.step_kw_aggregation.get_aggregation_attributes())
        if self.step_kw_subcategory.selected_subcategory():
            key = self.step_kw_purpose.selected_purpose()['key']
            keywords[key] = self.step_kw_subcategory.\
                selected_subcategory()['key']
        if self.step_kw_hazard_category.selected_hazard_category():
            keywords['hazard_category'] \
                = self.step_kw_hazard_category.\
                selected_hazard_category()['key']
        if self.step_kw_layermode.selected_layermode():
            keywords['layer_mode'] = self.step_kw_layermode.\
                selected_layermode()['key']
        if self.step_kw_unit.selected_unit():
            if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
                key = continuous_hazard_unit['key']
            else:
                key = exposure_unit['key']
            keywords[key] = self.step_kw_unit.selected_unit()['key']
        if self.step_kw_resample.selected_allowresampling() is not None:
            keywords['allow_resampling'] = (
                self.step_kw_resample.selected_allowresampling() and 'true'
                or 'false')
        if self.step_kw_field.lstFields.currentItem():
            field_keyword = self.field_keyword_for_the_layer()
            keywords[field_keyword] = self.step_kw_field.\
                lstFields.currentItem().text()
        if self.step_kw_classification.selected_classification():
            geom = 'raster' if is_raster_layer(self.layer) else 'vector'
            key = '%s_%s_classification' % (
                geom, self.step_kw_purpose.selected_purpose()['key'])
            keywords[key] = self.step_kw_classification.\
                selected_classification()['key']
        value_map = self.step_kw_classify.selected_mapping()
        if value_map:
            if self.step_kw_classification.selected_classification():
                # hazard mapping
                keyword = 'value_map'
            else:
                # exposure mapping
                keyword = 'value_mapping'
            keywords[keyword] = json.dumps(value_map)

        name_field = self.step_kw_name_field.selected_field()
        if name_field:
            keywords['name_field'] = name_field

        population_field = self.step_kw_population_field.selected_field()
        if population_field:
            keywords['population_field'] = population_field

        extra_keywords = self.step_kw_extrakeywords.selected_extra_keywords()
        for key in extra_keywords:
            keywords[key] = extra_keywords[key]
        if self.step_kw_source.leSource.text():
            keywords['source'] = get_unicode(
                self.step_kw_source.leSource.text())
        if self.step_kw_source.leSource_url.text():
            keywords['url'] = get_unicode(
                self.step_kw_source.leSource_url.text())
        if self.step_kw_source.leSource_scale.text():
            keywords['scale'] = get_unicode(
                self.step_kw_source.leSource_scale.text())
        if self.step_kw_source.ckbSource_date.isChecked():
            keywords['date'] = self.step_kw_source.dtSource_date.dateTime()
        if self.step_kw_source.leSource_license.text():
            keywords['license'] = get_unicode(
                self.step_kw_source.leSource_license.text())
        if self.step_kw_title.leTitle.text():
            keywords['title'] = get_unicode(self.step_kw_title.leTitle.text())

        return keywords

    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()
    def accept(self):
        """Handler for when OK is clicked."""
        input_path = self.input_path.text()
        input_title = self.line_edit_title.text()
        input_source = self.line_edit_source.text()
        output_path = self.output_path.text()
        if not output_path.endswith('.tif'):
            # noinspection PyArgumentList,PyCallByClass,PyTypeChecker
            QMessageBox.warning(
                self,
                self.tr('InaSAFE'),
                (self.tr('Output file name must be tif file')))
        if not os.path.exists(input_path):
            # noinspection PyArgumentList,PyCallByClass,PyTypeChecker
            QMessageBox.warning(
                self,
                self.tr('InaSAFE'),
                (self.tr('Input file does not exist')))
            return

        if self.nearest_mode.isChecked():
            algorithm = 'nearest'
        else:
            algorithm = 'invdist'

        QtGui.qApp.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))

        file_name = convert_mmi_data(
            input_path,
            input_title,
            input_source,
            output_path,
            algorithm=algorithm,
            algorithm_filename_flag=True)

        # reclassify raster
        file_info = QFileInfo(file_name)
        base_name = file_info.baseName()
        self.output_layer = QgsRasterLayer(file_name, base_name)
        self.output_layer.keywords = KeywordIO.read_keywords(self.output_layer)
        self.output_layer.keywords['classification'] = (
            earthquake_mmi_scale['key'])
        keywords = self.output_layer.keywords
        if self.output_layer.isValid():
            self.output_layer = reclassify(
                self.output_layer, overwrite_input=True)
            KeywordIO.write_keywords(self.output_layer, keywords)
        else:
            LOGGER.debug("Failed to load")

        QtGui.qApp.restoreOverrideCursor()

        if self.load_result.isChecked():
            # noinspection PyTypeChecker
            mmi_ramp_roman(self.output_layer)
            self.output_layer.saveDefaultStyle()
            if not self.output_layer.isValid():
                LOGGER.debug("Failed to load")
            else:
                # noinspection PyArgumentList
                QgsMapLayerRegistry.instance().addMapLayer(self.output_layer)
                iface.zoomToActiveLayer()

        if (self.keyword_wizard_checkbox.isChecked() and
                self.keyword_wizard_checkbox.isEnabled()):
            self.launch_keyword_wizard()

        self.done(self.Accepted)
Exemplo n.º 14
0
    def save_hazard_data(self):
        if self.dummy_report_folder:
            filename = os.path.join(
                self.working_dir, self.dummy_report_folder, 'flood_data.json')
            hazard_geojson = DummySourceAPI.get_aggregate_report(filename)
        else:
            hazard_geojson = PetaJakartaAPI.get_aggregate_report(
                self.duration, self.level)

        if not hazard_geojson:
            raise PetaJakartaAPIError("Can't access PetaJakarta REST API")

        with open(self.hazard_path, 'w+') as f:
            f.write(hazard_geojson)

        # Save the layer as shp
        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(
            self.hazard_path, file_info.baseName(), 'ogr', False)

        target_name = 'flood_data.shp'
        self.hazard_path = os.path.join(self.report_path, target_name)
        QgsVectorFileWriter.writeAsVectorFormat(
            hazard_layer,
            self.hazard_path,
            'CP1250',
            None,
            'ESRI Shapefile')

        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(
            self.hazard_path, file_info.baseName(), 'ogr')

        # hazard_layer.startEditing()
        # field = QgsField('flooded', QVariant.Int)
        # hazard_layer.dataProvider().addAttributes([field])
        # hazard_layer.commitChanges()
        # idx = hazard_layer.fieldNameIndex('flooded')
        # expression = QgsExpression('count > 0')
        # expression.prepare(hazard_layer.pendingFields())
        #
        # hazard_layer.startEditing()
        # for feature in hazard_layer.getFeatures():
        #     feature[idx] = expression.evaluate(feature)
        #     hazard_layer.updateFeature(feature)
        #
        # hazard_layer.commitChanges()

        # writing keywords
        keyword_io = KeywordIO()

        keywords = {
            'field': 'state',
            'hazard': 'generic',
            'hazard_category': 'single_event',
            'keyword_version': '3.5',
            'layer_geometry': 'polygon',
            'layer_mode': 'classified',
            'layer_purpose': 'hazard',
            'title': 'Flood',
            'value_map': '{"high": [4], "medium": [3], '
                         '"low": [2], '
                         '"unaffected": ["None","","NULL",0,1]}',
            'vector_hazard_classification': 'generic_vector_hazard_classes'
        }

        keyword_io.write_keywords(hazard_layer, keywords)

        # copy layer styles
        style_path = self.flood_fixtures_dir(
            'flood_data_classified_state.qml')
        target_style_path = os.path.join(
            self.report_path, 'flood_data.qml')
        shutil.copy(style_path, target_style_path)

        # archiving hazard layer
        with ZipFile(self.hazard_zip_path, 'w') as zf:
            for root, dirs, files in os.walk(self.report_path):
                for f in files:
                    _, ext = os.path.splitext(f)
                    if 'flood_data' in f:
                        filename = os.path.join(root, f)
                        zf.write(filename, arcname=f)
Exemplo n.º 15
0
    def create_keyword_file(self, algorithm):
        """Create keyword file for the raster file created.

        Basically copy a template from keyword file in converter data
        and add extra keyword (usually a title)

        :param algorithm: Which re-sampling algorithm to use.
            valid options are 'nearest' (for nearest neighbour), 'invdist'
            (for inverse distance), 'average' (for moving average). Defaults
            to 'nearest' if not specified. Note that passing re-sampling alg
            parameters is currently not supported. If None is passed it will
            be replaced with 'nearest'.
        :type algorithm: str
        """
        keyword_io = KeywordIO()

        # Set thresholds for each exposure
        mmi_default_classes = default_classification_thresholds(
            earthquake_mmi_scale
        )
        mmi_default_threshold = {
            earthquake_mmi_scale['key']: {
                'active': True,
                'classes': mmi_default_classes
            }
        }
        generic_default_classes = default_classification_thresholds(
            generic_hazard_classes
        )
        generic_default_threshold = {
            generic_hazard_classes['key']: {
                'active': True,
                'classes': generic_default_classes
            }
        }

        threshold_keyword = {}
        for exposure in exposure_all:
            # Not all exposure is supported by earthquake_mmi_scale
            if exposure in earthquake_mmi_scale['exposures']:
                threshold_keyword[exposure['key']] = mmi_default_threshold
            else:
                threshold_keyword[
                    exposure['key']] = generic_default_threshold

        extra_keywords = {
            extra_keyword_earthquake_latitude['key']: self.latitude,
            extra_keyword_earthquake_longitude['key']: self.longitude,
            extra_keyword_earthquake_magnitude['key']: self.magnitude,
            extra_keyword_earthquake_depth['key']: self.depth,
            extra_keyword_earthquake_description['key']: self.description,
            extra_keyword_earthquake_location['key']: self.location,
            extra_keyword_earthquake_event_time['key']: self.time.strftime(
                '%Y-%m-%dT%H:%M:%S'),
            extra_keyword_time_zone['key']: self.time_zone,
            extra_keyword_earthquake_x_minimum['key']: self.x_minimum,
            extra_keyword_earthquake_x_maximum['key']: self.x_maximum,
            extra_keyword_earthquake_y_minimum['key']: self.y_minimum,
            extra_keyword_earthquake_y_maximum['key']: self.y_maximum,
            extra_keyword_earthquake_event_id['key']: self.event_id
        }
        for key, value in list(self.extra_keywords.items()):
            extra_keywords[key] = value

        # Delete empty element.
        empty_keys = []
        for key, value in list(extra_keywords.items()):
            if value is None:
                empty_keys.append(key)
        for empty_key in empty_keys:
            extra_keywords.pop(empty_key)
        keywords = {
            'hazard': hazard_earthquake['key'],
            'hazard_category': hazard_category_single_event['key'],
            'keyword_version': inasafe_keyword_version,
            'layer_geometry': layer_geometry_raster['key'],
            'layer_mode': layer_mode_continuous['key'],
            'layer_purpose': layer_purpose_hazard['key'],
            'continuous_hazard_unit': unit_mmi['key'],
            'classification': earthquake_mmi_scale['key'],
            'thresholds': threshold_keyword,
            'extra_keywords': extra_keywords,
            'active_band': 1
        }

        if self.algorithm_name:
            layer_path = os.path.join(
                self.output_dir, '%s-%s.tif' % (
                    self.output_basename, algorithm))
        else:
            layer_path = os.path.join(
                self.output_dir, '%s.tif' % self.output_basename)

        # append title and source to the keywords file
        if len(self.title.strip()) == 0:
            keyword_title = self.output_basename
        else:
            keyword_title = self.title

        keywords['title'] = keyword_title

        hazard_layer = QgsRasterLayer(layer_path, keyword_title)

        if not hazard_layer.isValid():
            raise InvalidLayerError()

        keyword_io.write_keywords(hazard_layer, keywords)
Exemplo n.º 16
0
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.lblStep.clear()
        # Set icons
        self.lblMainIcon.setPixmap(
            QPixmap(resources_path('img', 'icons', 'icon-white.svg')))

        self.keyword_io = KeywordIO()
        self.impact_function_manager = ImpactFunctionManager()

        self.is_selected_layer_keywordless = False
        self.parent_step = None

        self.pbnBack.setEnabled(False)
        self.pbnNext.setEnabled(False)
        self.pbnCancel.released.connect(self.reject)

        # Initialize attributes
        self.existing_keywords = None
        self.layer = None
        self.hazard_layer = None
        self.exposure_layer = None
        self.aggregation_layer = None
        self.analysis_handler = None

        self.step_kw_purpose = StepKwPurpose(self)
        self.step_kw_subcategory = StepKwSubcategory(self)
        self.step_kw_hazard_category = StepKwHazardCategory(self)
        self.step_kw_layermode = StepKwLayerMode(self)
        self.step_kw_unit = StepKwUnit(self)
        self.step_kw_classification = StepKwClassification(self)
        self.step_kw_field = StepKwField(self)
        self.step_kw_resample = StepKwResample(self)
        self.step_kw_classify = StepKwClassify(self)
        self.step_kw_name_field = StepKwNameField(self)
        self.step_kw_population_field = StepKwPopulationField(self)
        self.step_kw_extrakeywords = StepKwExtraKeywords(self)
        self.step_kw_aggregation = StepKwAggregation(self)
        self.step_kw_source = StepKwSource(self)
        self.step_kw_title = StepKwTitle(self)
        self.step_kw_summary = StepKwSummary(self)
        self.step_fc_functions1 = StepFcFunctions1(self)
        self.step_fc_functions2 = StepFcFunctions2(self)
        self.step_fc_function = StepFcFunction(self)
        self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self)
        self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self)
        self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self)
        self.step_fc_explayer_origin = StepFcExpLayerOrigin(self)
        self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self)
        self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self)
        self.step_fc_disjoint_layers = StepFcDisjointLayers(self)
        self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self)
        self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self)
        self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self)
        self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self)
        self.step_fc_extent = StepFcExtent(self)
        self.step_fc_extent_disjoint = StepFcExtentDisjoint(self)
        self.step_fc_params = StepFcParams(self)
        self.step_fc_summary = StepFcSummary(self)
        self.step_fc_analysis = StepFcAnalysis(self)
        self.stackedWidget.addWidget(self.step_kw_purpose)
        self.stackedWidget.addWidget(self.step_kw_subcategory)
        self.stackedWidget.addWidget(self.step_kw_hazard_category)
        self.stackedWidget.addWidget(self.step_kw_layermode)
        self.stackedWidget.addWidget(self.step_kw_unit)
        self.stackedWidget.addWidget(self.step_kw_classification)
        self.stackedWidget.addWidget(self.step_kw_field)
        self.stackedWidget.addWidget(self.step_kw_resample)
        self.stackedWidget.addWidget(self.step_kw_classify)
        self.stackedWidget.addWidget(self.step_kw_name_field)
        self.stackedWidget.addWidget(self.step_kw_population_field)
        self.stackedWidget.addWidget(self.step_kw_extrakeywords)
        self.stackedWidget.addWidget(self.step_kw_aggregation)
        self.stackedWidget.addWidget(self.step_kw_source)
        self.stackedWidget.addWidget(self.step_kw_title)
        self.stackedWidget.addWidget(self.step_kw_summary)
        self.stackedWidget.addWidget(self.step_fc_functions1)
        self.stackedWidget.addWidget(self.step_fc_functions2)
        self.stackedWidget.addWidget(self.step_fc_function)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_origin)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_explayer_origin)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_disjoint_layers)
        self.stackedWidget.addWidget(self.step_fc_agglayer_origin)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint)
        self.stackedWidget.addWidget(self.step_fc_extent)
        self.stackedWidget.addWidget(self.step_fc_extent_disjoint)
        self.stackedWidget.addWidget(self.step_fc_params)
        self.stackedWidget.addWidget(self.step_fc_summary)
        self.stackedWidget.addWidget(self.step_fc_analysis)

    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)

    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'))

    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,
                MetadataReadError):
            self.existing_keywords = None
        self.set_mode_label_to_keywords_creation()

        step = self.step_kw_purpose
        step.set_widgets()
        self.go_to_step(step)

    def set_function_centric_mode(self):
        """Set the Wizard to the Function Centric mode"""
        self.set_mode_label_to_ifcw()

        step = self.step_fc_functions1
        step.set_widgets()
        self.go_to_step(step)

    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.step_kw_purpose.selected_purpose() == \
                layer_purpose_aggregation:
            # purpose: aggregation
            return 'aggregation attribute'
        elif self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
            # purpose: hazard
            if (self.step_kw_layermode.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.step_kw_layermode.selected_layermode()['key']
            layer_geometry_key = self.get_layer_geometry_id()
            exposure_key = self.step_kw_subcategory.\
                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'

    def get_parent_mode_constraints(self):
        """Return the category and subcategory keys to be set in the
        subordinate mode.

        :returns: (the category definition, the hazard/exposure definition)
        :rtype: (dict, dict)
        """
        h, e, _hc, _ec = self.selected_impact_function_constraints()
        if self.parent_step in [self.step_fc_hazlayer_from_canvas,
                                self.step_fc_hazlayer_from_browser]:
            category = layer_purpose_hazard
            subcategory = h
        elif self.parent_step in [self.step_fc_explayer_from_canvas,
                                  self.step_fc_explayer_from_browser]:
            category = layer_purpose_exposure
            subcategory = e
        elif self.parent_step:
            category = layer_purpose_aggregation
            subcategory = None
        else:
            category = None
            subcategory = None
        return category, subcategory

    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.step_fc_functions1.tblFunctions1.selectedItems()
        if len(selection) != 1:
            return None, None, None, None

        h = selection[0].data(RoleHazard)
        e = selection[0].data(RoleExposure)

        selection = self.step_fc_functions2.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

    def is_layer_compatible(self, layer, layer_purpose=None, 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: None, string

        :param keywords: The layer keywords
        :type keywords: None, dict

        :returns: True if layer is appropriate for the selected role
        :rtype: boolean
        """

        # If not explicitly stated, find the desired purpose
        # from the parent step
        if not layer_purpose:
            layer_purpose = self.get_parent_mode_constraints()[0]['key']

        # If not explicitly stated, read the layer's keywords
        if not keywords:
            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

        # 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 not is_keyword_version_supported(keyword_version):
            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.step_fc_function.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

    def get_compatible_canvas_layers(self, category):
        """Collect layers from map canvas, compatible for the given category
           and selected impact function

        .. 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

    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'

    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, QUrl
        """
        if self.existing_keywords is None:
            return None
        if keyword is not None:
            return self.existing_keywords.get(keyword, None)
        else:
            return None

    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 = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.is_selected_layer_keywordless = True

        desc = layer_description_html(layer, keywords)
        return desc

    # ===========================
    # NAVIGATION
    # ===========================

    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 widget to be moved to.
        :type step: QWidget
        """
        self.stackedWidget.setCurrentWidget(step)

        # Disable the Next button unless new data already entered
        self.pbnNext.setEnabled(step.is_ready_to_next_step())

        # Enable the Back button unless it's not the first step
        self.pbnBack.setEnabled(
            step not in [self.step_kw_purpose, self.step_fc_functions1] or
            self.parent_step is not None)

        # Set Next button label
        if (step in [self.step_kw_summary, self.step_fc_analysis] and
                self.parent_step is None):
            self.pbnNext.setText(self.tr('Finish'))
        elif step == self.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 == self.step_fc_analysis:
            self.step_fc_analysis.setup_and_run_analysis()

        # Set lblSelectCategory label if entering the kw mode
        # from the ifcw mode
        if step == self.step_kw_purpose and self.parent_step:
            if self.parent_step in [self.step_fc_hazlayer_from_canvas,
                                    self.step_fc_hazlayer_from_browser]:
                text_label = category_question_hazard
            elif self.parent_step in [self.step_fc_explayer_from_canvas,
                                      self.step_fc_explayer_from_browser]:
                text_label = category_question_exposure
            else:
                text_label = category_question_aggregation
            self.step_kw_purpose.lblSelectCategory.setText(text_label)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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 == self.step_kw_summary:
            self.save_current_keywords()

        if current_step == self.step_kw_aggregation:
            good_age_ratio, sum_age_ratios = self.step_kw_aggregation.\
                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 [self.step_fc_hazlayer_from_browser,
                            self.step_fc_explayer_from_browser,
                            self.step_fc_agglayer_from_browser]:
            if not QgsMapLayerRegistry.instance().mapLayersByName(
                    self.layer.name()):
                QgsMapLayerRegistry.instance().addMapLayers([self.layer])

                # Make the layer visible. Might be hidden by default. See #2925
                legend = self.iface.legendInterface()
                legend.setLayerVisible(self.layer, True)

        # After the extent selection, save the extent and disconnect signals
        if current_step == self.step_fc_extent:
            self.step_fc_extent.write_extent()

        # Determine the new step to be switched
        new_step = current_step.get_next_step()
        if (new_step == self.step_kw_extrakeywords and not
                self.step_kw_extrakeywords.
                additional_keywords_for_the_layer()):
            # Skip the extra_keywords tab if no extra keywords available:
            new_step = self.step_kw_source

        if new_step is not None:
            # Prepare the next tab
            new_step.set_widgets()
        else:
            # Wizard complete
            self.accept()
            return

        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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 = current_step.get_previous_step()
        # set focus to table widgets, as the inactive selection style is gray
        if new_step == self.step_fc_functions1:
            self.step_fc_functions1.tblFunctions1.setFocus()
        if new_step == self.step_fc_functions2:
            self.step_fc_functions2.tblFunctions2.setFocus()
        # Re-connect disconnected signals when coming back to the Extent step
        if new_step == self.step_fc_extent:
            self.step_fc_extent.set_widgets()
        # Set Next button label
        self.pbnNext.setText(self.tr('Next'))
        self.pbnNext.setEnabled(True)
        self.go_to_step(new_step)

    def get_current_step(self):
        """Return current step of the wizard.

        :returns: Current step of the wizard.
        :rtype: WizardStep instance
        """
        return self.stackedWidget.currentWidget()

    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.step_kw_purpose.selected_purpose():
            keywords['layer_purpose'] = self.step_kw_purpose.\
                selected_purpose()['key']
            if keywords['layer_purpose'] == 'aggregation':
                keywords.update(
                    self.step_kw_aggregation.get_aggregation_attributes())
        if self.step_kw_subcategory.selected_subcategory():
            key = self.step_kw_purpose.selected_purpose()['key']
            keywords[key] = self.step_kw_subcategory.\
                selected_subcategory()['key']
        if self.step_kw_hazard_category.selected_hazard_category():
            keywords['hazard_category'] \
                = self.step_kw_hazard_category.\
                selected_hazard_category()['key']
        if self.step_kw_layermode.selected_layermode():
            keywords['layer_mode'] = self.step_kw_layermode.\
                selected_layermode()['key']
        if self.step_kw_unit.selected_unit():
            if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
                key = continuous_hazard_unit['key']
            else:
                key = exposure_unit['key']
            keywords[key] = self.step_kw_unit.selected_unit()['key']
        if self.step_kw_resample.selected_allowresampling() is not None:
            keywords['allow_resampling'] = (
                self.step_kw_resample.selected_allowresampling() and
                'true' or 'false')
        if self.step_kw_field.lstFields.currentItem():
            field_keyword = self.field_keyword_for_the_layer()
            keywords[field_keyword] = self.step_kw_field.\
                lstFields.currentItem().text()
        if self.step_kw_classification.selected_classification():
            geom = 'raster' if is_raster_layer(self.layer) else 'vector'
            key = '%s_%s_classification' % (
                geom, self.step_kw_purpose.selected_purpose()['key'])
            keywords[key] = self.step_kw_classification.\
                selected_classification()['key']
        value_map = self.step_kw_classify.selected_mapping()
        if value_map:
            if self.step_kw_classification.selected_classification():
                # hazard mapping
                keyword = 'value_map'
            else:
                # exposure mapping
                keyword = 'value_mapping'
            keywords[keyword] = json.dumps(value_map)

        name_field = self.step_kw_name_field.selected_field()
        if name_field:
            keywords['name_field'] = name_field

        population_field = self.step_kw_population_field.selected_field()
        if population_field:
            keywords['population_field'] = population_field

        extra_keywords = self.step_kw_extrakeywords.selected_extra_keywords()
        for key in extra_keywords:
            keywords[key] = extra_keywords[key]
        if self.step_kw_source.leSource.text():
            keywords['source'] = get_unicode(
                self.step_kw_source.leSource.text())
        if self.step_kw_source.leSource_url.text():
            keywords['url'] = get_unicode(
                self.step_kw_source.leSource_url.text())
        if self.step_kw_source.leSource_scale.text():
            keywords['scale'] = get_unicode(
                self.step_kw_source.leSource_scale.text())
        if self.step_kw_source.ckbSource_date.isChecked():
            keywords['date'] = self.step_kw_source.dtSource_date.dateTime()
        if self.step_kw_source.leSource_license.text():
            keywords['license'] = get_unicode(
                self.step_kw_source.leSource_license.text())
        if self.step_kw_title.leTitle.text():
            keywords['title'] = get_unicode(self.step_kw_title.leTitle.text())

        return keywords

    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()
Exemplo n.º 17
0
    def accept(self):
        """Handler for when OK is clicked."""
        input_path = self.input_path.text()
        input_title = self.line_edit_title.text()
        input_source = self.line_edit_source.text()
        output_path = self.output_path.text()
        if not output_path.endswith('.tif'):
            # noinspection PyArgumentList,PyCallByClass,PyTypeChecker
            QMessageBox.warning(
                self,
                self.tr('InaSAFE'),
                (self.tr('Output file name must be tif file')))
        if not os.path.exists(input_path):
            # noinspection PyArgumentList,PyCallByClass,PyTypeChecker
            QMessageBox.warning(
                self,
                self.tr('InaSAFE'),
                (self.tr('Input file does not exist')))
            return

        if self.nearest_mode.isChecked():
            algorithm = 'nearest'
        else:
            algorithm = 'invdist'

        QtGui.qApp.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))

        file_name = convert_mmi_data(
            input_path,
            input_title,
            input_source,
            output_path,
            algorithm=algorithm,
            algorithm_filename_flag=True)

        # reclassify raster
        file_info = QFileInfo(file_name)
        base_name = file_info.baseName()
        self.output_layer = QgsRasterLayer(file_name, base_name)
        self.output_layer.keywords = KeywordIO.read_keywords(self.output_layer)
        self.output_layer.keywords['classification'] = (
            earthquake_mmi_scale['key'])
        keywords = self.output_layer.keywords
        if self.output_layer.isValid():
            self.output_layer = reclassify(
                self.output_layer, overwrite_input=True)
            KeywordIO.write_keywords(self.output_layer, keywords)
        else:
            LOGGER.debug("Failed to load")

        QtGui.qApp.restoreOverrideCursor()

        if self.load_result.isChecked():
            # noinspection PyTypeChecker
            mmi_ramp_roman(self.output_layer)
            self.output_layer.saveDefaultStyle()
            if not self.output_layer.isValid():
                LOGGER.debug("Failed to load")
            else:
                # noinspection PyArgumentList
                QgsMapLayerRegistry.instance().addMapLayer(self.output_layer)
                iface.zoomToActiveLayer()

        if (self.keyword_wizard_checkbox.isChecked() and
                self.keyword_wizard_checkbox.isEnabled()):
            self.launch_keyword_wizard()

        self.done(self.Accepted)
Exemplo n.º 18
0
    def save_hazard_data(self):
        if self.dummy_report_folder:
            filename = os.path.join(self.working_dir, self.dummy_report_folder,
                                    'flood_data.json')
            hazard_geojson = DummySourceAPI.get_aggregate_report(filename)
        else:
            hazard_geojson = PetaJakartaAPI.get_aggregate_report(
                self.duration, self.level)

        if not hazard_geojson:
            raise PetaJakartaAPIError("Can't access PetaJakarta REST API")

        with open(self.hazard_path, 'w+') as f:
            f.write(hazard_geojson)

        # Save the layer as shp
        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(),
                                      'ogr', False)

        target_name = 'flood_data.shp'
        self.hazard_path = os.path.join(self.report_path, target_name)
        QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path,
                                                'CP1250', None,
                                                'ESRI Shapefile')

        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(),
                                      'ogr')

        # hazard_layer.startEditing()
        # field = QgsField('flooded', QVariant.Int)
        # hazard_layer.dataProvider().addAttributes([field])
        # hazard_layer.commitChanges()
        # idx = hazard_layer.fieldNameIndex('flooded')
        # expression = QgsExpression('count > 0')
        # expression.prepare(hazard_layer.pendingFields())
        #
        # hazard_layer.startEditing()
        # for feature in hazard_layer.getFeatures():
        #     feature[idx] = expression.evaluate(feature)
        #     hazard_layer.updateFeature(feature)
        #
        # hazard_layer.commitChanges()

        # writing keywords
        keyword_io = KeywordIO()

        keywords = {
            'field': 'state',
            'hazard': 'generic',
            'hazard_category': 'single_event',
            'keyword_version': '3.5',
            'layer_geometry': 'polygon',
            'layer_mode': 'classified',
            'layer_purpose': 'hazard',
            'title': 'Flood',
            'value_map': '{"high": [4], "medium": [3], '
            '"low": [2], '
            '"unaffected": ["None","","NULL",0,1]}',
            'vector_hazard_classification': 'generic_vector_hazard_classes'
        }

        keyword_io.write_keywords(hazard_layer, keywords)

        # copy layer styles
        style_path = self.flood_fixtures_dir('flood_data_classified_state.qml')
        target_style_path = os.path.join(self.report_path, 'flood_data.qml')
        shutil.copy(style_path, target_style_path)

        # archiving hazard layer
        with ZipFile(self.hazard_zip_path, 'w') as zf:
            for root, dirs, files in os.walk(self.report_path):
                for f in files:
                    _, ext = os.path.splitext(f)
                    if 'flood_data' in f:
                        filename = os.path.join(root, f)
                        zf.write(filename, arcname=f)
Exemplo n.º 19
0
class WizardDialog(QDialog, FORM_CLASS):
    """Dialog implementation class for the InaSAFE wizard."""

    resized = pyqtSignal()

    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 = tr(
            'InaSAFE Keywords Creation Wizard')
        self.ifcw_name = tr('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.lblStep.clear()
        # Set icons
        self.lblMainIcon.setPixmap(
            QPixmap(resources_path('img', 'icons', 'icon-white.svg')))

        self.keyword_io = KeywordIO()

        self.is_selected_layer_keywordless = False
        self.parent_step = None

        self.pbnBack.setEnabled(False)
        self.pbnNext.setEnabled(False)
        self.pbnCancel.released.connect(self.reject)

        # Initialize attributes
        self.existing_keywords = None
        self.layer = None
        self.hazard_layer = None
        self.exposure_layer = None
        self.aggregation_layer = None

        self.step_kw_purpose = StepKwPurpose(self)
        self.step_kw_subcategory = StepKwSubcategory(self)
        self.step_kw_hazard_category = StepKwHazardCategory(self)
        self.step_kw_band_selector = StepKwBandSelector(self)
        self.step_kw_layermode = StepKwLayerMode(self)
        self.step_kw_unit = StepKwUnit(self)
        self.step_kw_classification = StepKwClassification(self)
        self.step_kw_field = StepKwField(self)
        self.step_kw_multi_classifications = StepKwMultiClassifications(self)
        self.step_kw_classify = StepKwClassify(self)
        self.step_kw_threshold = StepKwThreshold(self)
        self.step_kw_fields_mapping = StepKwFieldsMapping(self)
        self.step_kw_inasafe_fields = StepKwInaSAFEFields(self)
        self.step_kw_default_inasafe_fields = StepKwDefaultInaSAFEFields(self)
        self.step_kw_inasafe_raster_default_values = \
            StepKwInaSAFERasterDefaultValues(self)
        self.step_kw_source = StepKwSource(self)
        self.step_kw_title = StepKwTitle(self)
        self.step_kw_summary = StepKwSummary(self)

        self.step_fc_functions1 = StepFcFunctions1(self)
        self.step_fc_functions2 = StepFcFunctions2(self)
        self.step_fc_hazlayer_origin = StepFcHazLayerOrigin(self)
        self.step_fc_hazlayer_from_canvas = StepFcHazLayerFromCanvas(self)
        self.step_fc_hazlayer_from_browser = StepFcHazLayerFromBrowser(self)
        self.step_fc_explayer_origin = StepFcExpLayerOrigin(self)
        self.step_fc_explayer_from_canvas = StepFcExpLayerFromCanvas(self)
        self.step_fc_explayer_from_browser = StepFcExpLayerFromBrowser(self)
        self.step_fc_disjoint_layers = StepFcDisjointLayers(self)
        self.step_fc_agglayer_origin = StepFcAggLayerOrigin(self)
        self.step_fc_agglayer_from_canvas = StepFcAggLayerFromCanvas(self)
        self.step_fc_agglayer_from_browser = StepFcAggLayerFromBrowser(self)
        self.step_fc_agglayer_disjoint = StepFcAggLayerDisjoint(self)
        self.step_fc_extent = StepFcExtent(self)
        self.step_fc_extent_disjoint = StepFcExtentDisjoint(self)
        self.step_fc_summary = StepFcSummary(self)
        self.step_fc_analysis = StepFcAnalysis(self)

        self.wizard_help = WizardHelp(self)

        self.stackedWidget.addWidget(self.step_kw_purpose)
        self.stackedWidget.addWidget(self.step_kw_subcategory)
        self.stackedWidget.addWidget(self.step_kw_hazard_category)
        self.stackedWidget.addWidget(self.step_kw_band_selector)
        self.stackedWidget.addWidget(self.step_kw_layermode)
        self.stackedWidget.addWidget(self.step_kw_unit)
        self.stackedWidget.addWidget(self.step_kw_classification)
        self.stackedWidget.addWidget(self.step_kw_field)
        self.stackedWidget.addWidget(self.step_kw_multi_classifications)
        self.stackedWidget.addWidget(self.step_kw_classify)
        self.stackedWidget.addWidget(self.step_kw_threshold)
        self.stackedWidget.addWidget(self.step_kw_fields_mapping)
        self.stackedWidget.addWidget(self.step_kw_inasafe_fields)
        self.stackedWidget.addWidget(self.step_kw_default_inasafe_fields)
        self.stackedWidget.addWidget(
            self.step_kw_inasafe_raster_default_values)
        self.stackedWidget.addWidget(self.step_kw_source)
        self.stackedWidget.addWidget(self.step_kw_title)
        self.stackedWidget.addWidget(self.step_kw_summary)
        self.stackedWidget.addWidget(self.step_fc_functions1)
        self.stackedWidget.addWidget(self.step_fc_functions2)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_origin)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_hazlayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_explayer_origin)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_explayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_disjoint_layers)
        self.stackedWidget.addWidget(self.step_fc_agglayer_origin)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_canvas)
        self.stackedWidget.addWidget(self.step_fc_agglayer_from_browser)
        self.stackedWidget.addWidget(self.step_fc_agglayer_disjoint)
        self.stackedWidget.addWidget(self.step_fc_extent)
        self.stackedWidget.addWidget(self.step_fc_extent_disjoint)
        self.stackedWidget.addWidget(self.step_fc_summary)
        self.stackedWidget.addWidget(self.step_fc_analysis)

        self.stackedWidget.addWidget(self.wizard_help)

        # QSetting
        self.setting = QSettings()

        # Wizard Steps
        self.impact_function_steps = []
        self.keyword_steps = []
        self.on_help = False

        self.resized.connect(self.after_resize)

    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 = tr(
                'Keywords update wizard for layer <b>{layer_name}</b>').format(
                    layer_name=self.layer.name())
        else:
            mode_name = tr(
                'Keywords creation wizard for layer <b>{layer_name}</b>'
            ).format(layer_name=self.layer.name())
        self.lblSubtitle.setText(mode_name)

    def set_mode_label_to_ifcw(self):
        """Set the mode label to the IFCW."""
        self.setWindowTitle(self.ifcw_name)
        self.lblSubtitle.setText(
            tr('Use this wizard to run a guided impact assessment'))

    def set_keywords_creation_mode(self, layer=None, keywords=None):
        """Set the Wizard to the Keywords Creation mode.

        :param layer: Layer to set the keywords for
        :type layer: QgsMapLayer

        :param keywords: Keywords for the layer.
        :type keywords: dict, None
        """
        self.layer = layer or self.iface.mapCanvas().currentLayer()

        if keywords is not None:
            self.existing_keywords = keywords
        else:
            # Always read from metadata file.
            try:
                self.existing_keywords = self.keyword_io.read_keywords(
                    self.layer)
            except (HashNotFoundError, OperationalError, NoKeywordsFoundError,
                    KeywordNotFoundError, InvalidParameterError,
                    UnsupportedProviderError, MetadataReadError):
                self.existing_keywords = None
        self.set_mode_label_to_keywords_creation()

        step = self.step_kw_purpose
        step.set_widgets()
        self.go_to_step(step)

    def set_function_centric_mode(self):
        """Set the Wizard to the Function Centric mode."""
        self.set_mode_label_to_ifcw()

        step = self.step_fc_functions1
        step.set_widgets()
        self.go_to_step(step)

    def field_keyword_for_the_layer(self):
        """Return the proper keyword for field for the current layer.

        :returns: the field keyword
        :rtype: str
        """
        layer_purpose_key = self.step_kw_purpose.selected_purpose()['key']
        if layer_purpose_key == layer_purpose_aggregation['key']:
            return get_compulsory_fields(layer_purpose_key)['key']
        elif layer_purpose_key in [
                layer_purpose_exposure['key'], layer_purpose_hazard['key']
        ]:
            layer_subcategory_key = \
                self.step_kw_subcategory.selected_subcategory()['key']
            return get_compulsory_fields(layer_purpose_key,
                                         layer_subcategory_key)['key']
        else:
            raise InvalidParameterError

    def get_parent_mode_constraints(self):
        """Return the category and subcategory keys to be set in the
        subordinate mode.

        :returns: (the category definition, the hazard/exposure definition)
        :rtype: (dict, dict)
        """
        h, e, _hc, _ec = self.selected_impact_function_constraints()
        if self.parent_step in [
                self.step_fc_hazlayer_from_canvas,
                self.step_fc_hazlayer_from_browser
        ]:
            category = layer_purpose_hazard
            subcategory = h
        elif self.parent_step in [
                self.step_fc_explayer_from_canvas,
                self.step_fc_explayer_from_browser
        ]:
            category = layer_purpose_exposure
            subcategory = e
        elif self.parent_step:
            category = layer_purpose_aggregation
            subcategory = None
        else:
            category = None
            subcategory = None
        return category, subcategory

    def selected_impact_function_constraints(self):
        """Obtain impact function constraints selected by user.

        :returns: Tuple of metadata of hazard, exposure,
            hazard layer geometry and exposure layer geometry
        :rtype: tuple
        """

        hazard = self.step_fc_functions1.selected_value(
            layer_purpose_hazard['key'])
        exposure = self.step_fc_functions1.selected_value(
            layer_purpose_exposure['key'])

        hazard_geometry = self.step_fc_functions2.selected_value(
            layer_purpose_hazard['key'])
        exposure_geometry = self.step_fc_functions2.selected_value(
            layer_purpose_exposure['key'])
        return hazard, exposure, hazard_geometry, exposure_geometry

    def is_layer_compatible(self, layer, layer_purpose=None, 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: None, string

        :param keywords: The layer keywords
        :type keywords: None, dict

        :returns: True if layer is appropriate for the selected role
        :rtype: boolean
        """

        # If not explicitly stated, find the desired purpose
        # from the parent step
        if not layer_purpose:
            layer_purpose = self.get_parent_mode_constraints()[0]['key']

        # If not explicitly stated, read the layer's keywords
        if not keywords:
            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

        # 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_key(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 not is_keyword_version_supported(keyword_version):
            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

        return True

    def get_compatible_canvas_layers(self, category):
        """Collect layers from map canvas, compatible for the given category
           and selected impact function

        .. 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:
                    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

    def get_layer_geometry_key(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 layer_geometry_raster['key']
        elif is_point_layer(layer):
            return layer_geometry_point['key']
        elif is_polygon_layer(layer):
            return layer_geometry_polygon['key']
        else:
            return layer_geometry_line['key']

    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, QUrl
        """
        if self.existing_keywords is None:
            return {}
        if keyword is not None:
            return self.existing_keywords.get(keyword, {})
        else:
            return {}

    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 purpose: The layer purpose of the layer to get the description.
        :type purpose: 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 == layer_purpose_hazard['key']:
            self.hazard_layer = layer
        elif purpose == layer_purpose_exposure['key']:
            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 = (
                not is_keyword_version_supported(kw_ver))
        else:
            self.is_selected_layer_keywordless = True

        description = layer_description_html(layer, keywords)
        return description

    # ===========================
    # NAVIGATION
    # ===========================

    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 widget to be moved to.
        :type step: WizardStep
        """
        self.stackedWidget.setCurrentWidget(step)

        # Disable the Next button unless new data already entered
        self.pbnNext.setEnabled(step.is_ready_to_next_step())

        # Enable the Back button unless it's not the first step
        self.pbnBack.setEnabled(
            step not in [self.step_kw_purpose, self.step_fc_functions1]
            or self.parent_step is not None)

        # Set Next button label
        if (step in [self.step_kw_summary, self.step_fc_analysis]
                and self.parent_step is None):
            self.pbnNext.setText(tr('Finish'))
        elif step == self.step_fc_summary:
            self.pbnNext.setText(tr('Run'))
        else:
            self.pbnNext.setText(tr('Next'))

        # Run analysis after switching to the new step
        if step == self.step_fc_analysis:
            self.step_fc_analysis.setup_and_run_analysis()

        # Set lblSelectCategory label if entering the kw mode
        # from the ifcw mode
        if step == self.step_kw_purpose and self.parent_step:
            if self.parent_step in [
                    self.step_fc_hazlayer_from_canvas,
                    self.step_fc_hazlayer_from_browser
            ]:
                text_label = category_question_hazard
            elif self.parent_step in [
                    self.step_fc_explayer_from_canvas,
                    self.step_fc_explayer_from_browser
            ]:
                text_label = category_question_exposure
            else:
                text_label = category_question_aggregation
            self.step_kw_purpose.lblSelectCategory.setText(text_label)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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()

        if current_step == self.step_kw_fields_mapping:
            try:
                self.step_kw_fields_mapping.get_field_mapping()
            except InvalidValidationException as e:
                display_warning_message_box(self, tr('Invalid Field Mapping'),
                                            get_string(e.message))
                return

        if current_step.step_type == STEP_FC:
            self.impact_function_steps.append(current_step)
        elif current_step.step_type == STEP_KW:
            self.keyword_steps.append(current_step)
        else:
            LOGGER.debug(current_step.step_type)
            raise InvalidWizardStep

        # Save keywords if it's the end of the keyword creation mode
        if current_step == self.step_kw_summary:
            self.save_current_keywords()

        # After any step involving Browser, add selected layer to map canvas
        if current_step in [
                self.step_fc_hazlayer_from_browser,
                self.step_fc_explayer_from_browser,
                self.step_fc_agglayer_from_browser
        ]:
            if not QgsMapLayerRegistry.instance().mapLayersByName(
                    self.layer.name()):
                QgsMapLayerRegistry.instance().addMapLayers([self.layer])

                # Make the layer visible. Might be hidden by default. See #2925
                legend = self.iface.legendInterface()
                legend.setLayerVisible(self.layer, True)

        # After the extent selection, save the extent and disconnect signals
        if current_step == self.step_fc_extent:
            self.step_fc_extent.write_extent()

        # Determine the new step to be switched
        new_step = current_step.get_next_step()

        if new_step is not None:
            # Prepare the next tab
            new_step.set_widgets()
        else:
            # Wizard complete
            self.accept()
            return

        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    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()
        if current_step.step_type == STEP_FC:
            new_step = self.impact_function_steps.pop()
        elif current_step.step_type == STEP_KW:
            try:
                new_step = self.keyword_steps.pop()
            except IndexError:
                new_step = self.impact_function_steps.pop()
        else:
            raise InvalidWizardStep

        # set focus to table widgets, as the inactive selection style is gray
        if new_step == self.step_fc_functions1:
            self.step_fc_functions1.tblFunctions1.setFocus()
        if new_step == self.step_fc_functions2:
            self.step_fc_functions2.tblFunctions2.setFocus()
        # Re-connect disconnected signals when coming back to the Extent step
        if new_step == self.step_fc_extent:
            self.step_fc_extent.set_widgets()
        # Set Next button label
        self.pbnNext.setText(tr('Next'))
        self.pbnNext.setEnabled(True)
        self.go_to_step(new_step)

    # prevents actions being handled twice
    # noinspection PyPep8Naming
    @pyqtSignature('')
    def on_pbnHelp_released(self):
        if self.on_help:
            self.pbnHelp.setText(tr('Show help'))
            self.wizard_help.restore_button_state()
            self.stackedWidget.setCurrentWidget(self.wizard_help.wizard_step)
        else:
            self.pbnHelp.setText(tr('Hide help'))
            self.wizard_help.show_help(self.get_current_step())
            self.stackedWidget.setCurrentWidget(self.wizard_help)

        self.on_help = not self.on_help

    def get_current_step(self):
        """Return current step of the wizard.

        :returns: Current step of the wizard.
        :rtype: WizardStep instance
        """
        return self.stackedWidget.currentWidget()

    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 = {}
        inasafe_fields = {}
        keywords['layer_geometry'] = self.get_layer_geometry_key()
        if self.step_kw_purpose.selected_purpose():
            keywords['layer_purpose'] = self.step_kw_purpose.\
                selected_purpose()['key']
        if self.step_kw_subcategory.selected_subcategory():
            key = self.step_kw_purpose.selected_purpose()['key']
            keywords[key] = self.step_kw_subcategory.\
                selected_subcategory()['key']
        if self.get_layer_geometry_key() == layer_geometry_raster['key']:
            if self.step_kw_band_selector.selected_band():
                keywords['active_band'] = self.step_kw_band_selector.\
                    selected_band()
        if keywords['layer_purpose'] == layer_purpose_hazard['key']:
            if self.step_kw_hazard_category.selected_hazard_category():
                keywords['hazard_category'] \
                    = self.step_kw_hazard_category.\
                    selected_hazard_category()['key']
        if self.step_kw_layermode.selected_layermode():
            keywords['layer_mode'] = self.step_kw_layermode.\
                selected_layermode()['key']
        if self.step_kw_unit.selected_unit():
            if self.step_kw_purpose.selected_purpose() == layer_purpose_hazard:
                key = continuous_hazard_unit['key']
            else:
                key = exposure_unit['key']
            keywords[key] = self.step_kw_unit.selected_unit()['key']
        if self.step_kw_field.selected_fields():
            field_key = self.field_keyword_for_the_layer()
            inasafe_fields[field_key] = self.step_kw_field.selected_fields()
        if self.step_kw_classification.selected_classification():
            keywords['classification'] = self.step_kw_classification.\
                selected_classification()['key']

        if keywords['layer_purpose'] == layer_purpose_hazard['key']:
            multi_classifications = self.step_kw_multi_classifications.\
                get_current_state()
            value_maps = multi_classifications.get('value_maps')
            if value_maps is not None:
                keywords['value_maps'] = value_maps
            thresholds = multi_classifications.get('thresholds')
            if thresholds is not None:
                keywords['thresholds'] = thresholds
        else:
            if self.step_kw_layermode.selected_layermode():
                layer_mode = self.step_kw_layermode.selected_layermode()
                if layer_mode == layer_mode_continuous:
                    thresholds = self.step_kw_threshold.get_threshold()
                    if thresholds:
                        keywords['thresholds'] = thresholds
                elif layer_mode == layer_mode_classified:
                    value_map = self.step_kw_classify.selected_mapping()
                    if value_map:
                        keywords['value_map'] = value_map

        if self.step_kw_source.leSource.text():
            keywords['source'] = get_unicode(
                self.step_kw_source.leSource.text())
        if self.step_kw_source.leSource_url.text():
            keywords['url'] = get_unicode(
                self.step_kw_source.leSource_url.text())
        if self.step_kw_source.leSource_scale.text():
            keywords['scale'] = get_unicode(
                self.step_kw_source.leSource_scale.text())
        if self.step_kw_source.ckbSource_date.isChecked():
            keywords['date'] = self.step_kw_source.dtSource_date.dateTime()
        if self.step_kw_source.leSource_license.text():
            keywords['license'] = get_unicode(
                self.step_kw_source.leSource_license.text())
        if self.step_kw_title.leTitle.text():
            keywords['title'] = get_unicode(self.step_kw_title.leTitle.text())

        inasafe_fields.update(self.step_kw_inasafe_fields.get_inasafe_fields())
        inasafe_fields.update(
            self.step_kw_default_inasafe_fields.get_inasafe_fields())
        inasafe_fields.update(
            self.step_kw_fields_mapping.get_field_mapping()['fields'])

        if inasafe_fields:
            keywords['inasafe_fields'] = inasafe_fields

        inasafe_default_values = {}
        if keywords['layer_geometry'] == layer_geometry_raster['key']:
            pass
            # Notes(IS): Skipped assigning raster inasafe default value for
            # now.
            # inasafe_default_values = self.\
            #     step_kw_inasafe_raster_default_values.\
            #     get_inasafe_default_values()
        else:
            inasafe_default_values.update(self.step_kw_default_inasafe_fields.
                                          get_inasafe_default_values())
            inasafe_default_values.update(
                self.step_kw_fields_mapping.get_field_mapping()['values'])

        if inasafe_default_values:
            keywords['inasafe_default_values'] = inasafe_default_values

        return keywords

    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, tr('InaSAFE'),
                tr('An error was encountered when saving the following '
                   'keywords:\n {error_message}').format(
                       error_message=error_message.to_html()))
        if self.dock is not None:
            # noinspection PyUnresolvedReferences
            self.dock.get_layers()

        # Save default value to QSetting
        if current_keywords.get('inasafe_default_values'):
            for key, value in (
                    current_keywords['inasafe_default_values'].items()):
                set_inasafe_default_value_qsetting(self.setting, RECENT, key,
                                                   value)
Exemplo n.º 20
0
    def save_hazard_data(self):
        hazard_geojson = PetaJakartaAPI.get_aggregate_report(
            self.duration, self.level)

        if not hazard_geojson:
            raise PetaJakartaAPIError("Can't access PetaJakarta REST API")

        with open(self.hazard_path, 'w+') as f:
            f.write(hazard_geojson)

        # Save the layer as shp
        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(),
                                      'ogr', False)

        target_name = 'flood_data.shp'
        self.hazard_path = os.path.join(self.report_path, target_name)
        QgsVectorFileWriter.writeAsVectorFormat(hazard_layer, self.hazard_path,
                                                'CP1250', None,
                                                'ESRI Shapefile')

        file_info = QFileInfo(self.hazard_path)
        hazard_layer = QgsVectorLayer(self.hazard_path, file_info.baseName(),
                                      'ogr')

        hazard_layer.startEditing()
        field = QgsField('flooded', QVariant.Int)
        hazard_layer.dataProvider().addAttributes([field])
        hazard_layer.commitChanges()
        idx = hazard_layer.fieldNameIndex('flooded')
        expression = QgsExpression('count > 0')
        expression.prepare(hazard_layer.pendingFields())

        hazard_layer.startEditing()
        for feature in hazard_layer.getFeatures():
            feature[idx] = expression.evaluate(feature)
            hazard_layer.updateFeature(feature)

        hazard_layer.commitChanges()

        # writing keywords
        keyword_io = KeywordIO()

        keywords = {
            'field': 'flooded',
            'hazard': 'flood',
            'hazard_category': 'single_event',
            'keyword_version': '3.3',
            'layer_geometry': 'polygon',
            'layer_mode': 'classified',
            'layer_purpose': 'hazard',
            'title': 'Flood',
            'value_map': '{"wet": [1], "dry": [0]}',
            'vector_hazard_classification': 'flood_vector_hazard_classes'
        }

        keyword_io.write_keywords(hazard_layer, keywords)

        # archiving hazard layer
        with ZipFile(self.hazard_zip_path, 'w') as zf:
            for root, dirs, files in os.walk(self.report_path):
                for f in files:
                    _, ext = os.path.splitext(f)
                    if 'flood_data' in f:
                        filename = os.path.join(root, f)
                        zf.write(filename, arcname=f)