def test_is_polygonal_layer(self): """Test we can get the correct attributes back""" # Polygon layer layer = load_test_vector_layer( 'aggregation', 'district_osm_jakarta.geojson', clone=True ) message = 'isPolygonLayer, %s layer should be polygonal' % layer self.assertTrue(is_polygon_layer(layer), message) # Point layer layer = load_test_vector_layer('hazard', 'volcano_point.geojson') message = '%s layer should be polygonal' % layer self.assertFalse(is_polygon_layer(layer), message) # Raster layer layer = clone_raster_layer( name='earthquake', extension='.tif', include_keywords=True, source_directory=standard_data_path('hazard') ) message = ('%s raster layer should not be polygonal' % layer) self.assertFalse(is_polygon_layer(layer), message)
def test_is_polygonal_layer(self): """Test we can get the correct attributes back""" # Polygon layer layer = clone_shp_layer( name='district_osm_jakarta', include_keywords=True, source_directory=test_data_path('boundaries')) message = 'isPolygonLayer, %s layer should be polygonal' % layer self.assertTrue(is_polygon_layer(layer), message) # Point layer layer = clone_shp_layer( name='volcano_point', include_keywords=True, source_directory=test_data_path('hazard')) message = '%s layer should be polygonal' % layer self.assertFalse(is_polygon_layer(layer), message) layer = clone_raster_layer( name='padang_tsunami_mw8', extension='.tif', include_keywords=True, source_directory=test_data_path('hazard') ) message = ('%s raster layer should not be polygonal' % layer) self.assertFalse(is_polygon_layer(layer), message)
def polygon_layers_to_combo(self): """Populate the combo with all polygon layers loaded in QGIS.""" # noinspection PyArgumentList registry = QgsMapLayerRegistry.instance() layers = registry.mapLayers().values() found_flag = False for layer in layers: name = layer.name() source = layer.id() # check if layer is a vector polygon layer if is_polygon_layer(layer) or is_point_layer(layer): found_flag = True add_ordered_combo_item(self.cboPolygonLayers, name, source) if found_flag: self.cboPolygonLayers.setCurrentIndex(0)
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 geometry_type(layer): """Retrieve the geometry type: point, line, polygon or raster for a layer. :param layer: The layer. :type layer: QgsMapLayer :return: The definition key. :rtype: basestring """ if is_raster_layer(layer): return layer_geometry_raster['key'] elif is_point_layer(layer): return layer_geometry_point['key'] elif is_line_layer(layer): return layer_geometry_line['key'] elif is_polygon_layer(layer): return layer_geometry_polygon['key'] else: return None
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 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_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 polygon_layers_to_combo(self): """Populate the combo with all polygon layers loaded in QGIS.""" # noinspection PyArgumentList registry = QgsMapLayerRegistry.instance() layers = registry.mapLayers().values() found_flag = False for layer in layers: name = layer.name() source = layer.id() # check if layer is a vector polygon layer if is_polygon_layer(layer) or is_point_layer(layer): found_flag = True add_ordered_combo_item(self.cboPolygonLayers, name, source) # Now disable the run button if no suitable layers were found # see #2206 ok_button = self.button_box.button(QtGui.QDialogButtonBox.Ok) if found_flag: self.cboPolygonLayers.setCurrentIndex(0) ok_button.setEnabled(True) else: ok_button.setEnabled(False)
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 layer_description_html(layer, keywords=None): """Form a html description of a given layer based on the layer parameters and keywords if provided :param layer: The layer to get the description :type layer: QgsMapLayer :param keywords: The layer keywords :type keywords: None, dict :returns: The html description in tabular format, ready to use in a label or tool tip. :rtype: str """ if keywords and 'keyword_version' in keywords: keyword_version = str(keywords['keyword_version']) else: keyword_version = None if (keywords and keyword_version and is_keyword_version_supported(keyword_version)): # The layer has valid keywords purpose = keywords.get('layer_purpose') if purpose == layer_purpose_hazard['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Hazard'), keywords.get(purpose)) unit = keywords.get('continuous_hazard_unit') elif purpose == layer_purpose_exposure['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Exposure'), keywords.get(purpose)) unit = keywords.get('exposure_unit') else: subcategory = '' unit = None if keywords.get('layer_mode') == layer_mode_classified['key']: unit = tr('classified data') if unit: unit = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % (tr('Unit'), unit) description = """ <table border="0" width="100%%"> <tr><td><b>%s</b>: </td><td>%s</td></tr> <tr><td><b>%s</b>: </td><td>%s</td></tr> %s %s <tr><td><b>%s</b>: </td><td>%s</td></tr> </table> """ % (tr('Title'), keywords.get('title'), tr('Purpose'), keywords.get('layer_purpose'), subcategory, unit, tr('Source'), keywords.get('source')) elif keywords: # The layer has keywords, but the version is wrong layer_version = keyword_version or tr('No Version') description = tr( 'Your layer\'s keyword\'s version ({layer_version}) does not ' 'match with your InaSAFE version ({inasafe_version}). If you wish ' 'to use it as an exposure, hazard, or aggregation layer in an ' 'analysis, please update the keywords. Click Next if you want to ' 'assign keywords now.').format(layer_version=layer_version, inasafe_version=get_version()) else: # The layer is keywordless if is_point_layer(layer): geom_type = layer_geometry_point['key'] elif is_polygon_layer(layer): geom_type = layer_geometry_polygon['key'] else: geom_type = layer_geometry_line['key'] # hide password in the layer source source = layer.publicSource() description = """ %s<br/><br/> <b>%s</b>: %s<br/> <b>%s</b>: %s<br/><br/> %s """ % (tr('This layer has no valid keywords assigned'), tr('SOURCE'), source, tr('TYPE'), is_raster_layer(layer) and 'raster' or 'vector (%s)' % geom_type, tr('In the next step you will be able' + ' to assign keywords to this layer.')) return description
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 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 layer_description_html(layer, keywords=None): """Form a html description of a given layer based on the layer parameters and keywords if provided :param layer: The layer to get the description :type layer: QgsMapLayer :param keywords: The layer keywords :type keywords: None, dict :returns: The html description in tabular format, ready to use in a label or tool tip. :rtype: str """ if keywords and 'keyword_version' in keywords: keyword_version = str(keywords['keyword_version']) else: keyword_version = None if (keywords and keyword_version and is_keyword_version_supported(keyword_version)): # The layer has valid keywords purpose = keywords.get('layer_purpose') if purpose == layer_purpose_hazard['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Hazard'), keywords.get(purpose)) unit = keywords.get('continuous_hazard_unit') elif purpose == layer_purpose_exposure['key']: subcategory = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Exposure'), keywords.get(purpose)) unit = keywords.get('exposure_unit') else: subcategory = '' unit = None if keywords.get('layer_mode') == layer_mode_classified['key']: unit = tr('classified data') if unit: unit = '<tr><td><b>%s</b>: </td><td>%s</td></tr>' % ( tr('Unit'), unit) desc = """ <table border="0" width="100%%"> <tr><td><b>%s</b>: </td><td>%s</td></tr> <tr><td><b>%s</b>: </td><td>%s</td></tr> %s %s <tr><td><b>%s</b>: </td><td>%s</td></tr> </table> """ % (tr('Title'), keywords.get('title'), tr('Purpose'), keywords.get('layer_purpose'), subcategory, unit, tr('Source'), keywords.get('source')) elif keywords: # The layer has keywords, but the version is wrong layer_version = keyword_version or tr('No Version') desc = tr( 'Your layer\'s keyword\'s version ({layer_version}) does not ' 'match with your InaSAFE version ({inasafe_version}). If you wish ' 'to use it as an exposure, hazard, or aggregation layer in an ' 'analysis, please update the keywords. Click Next if you want to ' 'assign keywords now.').format( layer_version=layer_version, inasafe_version=get_version()) else: # The layer is keywordless if is_point_layer(layer): geom_type = 'point' elif is_polygon_layer(layer): geom_type = 'polygon' else: geom_type = 'line' # hide password in the layer source source = layer.publicSource() desc = """ %s<br/><br/> <b>%s</b>: %s<br/> <b>%s</b>: %s<br/><br/> %s """ % (tr('This layer has no valid keywords assigned'), tr('SOURCE'), source, tr('TYPE'), is_raster_layer(layer) and 'raster' or 'vector (%s)' % geom_type, tr('In the next step you will be able' + ' to assign keywords to this layer.')) return desc
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 calculate_zonal_stats(raster_layer, polygon_layer): """Calculate zonal statics given two layers. :param raster_layer: A QGIS raster layer. :type raster_layer: QgsRasterLayer, QgsMapLayer :param polygon_layer: A QGIS vector layer containing polygons. :type polygon_layer: QgsVectorLayer, QgsMapLayer :returns: A data structure containing sum, mean, min, max, count of raster values for each polygonal area. :rtype: dict :raises: InvalidParameterError, InvalidGeometryError Note: * InvalidParameterError if incorrect inputs are received. * InvalidGeometryError if none geometry is found during calculations. * Any other exceptions are propagated. Example of output data structure: { 1: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 2: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 3 {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}} The key in the outer dict is the feature id .. note:: This is a python port of the zonal stats implementation in QGIS . See https://github.com/qgis/Quantum-GIS/blob/master/src/analysis/ vector/qgszonalstatistics.cpp .. note:: Currently not projection checks are made to ensure that both layers are in the same CRS - we assume they are. """ if not is_polygon_layer(polygon_layer): raise InvalidParameterError( tr('Zonal stats needs a polygon layer in order to compute ' 'statistics.')) if not is_raster_layer(raster_layer): raise InvalidParameterError( tr('Zonal stats needs a raster layer in order to compute statistics.' )) LOGGER.debug('Calculating zonal stats for:') LOGGER.debug('Raster: %s' % raster_layer.source()) LOGGER.debug('Vector: %s' % polygon_layer.source()) results = {} raster_source = raster_layer.source() feature_id = gdal.Open(str(raster_source), gdal.GA_ReadOnly) geo_transform = feature_id.GetGeoTransform() columns = feature_id.RasterXSize rows = feature_id.RasterYSize # Get first band. band = feature_id.GetRasterBand(1) no_data = band.GetNoDataValue() # print 'No data %s' % no_data cell_size_x = geo_transform[1] if cell_size_x < 0: cell_size_x = -cell_size_x cell_size_y = geo_transform[5] if cell_size_y < 0: cell_size_y = -cell_size_y raster_box = QgsRectangle(geo_transform[0], geo_transform[3] - (cell_size_y * rows), geo_transform[0] + (cell_size_x * columns), geo_transform[3]) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList raster_geometry = QgsGeometry.fromRect(raster_box) # Get vector layer provider = polygon_layer.dataProvider() if provider is None: message = tr('Could not obtain data provider from layer "%s"') % ( polygon_layer.source()) raise Exception(message) request = QgsFeatureRequest() crs = osr.SpatialReference() crs.ImportFromProj4(str(polygon_layer.crs().toProj4())) count = 0 for myFeature in provider.getFeatures(request): geometry = myFeature.geometry() if geometry is None: message = tr('Feature %d has no geometry or geometry is invalid' ) % (myFeature.id()) raise InvalidGeometryError(message) count += 1 feature_box = geometry.boundingBox().intersect(raster_box) print 'NEW AGGR: %s' % myFeature.id() # print 'Raster Box: %s' % raster_box.asWktCoordinates() # print 'Feature Box: %s' % feature_box.asWktCoordinates() offset_x, offset_y, cells_x, cells_y = intersection_box( raster_box, feature_box, cell_size_x, cell_size_y) # If the poly does not intersect the raster just continue if None in [offset_x, offset_y, cells_x, cells_y]: continue # avoid access to cells outside of the raster (may occur because of # rounding) if (offset_x + cells_x) > columns: offset_x = columns - offset_x if (offset_y + cells_y) > rows: cells_y = rows - offset_y intersected_geometry = raster_geometry.intersection(geometry) geometry_sum, count = numpy_stats(band, intersected_geometry, geo_transform, no_data, crs) if count <= 1: # The cell resolution is probably larger than the polygon area. # We switch to precise pixel - polygon intersection in this case geometry_sum, count = precise_stats(band, geometry, offset_x, offset_y, cells_x, cells_y, cell_size_x, cell_size_y, raster_box, no_data) # print geometry_sum, count if count == 0: mean = 0 else: mean = geometry_sum / count results[myFeature.id()] = { 'sum': geometry_sum, 'count': count, 'mean': mean } # noinspection PyUnusedLocal feature_id = None # Close return results
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 __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 calculate_zonal_stats(raster_layer, polygon_layer): """Calculate zonal statics given two layers. :param raster_layer: A QGIS raster layer. :type raster_layer: QgsRasterLayer, QgsMapLayer :param polygon_layer: A QGIS vector layer containing polygons. :type polygon_layer: QgsVectorLayer, QgsMapLayer :returns: A data structure containing sum, mean, min, max, count of raster values for each polygonal area. :rtype: dict :raises: InvalidParameterError, InvalidGeometryError Note: * InvalidParameterError if incorrect inputs are received. * InvalidGeometryError if none geometry is found during calculations. * Any other exceptions are propagated. Example of output data structure: { 1: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 2: {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}, 3 {'sum': 10, 'count': 20, 'min': 1, 'max': 4, 'mean': 2}} The key in the outer dict is the feature id .. note:: This is a python port of the zonal stats implementation in QGIS . See https://github.com/qgis/Quantum-GIS/blob/master/src/analysis/ vector/qgszonalstatistics.cpp .. note:: Currently not projection checks are made to ensure that both layers are in the same CRS - we assume they are. """ if not is_polygon_layer(polygon_layer): raise InvalidParameterError(tr( 'Zonal stats needs a polygon layer in order to compute ' 'statistics.')) if not is_raster_layer(raster_layer): raise InvalidParameterError(tr( 'Zonal stats needs a raster layer in order to compute statistics.' )) LOGGER.debug('Calculating zonal stats for:') LOGGER.debug('Raster: %s' % raster_layer.source()) LOGGER.debug('Vector: %s' % polygon_layer.source()) results = {} raster_source = raster_layer.source() feature_id = gdal.Open(str(raster_source), gdal.GA_ReadOnly) geo_transform = feature_id.GetGeoTransform() columns = feature_id.RasterXSize rows = feature_id.RasterYSize # Get first band. band = feature_id.GetRasterBand(1) no_data = band.GetNoDataValue() # print 'No data %s' % no_data cell_size_x = geo_transform[1] if cell_size_x < 0: cell_size_x = -cell_size_x cell_size_y = geo_transform[5] if cell_size_y < 0: cell_size_y = -cell_size_y raster_box = QgsRectangle( geo_transform[0], geo_transform[3] - (cell_size_y * rows), geo_transform[0] + (cell_size_x * columns), geo_transform[3]) # noinspection PyCallByClass,PyTypeChecker,PyArgumentList raster_geometry = QgsGeometry.fromRect(raster_box) # Get vector layer provider = polygon_layer.dataProvider() if provider is None: message = tr( 'Could not obtain data provider from layer "%s"') % ( polygon_layer.source()) raise Exception(message) request = QgsFeatureRequest() crs = osr.SpatialReference() crs.ImportFromProj4(str(polygon_layer.crs().toProj4())) count = 0 for myFeature in provider.getFeatures(request): geometry = myFeature.geometry() if geometry is None: message = tr( 'Feature %d has no geometry or geometry is invalid') % ( myFeature.id()) raise InvalidGeometryError(message) count += 1 feature_box = geometry.boundingBox().intersect(raster_box) print 'NEW AGGR: %s' % myFeature.id() # print 'Raster Box: %s' % raster_box.asWktCoordinates() # print 'Feature Box: %s' % feature_box.asWktCoordinates() offset_x, offset_y, cells_x, cells_y = intersection_box( raster_box, feature_box, cell_size_x, cell_size_y) # If the poly does not intersect the raster just continue if None in [offset_x, offset_y, cells_x, cells_y]: continue # avoid access to cells outside of the raster (may occur because of # rounding) if (offset_x + cells_x) > columns: offset_x = columns - offset_x if (offset_y + cells_y) > rows: cells_y = rows - offset_y intersected_geometry = raster_geometry.intersection(geometry) geometry_sum, count = numpy_stats( band, intersected_geometry, geo_transform, no_data, crs) if count <= 1: # The cell resolution is probably larger than the polygon area. # We switch to precise pixel - polygon intersection in this case geometry_sum, count = precise_stats( band, geometry, offset_x, offset_y, cells_x, cells_y, cell_size_x, cell_size_y, raster_box, no_data) # print geometry_sum, count if count == 0: mean = 0 else: mean = geometry_sum / count results[myFeature.id()] = { 'sum': geometry_sum, 'count': count, 'mean': mean} # noinspection PyUnusedLocal feature_id = None # Close return results