def test_main_type(self): """Test the good feature type according to the value mapping.""" mapping = { 'residential': ['house', 'apartments', 'residential'], 'industrial': ['commercial', 'retail'] } self.assertEqual(main_type('residential', mapping), 'residential') self.assertEqual(main_type('house', mapping), 'residential') self.assertEqual(main_type('apartments', mapping), 'residential') self.assertEqual(main_type('warehouse', mapping), 'other') self.assertEqual(main_type(None, mapping), 'other') self.assertEqual(main_type('null', mapping), 'other')
def _calculate_type(self, category): """Indicator that shows total features impacted for one category. This indicator reports the features by category. The logic is: - look for the fields that occurs with a name included in self.valid_type_fields - if the main usage from a record is equal to the category then it is considered affected. This function uses safe.utilities.utilities.main_type """ result = 0 if self.type_fields is not None: try: for feature in self.impact_attrs: for type_field in self.type_fields: field_value = feature[self.target_field] val = 0 if isinstance(field_value, basestring): if field_value != 'Not Affected': val += self.feature_value(feature) else: if field_value: val += self.feature_value(feature) if val: feature_type = feature[type_field] main_feature_type = main_type( feature_type, self.value_mapping) if main_feature_type == category: result += val break result = int(round(result)) except (ValueError, KeyError): result = self.NO_DATA_TEXT else: if self.no_features: result = 0 else: result = self.NO_DATA_TEXT self._append_result(category, result)
def run(self): """Classified hazard impact to buildings (e.g. from Open Street Map). """ # Value from layer's keywords structure_class_field = self.exposure.keyword("structure_class_field") try: exposure_value_mapping = self.exposure.keyword("value_mapping") except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} self.hazard_class_mapping = self.hazard.keyword("value_map") keys = [x["key"] for x in generic_raster_hazard_classes["classes"]] names = [x["name"] for x in generic_raster_hazard_classes["classes"]] classes = OrderedDict() for i in range(len(keys)): classes[keys[i]] = names[i] # Determine attribute name for hazard class hazard_class_attribute = get_non_conflicting_attribute_name( "haz_class", [x.keys() for x in self.exposure.layer.data][0] ) interpolated_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_class_attribute, mode="constant" ) # Extract relevant exposure data attributes = interpolated_result.get_data() # Number of building in the interpolated layer buildings_total = len(interpolated_result) # Inverse the order from low to high self.init_report_var(classes.values()[::-1]) for i in range(buildings_total): # Get the usage of the building usage = attributes[i][structure_class_field] usage = main_type(usage, exposure_value_mapping) # Initialize value as Not affected attributes[i][self.target_field] = tr("Not affected") attributes[i][self.affected_field] = 0 # Get the hazard level of the building level = float(attributes[i][hazard_class_attribute]) level = float(numpy_round(level)) # Find the class according the building's level for k, v in self.hazard_class_mapping.items(): if level in v: impact_class = classes[k] # Set the impact level attributes[i][self.target_field] = impact_class # Set to affected attributes[i][self.affected_field] = 1 break # Count affected buildings by type self.classify_feature(attributes[i][self.target_field], usage, bool(attributes[i][self.affected_field])) self.reorder_dictionaries() # Create style # Low, Medium and High are translated in the attribute table. # "Not affected" is not translated in the attribute table. style_classes = [ dict( label=tr("Not Affected"), value="Not affected", colour="#1EFC7C", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("Low"), value=tr("Low hazard zone"), colour="#EBF442", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("Medium"), value=tr("Medium hazard zone"), colour="#F4A442", transparency=0, size=2, border_color="#969696", border_width=0.2, ), dict( label=tr("High"), value=tr("High hazard zone"), colour="#F31A1C", transparency=0, size=2, border_color="#969696", border_width=0.2, ), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type="categorizedSymbol") LOGGER.debug("target field : " + self.target_field) impact_data = self.generate_data() extra_keywords = { "target_field": self.affected_field, "map_title": self.map_title(), "legend_units": self.metadata().key("legend_units"), "legend_title": self.metadata().key("legend_title"), "buildings_total": buildings_total, "buildings_affected": self.total_affected_buildings, } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create impact layer and return impact_layer = Vector( data=attributes, projection=self.exposure.layer.get_projection(), geometry=self.exposure.layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info, ) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Experimental impact function for flood polygons on roads.""" # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') # There is no wet in the class mapping if self.wet not in self.hazard_class_mapping: raise ZeroImpactException( tr('There is no flooded area in the hazard layers, thus there ' 'is no affected road.')) self.exposure_class_attribute = self.exposure.keyword( 'road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_provider = self.hazard.layer.dataProvider() affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) # see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) # raise GetDataError(message) # LOGGER.info('Affected field: %s' % self.hazard_class_attribute) # LOGGER.info('Affected field index: %s' % affected_field_index) # Filter geometry and data using the extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform(self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = self.hazard.layer.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: value = attributes[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( 'There are no objects in the hazard layer with %s (Affected ' 'Field) in %s (Affected Value). Please check the value or use ' 'a different extent.' % (self.hazard_class_attribute, self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(requested_extent) line_layer = clip_by_polygon(self.exposure.layer, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon(line_layer, hazard_poly, request, mark_value=(self.target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(self.exposure.layer.crs(), destination_crs) roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex( self.exposure_class_attribute) target_field_index = line_layer.fieldNameIndex(self.target_field) classes = [tr('Temporarily closed')] self.init_report_var(classes) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, affected) self.reorder_dictionaries() style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5) ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it if line_layer.featureCount() == 0: # Raising an exception seems poor semantics here.... raise ZeroImpactException( tr('No roads are flooded in this scenario.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector(data=line_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Tsunami raster impact to buildings (e.g. from Open Street Map).""" # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') self.init_report_var(self.hazard_classes) for i in range(total_features): # Get the interpolated depth water_depth = float(features[i][self.target_field]) if water_depth <= 0: inundated_status = 0 elif 0 < water_depth <= low_max: inundated_status = 1 # low elif low_max < water_depth <= medium_max: inundated_status = 2 # medium elif medium_max < water_depth <= high_max: inundated_status = 3 # high elif high_max < water_depth: inundated_status = 4 # very high # If not a number or a value beside real number. else: inundated_status = 0 usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = inundated_status category = self.categories[inundated_status] self.classify_feature(category, usage, True) self.reorder_dictionaries() style_classes = [ dict(label=self.hazard_classes[0] + ': 0 m', value=0, colour='#00FF00', transparency=0, size=1), dict(label=self.hazard_classes[1] + ': >0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1), dict(label=self.hazard_classes[2] + ': %.1f - %.1f m' % (low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1), dict(label=self.hazard_classes[3] + ': %.1f - %.1f m' % (medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1), dict(label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), 'buildings_total': total_features, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector(data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Experimental impact function.""" # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') # There is no wet in the class mapping if self.wet not in self.hazard_class_mapping: raise ZeroImpactException(tr( 'There is no flooded area in the hazard layers, thus there ' 'is no affected building.')) self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) if affected_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of the ' 'hazard layer. Please change the Affected Field parameter in ' 'the IF Option.') % self.hazard_class_attribute raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( self.exposure_class_attribute) if building_type_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of ' 'the exposure layer. Please change the Building Type ' 'Field parameter in the IF Option.' ) % self.exposure_class_attribute raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes( [QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex( self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the buildings from E and extent buildings_are_points = is_point_layer(self.exposure.layer) if buildings_are_points: building_layer = QgsVectorLayer( 'Point?crs=' + srs, 'impact_buildings', 'memory') else: building_layer = QgsVectorLayer( 'Polygon?crs=' + srs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( 'There are no objects in the hazard layer with %s ' 'value in %s. Please check your data or use another ' 'attribute.') % ( self.hazard_class_attribute, ', '.join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Filter out just those EXPOSURE features in the analysis extents transform = QgsCoordinateTransform( self.requested_extent_crs, self.exposure.layer.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # We will use this transform to project each exposure feature into # the CRS of the Hazard. transform = QgsCoordinateTransform( self.exposure.crs(), self.hazard.crs()) features = [] for feature in self.exposure.layer.getFeatures(request): # Make a deep copy as the geometry is passed by reference # If we don't do this, subsequent operations will affect the # original feature geometry as well as the copy TS building_geom = QgsGeometry(feature.geometry()) # Project the building geometry to hazard CRS building_bounds = transform.transform(building_geom.boundingBox()) affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_bounds) for fid in ids: # run (slow) exact intersection test building_geom.transform(transform) if hazard_geometries[fid].intersects(building_geom): affected = True break new_feature = QgsFeature() # We write out the original feature geom, not the projected one new_feature.setGeometry(feature.geometry()) new_feature.setAttributes(feature.attributes()) new_feature[target_field_index] = 1 if affected else 0 features.append(new_feature) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report hazard_classes = [tr('Flooded')] self.init_report_var(hazard_classes) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() usage = record[building_type_field_index] usage = main_type(usage, exposure_value_mapping) affected = False if record[target_field_index] == 1: affected = True self.classify_feature(hazard_classes[0], usage, affected) self.reorder_dictionaries() style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr( 'No buildings were impacted by this flood.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=building_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Experimental impact function for flood polygons on roads.""" # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_provider = self.hazard.layer.dataProvider() affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) # see #818: should still work if there is no valid attribute if affected_field_index == -1: pass # message = tr('''Parameter "Affected Field"(='%s') # is not present in the attribute table of the hazard layer. # ''' % (affected_field, )) # raise GetDataError(message) # LOGGER.info('Affected field: %s' % self.hazard_class_attribute) # LOGGER.info('Affected field index: %s' % affected_field_index) # Filter geometry and data using the extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split line_layer by hazard and save as result: # 1) Filter from hazard inundated features # 2) Mark roads as inundated (1) or not inundated (0) ################################# # REMARK 1 # In qgis 2.2 we can use request to filter inundated # polygons directly (it allows QgsExpression). Then # we can delete the lines and call # # request = .... # hazard_poly = union_geometry(H, request) # ################################ hazard_features = self.hazard.layer.getFeatures(request) hazard_poly = None for feature in hazard_features: attributes = feature.attributes() if affected_field_index != -1: value = attributes[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue if hazard_poly is None: hazard_poly = QgsGeometry(feature.geometry()) else: # Make geometry union of inundated polygons # But some feature.geometry() could be invalid, skip them tmp_geometry = hazard_poly.combine(feature.geometry()) try: if tmp_geometry.isGeosValid(): hazard_poly = tmp_geometry except AttributeError: pass ############################################### # END REMARK 1 ############################################### if hazard_poly is None: message = tr( 'There are no objects in the hazard layer with %s (Affected ' 'Field) in %s (Affected Value). Please check the value or use ' 'a different extent.' % ( self.hazard_class_attribute, self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Clip exposure by the extent extent_as_polygon = QgsGeometry().fromRect(requested_extent) line_layer = clip_by_polygon(self.exposure.layer, extent_as_polygon) # Find inundated roads, mark them line_layer = split_by_polygon( line_layer, hazard_poly, request, mark_value=(self.target_field, 1)) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) destination_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), destination_crs) roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex( self.exposure_class_attribute) target_field_index = line_layer.fieldNameIndex(self.target_field) classes = [tr('Temporarily closed')] self.init_report_var(classes) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, affected) self.reorder_dictionaries() style_classes = [dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it if line_layer.featureCount() == 0: # Raising an exception seems poor semantics here.... raise ZeroImpactException( tr('No roads are flooded in this scenario.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info ) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Get parameters from IF parameter threshold_min = self.parameters['min threshold'].value threshold_max = self.parameters['max threshold'].value if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater than the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation index, flood_cells_map = _raster_to_vector_cells( small_raster, threshold_min, threshold_max, self.exposure.layer.crs()) if len(flood_cells_map) == 0: message = tr( 'There are no objects in the hazard layer with "value" > %s. ' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) classes = [tr('Flooded in the threshold (m)')] self.init_report_var(classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, affected) self.reorder_dictionaries() style_classes = [ dict( label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict( label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector( data=line_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation ranges = ranges_according_thresholds(low_max, medium_max, high_max) index, flood_cells_map = _raster_to_vector_cells( small_raster, ranges, self.exposure.layer.crs()) # Filter geometry and data using the extent ct = QgsCoordinateTransform(QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat(line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells(self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform(self.exposure.layer.crs(), output_crs) # Roads breakdown self.init_report_var(self.hazard_classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() affected = attributes[target_field_index] if isinstance(affected, QPyNullVariant): continue else: hazard_zone = self.hazard_classes[affected] usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False num_classes = len(self.hazard_classes) if attributes[target_field_index] in range(num_classes): affected = True self.classify_feature(hazard_zone, usage, length, affected) self.reorder_dictionaries() style_classes = [ # FIXME 0 - 0.1 dict(label=self.hazard_classes[0] + ': 0m', value=0, colour='#00FF00', transparency=0, size=1), dict(label=self.hazard_classes[1] + ': >0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1), dict(label=self.hazard_classes[2] + ': %.1f - %.1f m' % (low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1), dict(label=self.hazard_classes[3] + ': %.1f - %.1f m' % (medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1), dict(label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1), ] style_info = dict(target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector(data=line_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Tsunami raster impact to buildings (e.g. from Open Street Map).""" # Range for ash hazard group_parameters = self.parameters['group_threshold'] unaffected_max = group_parameters.value_map[ 'unaffected_threshold'].value very_low_max = group_parameters.value_map['very_low_threshold'].value low_max = group_parameters.value_map['low_threshold'].value medium_max = group_parameters.value_map['moderate_threshold'].value high_max = group_parameters.value_map['high_threshold'].value # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) try: population_field = self.exposure.keyword('population_field') except KeywordNotFoundError: population_field = None # required for real time self.exposure.keyword('name_field') structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') self.init_report_var(self.hazard_classes) for i in range(total_features): # Get the interpolated depth ash_hazard_zone = float(features[i][self.target_field]) if ash_hazard_zone <= unaffected_max: current_hash_zone = 0 # not affected elif unaffected_max < ash_hazard_zone <= very_low_max: current_hash_zone = 1 # very low elif very_low_max < ash_hazard_zone <= low_max: current_hash_zone = 2 # low elif low_max < ash_hazard_zone <= medium_max: current_hash_zone = 2 # medium elif medium_max < ash_hazard_zone <= high_max: current_hash_zone = 3 # high elif high_max < ash_hazard_zone: current_hash_zone = 4 # very high # If not a number or a value beside real number. else: current_hash_zone = 0 usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = current_hash_zone category = self.hazard_classes[current_hash_zone] if population_field is not None: population = float(features[i][population_field]) else: population = 1 self.classify_feature(category, usage, population, True) self.reorder_dictionaries() style_classes = [ dict( label=self.hazard_classes[0] + ': >%.1f - %.1f cm' % ( unaffected_max, very_low_max), value=0, colour='#00FF00', transparency=0, size=1 ), dict( label=self.hazard_classes[1] + ': >%.1f - %.1f cm' % ( very_low_max, low_max), value=1, colour='#FFFF00', transparency=0, size=1 ), dict( label=self.hazard_classes[2] + ': >%.1f - %.1f cm' % ( low_max, medium_max), value=2, colour='#FFB700', transparency=0, size=1 ), dict( label=self.hazard_classes[3] + ': >%.1f - %.1f cm' % ( medium_max, high_max), value=3, colour='#FF6F00', transparency=0, size=1 ), dict( label=self.hazard_classes[4] + ': <%.1f cm' % high_max, value=4, colour='#FF0000', transparency=0, size=1 ), ] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Experimental impact function.""" # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Prepare Hazard Layer hazard_provider = self.hazard.layer.dataProvider() # Check affected field exists in the hazard layer affected_field_index = hazard_provider.fieldNameIndex( self.hazard_class_attribute) if affected_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of the ' 'hazard layer. Please change the Affected Field parameter in ' 'the IF Option.') % self.hazard_class_attribute raise GetDataError(message) srs = self.exposure.layer.crs().toWkt() exposure_provider = self.exposure.layer.dataProvider() exposure_fields = exposure_provider.fields() # Check self.exposure_class_attribute exists in exposure layer building_type_field_index = exposure_provider.fieldNameIndex( self.exposure_class_attribute) if building_type_field_index == -1: message = tr( 'Field "%s" is not present in the attribute table of ' 'the exposure layer. Please change the Building Type ' 'Field parameter in the IF Option.' ) % self.exposure_class_attribute raise GetDataError(message) # If target_field does not exist, add it: if exposure_fields.indexFromName(self.target_field) == -1: exposure_provider.addAttributes( [QgsField(self.target_field, QVariant.Int)]) target_field_index = exposure_provider.fieldNameIndex( self.target_field) exposure_fields = exposure_provider.fields() # Create layer to store the buildings from E and extent buildings_are_points = is_point_layer(self.exposure.layer) if buildings_are_points: building_layer = QgsVectorLayer( 'Point?crs=' + srs, 'impact_buildings', 'memory') else: building_layer = QgsVectorLayer( 'Polygon?crs=' + srs, 'impact_buildings', 'memory') building_provider = building_layer.dataProvider() # Set attributes building_provider.addAttributes(exposure_fields.toList()) building_layer.startEditing() building_layer.commitChanges() # Filter geometry and data using the requested extent requested_extent = QgsRectangle(*self.requested_extent) # This is a hack - we should be setting the extent CRS # in the IF base class via safe/engine/core.py:calculate_impact # for now we assume the extent is in 4326 because it # is set to that from geo_extent # See issue #1857 transform = QgsCoordinateTransform( self.requested_extent_crs, self.hazard.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # Split building_layer by H and save as result: # 1) Filter from H inundated features # 2) Mark buildings as inundated (1) or not inundated (0) # make spatial index of affected polygons hazard_index = QgsSpatialIndex() hazard_geometries = {} # key = feature id, value = geometry has_hazard_objects = False for feature in self.hazard.layer.getFeatures(request): value = feature[affected_field_index] if value not in self.hazard_class_mapping[self.wet]: continue hazard_index.insertFeature(feature) hazard_geometries[feature.id()] = QgsGeometry(feature.geometry()) has_hazard_objects = True if not has_hazard_objects: message = tr( 'There are no objects in the hazard layer with %s ' 'value in %s. Please check your data or use another ' 'attribute.') % ( self.hazard_class_attribute, ', '.join(self.hazard_class_mapping[self.wet])) raise GetDataError(message) # Filter out just those EXPOSURE features in the analysis extents transform = QgsCoordinateTransform( self.requested_extent_crs, self.exposure.layer.crs()) projected_extent = transform.transformBoundingBox(requested_extent) request = QgsFeatureRequest() request.setFilterRect(projected_extent) # We will use this transform to project each exposure feature into # the CRS of the Hazard. transform = QgsCoordinateTransform( self.exposure.crs(), self.hazard.crs()) features = [] for feature in self.exposure.layer.getFeatures(request): # Make a deep copy as the geometry is passed by reference # If we don't do this, subsequent operations will affect the # original feature geometry as well as the copy TS building_geom = QgsGeometry(feature.geometry()) # Project the building geometry to hazard CRS building_bounds = transform.transform(building_geom.boundingBox()) affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_bounds) for fid in ids: # run (slow) exact intersection test building_geom.transform(transform) if hazard_geometries[fid].intersects(building_geom): affected = True break new_feature = QgsFeature() # We write out the original feature geom, not the projected one new_feature.setGeometry(feature.geometry()) new_feature.setAttributes(feature.attributes()) new_feature[target_field_index] = 1 if affected else 0 features.append(new_feature) # every once in a while commit the created features # to the output layer if len(features) == 1000: (_, __) = building_provider.addFeatures(features) features = [] (_, __) = building_provider.addFeatures(features) building_layer.updateExtents() # Generate simple impact report hazard_classes = [tr('Flooded')] self.init_report_var(hazard_classes) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() usage = record[building_type_field_index] usage = main_type(usage, exposure_value_mapping) affected = False if record[target_field_index] == 1: affected = True self.classify_feature(hazard_classes[0], usage, affected) self.reorder_dictionaries() style_classes = [ dict(label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict(label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') # Convert QgsVectorLayer to inasafe layer and return it. if building_layer.featureCount() < 1: raise ZeroImpactException(tr( 'No buildings were impacted by this flood.')) impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=building_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Tsunami raster impact to buildings (e.g. from Open Street Map).""" # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') self.init_report_var(self.hazard_classes) for i in range(total_features): # Get the interpolated depth water_depth = float(features[i][self.target_field]) if water_depth <= 0: inundated_status = 0 elif 0 < water_depth <= low_max: inundated_status = 1 # low elif low_max < water_depth <= medium_max: inundated_status = 2 # medium elif medium_max < water_depth <= high_max: inundated_status = 3 # high elif high_max < water_depth: inundated_status = 4 # very high # If not a number or a value beside real number. else: inundated_status = 0 usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = inundated_status category = self.categories[inundated_status] self.classify_feature(category, usage, True) self.reorder_dictionaries() style_classes = [ dict( label=self.hazard_classes[0] + ': 0 m', value=0, colour='#00FF00', transparency=0, size=1 ), dict( label=self.hazard_classes[1] + ': >0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1 ), dict( label=self.hazard_classes[2] + ': %.1f - %.1f m' % ( low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1 ), dict( label=self.hazard_classes[3] + ': %.1f - %.1f m' % ( medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1 ), dict( label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1 ), ] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), 'buildings_total': total_features, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Classified hazard impact to buildings (e.g. from Open Street Map). """ # Value from layer's keywords structure_class_field = self.exposure.keyword('structure_class_field') try: exposure_value_mapping = self.exposure.keyword('value_mapping') except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} self.hazard_class_mapping = self.hazard.keyword('value_map') keys = [x['key'] for x in generic_raster_hazard_classes['classes']] names = [x['name'] for x in generic_raster_hazard_classes['classes']] classes = OrderedDict() for i in range(len(keys)): classes[keys[i]] = names[i] # Determine attribute name for hazard class hazard_class_attribute = get_non_conflicting_attribute_name( 'haz_class', [x.keys() for x in self.exposure.layer.data][0]) interpolated_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_class_attribute, mode='constant') # Extract relevant exposure data attributes = interpolated_result.get_data() # Number of building in the interpolated layer buildings_total = len(interpolated_result) # Inverse the order from low to high self.init_report_var(classes.values()[::-1]) for i in range(buildings_total): # Get the usage of the building usage = attributes[i][structure_class_field] usage = main_type(usage, exposure_value_mapping) # Initialize value as Not affected attributes[i][self.target_field] = tr('Not affected') attributes[i][self.affected_field] = 0 # Get the hazard level of the building level = float(attributes[i][hazard_class_attribute]) level = float(numpy_round(level)) # Find the class according the building's level for k, v in self.hazard_class_mapping.items(): if level in v: impact_class = classes[k] # Set the impact level attributes[i][self.target_field] = impact_class # Set to affected attributes[i][self.affected_field] = 1 break # Count affected buildings by type self.classify_feature(attributes[i][self.target_field], usage, bool(attributes[i][self.affected_field])) self.reorder_dictionaries() # Create style # Low, Medium and High are translated in the attribute table. # "Not affected" is not translated in the attribute table. style_classes = [ dict(label=tr('Not Affected'), value='Not affected', colour='#1EFC7C', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('Low'), value=tr('Low hazard zone'), colour='#EBF442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('Medium'), value=tr('Medium hazard zone'), colour='#F4A442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict(label=tr('High'), value=tr('High hazard zone'), colour='#F31A1C', transparency=0, size=2, border_color='#969696', border_width=0.2), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') LOGGER.debug('target field : ' + self.target_field) impact_data = self.generate_data() extra_keywords = { 'target_field': self.affected_field, 'map_title': self.map_title(), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title'), 'buildings_total': buildings_total, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create impact layer and return impact_layer = Vector(data=attributes, projection=self.exposure.layer.get_projection(), geometry=self.exposure.layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Classified hazard impact to buildings (e.g. from Open Street Map). """ # Value from layer's keywords structure_class_field = self.exposure.keyword('structure_class_field') try: exposure_value_mapping = self.exposure.keyword('value_mapping') except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} # The 3 classes categorical_hazards = self.parameters['Categorical hazards'].value low_t = categorical_hazards[0].value medium_t = categorical_hazards[1].value high_t = categorical_hazards[2].value # Determine attribute name for hazard levels if self.hazard.layer.is_raster: hazard_attribute = 'level' else: hazard_attribute = None interpolated_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_attribute, mode='constant') # Extract relevant exposure data attributes = interpolated_result.get_data() buildings_total = len(interpolated_result) hazard_classes = [ tr('Low Hazard Class'), tr('Medium Hazard Class'), tr('High Hazard Class') ] self.init_report_var(hazard_classes) for i in range(buildings_total): usage = attributes[i][structure_class_field] usage = main_type(usage, exposure_value_mapping) # Count all buildings by type attributes[i][self.target_field] = 0 attributes[i][self.affected_field] = 0 level = float(attributes[i]['level']) level = float(numpy_round(level)) if level == high_t: impact_level = tr('High Hazard Class') elif level == medium_t: impact_level = tr('Medium Hazard Class') elif level == low_t: impact_level = tr('Low Hazard Class') else: continue # Add calculated impact to existing attributes attributes[i][self.target_field] = { tr('High Hazard Class'): 3, tr('Medium Hazard Class'): 2, tr('Low Hazard Class'): 1 }[impact_level] attributes[i][self.affected_field] = 1 # Count affected buildings by type self.classify_feature(impact_level, usage, True) self.reorder_dictionaries() # Consolidate the small building usage groups < 25 to other # Building threshold #2468 postprocessors = self.parameters['postprocessors'] building_postprocessors = postprocessors['BuildingType'][0] self.building_report_threshold = building_postprocessors.value[0].value self._consolidate_to_other() # Create style style_classes = [ dict( label=tr('Not Affected'), value=None, colour='#1EFC7C', transparency=0, size=2, border_color='#969696', border_width=0.2), dict( label=tr('Low'), value=1, colour='#EBF442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict( label=tr('Medium'), value=2, colour='#F4A442', transparency=0, size=2, border_color='#969696', border_width=0.2), dict( label=tr('High'), value=3, colour='#F31A1C', transparency=0, size=2, border_color='#969696', border_width=0.2), ] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.affected_field, 'map_title': self.metadata().key('map_title'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title'), 'buildings_total': buildings_total, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create impact layer and return impact_layer = Vector( data=attributes, projection=self.exposure.layer.get_projection(), geometry=self.exposure.layer.get_geometry(), name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Risk plugin for classified polygon hazard on building/structure. Counts number of building exposed to each hazard zones. :returns: Impact vector layer building exposed to each hazard zones. Table with number of buildings affected :rtype: Vector """ # Value from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') try: exposure_value_mapping = self.exposure.keyword('value_mapping') except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Iterate over vector hazard classes hazard_classes = [] for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building hazard_classes.append(vector_hazard_class['name']) hazard_zone_attribute_index = self.hazard.layer.fieldNameIndex( self.hazard_class_attribute) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute_index < 0: message = ( 'Hazard data %s does not contain expected attribute %s ' % (self.hazard.layer.name(), self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Hazard zone categories from hazard layer unique_values = self.hazard.layer.uniqueValues( hazard_zone_attribute_index) # Values might be integer or float, we should have unicode. #2626 self.hazard_zones = [get_unicode(val) for val in unique_values] self.init_report_var(hazard_classes) wgs84_extent = QgsRectangle(self.requested_extent[0], self.requested_extent[1], self.requested_extent[2], self.requested_extent[3]) # Run interpolation function for polygon2polygon interpolated_layer = interpolate_polygon_polygon( self.hazard.layer, self.exposure.layer, wgs84_extent) new_field = QgsField(self.target_field, QVariant.String) interpolated_layer.dataProvider().addAttributes([new_field]) interpolated_layer.updateFields() target_field_index = interpolated_layer.fieldNameIndex( self.target_field) changed_values = {} if interpolated_layer.featureCount() < 1: raise ZeroImpactException() # Extract relevant interpolated data for feature in interpolated_layer.getFeatures(): # Get the hazard value based on the value mapping in keyword hazard_value = get_key_for_value( feature[self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value changed_values[feature.id()] = {target_field_index: hazard_value} usage = feature[self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.hazard_class_mapping.keys(): affected = True self.classify_feature(hazard_value, usage, affected) interpolated_layer.dataProvider().changeAttributeValues(changed_values) self.reorder_dictionaries() # Create style categories = self.affected_buildings.keys() categories.append(self._not_affected_value) colours = color_ramp(len(categories)) style_classes = [] for i, hazard_zone in enumerate(self.affected_buildings.keys()): style_class = dict() style_class['label'] = tr(hazard_zone) style_class['transparency'] = 0 style_class['value'] = hazard_zone style_class['size'] = 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector(data=interpolated_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Flood impact to buildings (e.g. from Open Street Map).""" threshold = self.parameters['threshold'].value # Flood threshold [m] verify(isinstance(threshold, float), 'Expected thresholds to be a float. Got %s' % str(threshold)) # Determine attribute name for hazard levels hazard_attribute = 'depth' # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_attribute) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_classes = [tr('Flooded'), tr('Wet'), tr('Dry')] self.init_report_var(hazard_classes) for i in range(total_features): # Get the interpolated depth water_depth = float(features[i]['depth']) if water_depth <= 0: inundated_status = 0 # dry elif water_depth >= threshold: inundated_status = 1 # inundated else: inundated_status = 2 # wet usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = inundated_status category = [tr('Dry'), tr('Flooded'), tr('Wet')][inundated_status] self.classify_feature(category, usage, True) self.reorder_dictionaries() style_classes = [ dict(label=tr('Dry (<= 0 m)'), value=0, colour='#1EFC7C', transparency=0, size=1), dict(label=tr('Wet (0 m - %.1f m)') % threshold, value=2, colour='#FF9900', transparency=0, size=1), dict(label=tr('Flooded (>= %.1f m)') % threshold, value=1, colour='#F31A1C', transparency=0, size=1) ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), 'buildings_total': total_features, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector(data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Counts number of building exposed to each volcano hazard zones. :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Parameters radii = self.parameters['distances'].value # Get parameters from layer's keywords volcano_name_attribute = self.hazard.keyword('volcano_name_field') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Category names for the impact zone category_names = radii # In kilometers self._affected_categories_volcano = [ tr('Radius %.1f km') % key for key in radii[::]] # Get names of volcanoes considered if volcano_name_attribute in self.hazard.layer.get_attribute_names(): for row in self.hazard.layer.get_data(): # Run through all polygons and get unique names self.volcano_names.add(row[volcano_name_attribute]) # Find the target field name that has no conflict with the attribute # names in the hazard layer hazard_attribute_names = self.hazard.layer.get_attribute_names() target_field = get_non_conflicting_attribute_name( self.target_field, hazard_attribute_names) # Run interpolation function for polygon2polygon interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer) # Extract relevant interpolated layer data features = interpolated_layer.get_data() self.init_report_var(radii) # Iterate the interpolated building layer for i in range(len(features)): hazard_value = features[i][self.hazard_zone_attribute] if not hazard_value: hazard_value = self._not_affected_value features[i][target_field] = hazard_value # Count affected buildings by usage type if available usage = features[i][self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.affected_buildings.keys(): affected = True self.classify_feature(hazard_value, usage, affected) self.reorder_dictionaries() # Adding 'km'. affected_building_keys = self.affected_buildings.keys() for key in affected_building_keys: self.affected_buildings[tr('Radius %.1f km' % key)] = \ self.affected_buildings.pop(key) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(category_names)] style_classes = [] for i, category_name in enumerate(category_names): style_class = dict() style_class['label'] = tr('Radius %s km') % tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(category_names): i = len(category_names) - 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': target_field, 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Risk plugin for volcano hazard on building/structure. Counts number of building exposed to each volcano hazard zones. :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.name_attribute = self.hazard.keyword('volcano_name_field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Input checks if not self.hazard.layer.is_polygon_data: message = ( 'Input hazard must be a polygon. I got %s with ' 'layer type %s' % (self.hazard.name, self.hazard.layer.get_geometry_name())) raise Exception(message) # Check if hazard_zone_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): message = ( 'Hazard data %s did not contain expected attribute %s ' % (self.hazard.name, self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Get names of volcanoes considered if self.name_attribute in self.hazard.layer.get_attribute_names(): volcano_name_list = set() for row in self.hazard.layer.get_data(): # Run through all polygons and get unique names volcano_name_list.add(row[self.name_attribute]) self.volcano_names = ', '.join(volcano_name_list) else: self.volcano_names = tr('Not specified in data') # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Initialize OrderedDict of affected buildings hazard_class = [] # Iterate over vector hazard classes for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building hazard_class.append(vector_hazard_class['name']) # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer) # Extract relevant exposure data features = interpolated_layer.get_data() self.init_report_var(hazard_class) for i in range(len(features)): # Get the hazard value based on the value mapping in keyword hazard_value = get_key_for_value( features[i][self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value features[i][self.target_field] = get_string(hazard_value) usage = features[i][self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.affected_buildings.keys(): affected = True self.classify_feature(hazard_value, usage, affected) self.reorder_dictionaries() # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(self.affected_buildings.keys())] style_classes = [] for i, category_name in enumerate(self.affected_buildings.keys()): style_class = dict() style_class['label'] = tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(self.affected_buildings.keys()): i = len(self.affected_buildings.keys()) - 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.metadata().key('map_title'), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info ) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ # Thresholds for tsunami hazard zone breakdown. low_max = self.parameters['low_threshold'].value medium_max = self.parameters['medium_threshold'].value high_max = self.parameters['high_threshold'].value target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation ranges = ranges_according_thresholds(low_max, medium_max, high_max) index, flood_cells_map = _raster_to_vector_cells( small_raster, ranges, self.exposure.layer.crs()) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) # Roads breakdown self.init_report_var(self.hazard_classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() affected = attributes[target_field_index] if isinstance(affected, QPyNullVariant): continue else: hazard_zone = self.hazard_classes[affected] usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False num_classes = len(self.hazard_classes) if attributes[target_field_index] in range(num_classes): affected = True self.classify_feature(hazard_zone, usage, length, affected) self.reorder_dictionaries() style_classes = [ # FIXME 0 - 0.1 dict( label=self.hazard_classes[0] + ': 0m', value=0, colour='#00FF00', transparency=0, size=1 ), dict( label=self.hazard_classes[1] + ': >0 - %.1f m' % low_max, value=1, colour='#FFFF00', transparency=0, size=1 ), dict( label=self.hazard_classes[2] + ': %.1f - %.1f m' % ( low_max + 0.1, medium_max), value=2, colour='#FFB700', transparency=0, size=1 ), dict( label=self.hazard_classes[3] + ': %.1f - %.1f m' % ( medium_max + 0.1, high_max), value=3, colour='#FF6F00', transparency=0, size=1 ), dict( label=self.hazard_classes[4] + ' > %.1f m' % high_max, value=4, colour='#FF0000', transparency=0, size=1 ), ] style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Risk plugin for classified polygon hazard on building/structure. Counts number of building exposed to each hazard zones. :returns: Impact vector layer building exposed to each hazard zones. Table with number of buildings affected :rtype: Vector """ # Value from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') try: exposure_value_mapping = self.exposure.keyword('value_mapping') except KeywordNotFoundError: # Generic IF, the keyword might not be defined base.py exposure_value_mapping = {} # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Iterate over vector hazard classes hazard_classes = [] for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building hazard_classes.append(vector_hazard_class['name']) hazard_zone_attribute_index = self.hazard.layer.fieldNameIndex( self.hazard_class_attribute) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute_index < 0: message = ( 'Hazard data %s does not contain expected attribute %s ' % (self.hazard.layer.name(), self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Hazard zone categories from hazard layer unique_values = self.hazard.layer.uniqueValues( hazard_zone_attribute_index) # Values might be integer or float, we should have unicode. #2626 self.hazard_zones = [get_unicode(val) for val in unique_values] self.init_report_var(hazard_classes) wgs84_extent = QgsRectangle( self.requested_extent[0], self.requested_extent[1], self.requested_extent[2], self.requested_extent[3]) # Run interpolation function for polygon2polygon interpolated_layer = interpolate_polygon_polygon( self.hazard.layer, self.exposure.layer, wgs84_extent) new_field = QgsField(self.target_field, QVariant.String) interpolated_layer.dataProvider().addAttributes([new_field]) interpolated_layer.updateFields() target_field_index = interpolated_layer.fieldNameIndex( self.target_field) changed_values = {} if interpolated_layer.featureCount() < 1: raise ZeroImpactException() # Extract relevant interpolated data for feature in interpolated_layer.getFeatures(): # Get the hazard value based on the value mapping in keyword hazard_value = get_key_for_value( feature[self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value changed_values[feature.id()] = {target_field_index: hazard_value} usage = feature[self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.hazard_class_mapping.keys(): affected = True self.classify_feature(hazard_value, usage, affected) interpolated_layer.dataProvider().changeAttributeValues(changed_values) self.reorder_dictionaries() # Create style categories = self.affected_buildings.keys() categories.append(self._not_affected_value) colours = color_ramp(len(categories)) style_classes = [] for i, hazard_zone in enumerate(self.affected_buildings.keys()): style_class = dict() style_class['label'] = tr(hazard_zone) style_class['transparency'] = 0 style_class['value'] = hazard_zone style_class['size'] = 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol' ) impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=interpolated_layer, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Earthquake impact to buildings (e.g. from OpenStreetMap).""" LOGGER.debug('Running earthquake building impact') # merely initialize building_value = 0 contents_value = 0 # Thresholds for mmi breakdown. t0 = self.parameters['low_threshold'].value t1 = self.parameters['medium_threshold'].value t2 = self.parameters['high_threshold'].value # Class Attribute and Label. class_1 = {'label': tr('Low'), 'class': 1} class_2 = {'label': tr('Medium'), 'class': 2} class_3 = {'label': tr('High'), 'class': 3} # Define attribute name for hazard levels. hazard_attribute = 'mmi' # Determine if exposure data have NEXIS attributes. attribute_names = self.exposure.layer.get_attribute_names() if ('FLOOR_AREA' in attribute_names and 'BUILDING_C' in attribute_names and 'CONTENTS_C' in attribute_names): self.is_nexis = True else: self.is_nexis = False # Interpolate hazard level to building locations. interpolate_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_attribute) # Get parameters from layer's keywords structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') attributes = interpolate_result.get_data() interpolate_size = len(interpolate_result) hazard_classes = [tr('Low'), tr('Medium'), tr('High')] self.init_report_var(hazard_classes) removed = [] for i in range(interpolate_size): # Classify building according to shake level # and calculate dollar losses if self.is_nexis: try: area = float(attributes[i]['FLOOR_AREA']) except (ValueError, KeyError): # print 'Got area', attributes[i]['FLOOR_AREA'] area = 0.0 try: building_value_density = float(attributes[i]['BUILDING_C']) except (ValueError, KeyError): # print 'Got bld value', attributes[i]['BUILDING_C'] building_value_density = 0.0 try: contents_value_density = float(attributes[i]['CONTENTS_C']) except (ValueError, KeyError): # print 'Got cont value', attributes[i]['CONTENTS_C'] contents_value_density = 0.0 building_value = building_value_density * area contents_value = contents_value_density * area usage = attributes[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): if self.is_nexis: self.affected_buildings[category][usage] = OrderedDict( [(tr('Buildings Affected'), 0), (tr('Buildings value ($M)'), 0), (tr('Contents value ($M)'), 0)]) else: self.affected_buildings[category][usage] = \ OrderedDict([(tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 try: mmi = float(attributes[i][hazard_attribute]) # MMI except TypeError: mmi = 0.0 if t0 <= mmi < t1: cls = 1 category = tr('Low') elif t1 <= mmi < t2: cls = 2 category = tr('Medium') elif t2 <= mmi: cls = 3 category = tr('High') else: # Not reported for less than level t0 # RMN: We still need to add target_field attribute # So, set it to None attributes[i][self.target_field] = None continue attributes[i][self.target_field] = cls self.affected_buildings[category][usage][tr( 'Buildings Affected')] += 1 if self.is_nexis: self.affected_buildings[category][usage][tr( 'Buildings value ($M)')] += building_value / 1000000.0 self.affected_buildings[category][usage][tr( 'Contents value ($M)')] += contents_value / 1000000.0 self.reorder_dictionaries() # remove un-categorized element removed.reverse() geometry = interpolate_result.get_geometry() for i in range(0, len(removed)): del attributes[removed[i]] del geometry[removed[i]] if len(attributes) < 1: raise ZeroImpactException() # Create style style_classes = [ dict(label=class_1['label'], value=class_1['class'], colour='#ffff00', transparency=1), dict(label=class_2['label'], value=class_2['class'], colour='#ffaa00', transparency=1), dict(label=class_3['label'], value=class_3['class'], colour='#ff0000', transparency=1) ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field, } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector(data=attributes, projection=interpolate_result.get_projection(), geometry=geometry, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Earthquake impact to buildings (e.g. from OpenStreetMap).""" LOGGER.debug('Running earthquake building impact') # merely initialize building_value = 0 contents_value = 0 # Thresholds for mmi breakdown. t0 = self.parameters['low_threshold'].value t1 = self.parameters['medium_threshold'].value t2 = self.parameters['high_threshold'].value # Class Attribute and Label. class_1 = {'label': tr('Low'), 'class': 1} class_2 = {'label': tr('Medium'), 'class': 2} class_3 = {'label': tr('High'), 'class': 3} # Define attribute name for hazard levels. hazard_attribute = 'mmi' # Determine if exposure data have NEXIS attributes. attribute_names = self.exposure.layer.get_attribute_names() if ( 'FLOOR_AREA' in attribute_names and 'BUILDING_C' in attribute_names and 'CONTENTS_C' in attribute_names): self.is_nexis = True else: self.is_nexis = False # Interpolate hazard level to building locations. interpolate_result = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_attribute ) # Get parameters from layer's keywords structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') attributes = interpolate_result.get_data() interpolate_size = len(interpolate_result) hazard_classes = [tr('Low'), tr('Medium'), tr('High')] self.init_report_var(hazard_classes) removed = [] for i in range(interpolate_size): # Classify building according to shake level # and calculate dollar losses if self.is_nexis: try: area = float(attributes[i]['FLOOR_AREA']) except (ValueError, KeyError): # print 'Got area', attributes[i]['FLOOR_AREA'] area = 0.0 try: building_value_density = float(attributes[i]['BUILDING_C']) except (ValueError, KeyError): # print 'Got bld value', attributes[i]['BUILDING_C'] building_value_density = 0.0 try: contents_value_density = float(attributes[i]['CONTENTS_C']) except (ValueError, KeyError): # print 'Got cont value', attributes[i]['CONTENTS_C'] contents_value_density = 0.0 building_value = building_value_density * area contents_value = contents_value_density * area usage = attributes[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) if usage not in self.buildings: self.buildings[usage] = 0 for category in self.affected_buildings.keys(): if self.is_nexis: self.affected_buildings[category][usage] = OrderedDict( [ (tr('Buildings Affected'), 0), (tr('Buildings value ($M)'), 0), (tr('Contents value ($M)'), 0)]) else: self.affected_buildings[category][usage] = \ OrderedDict([(tr('Buildings Affected'), 0)]) self.buildings[usage] += 1 try: mmi = float(attributes[i][hazard_attribute]) # MMI except TypeError: mmi = 0.0 if t0 <= mmi < t1: cls = 1 category = tr('Low') elif t1 <= mmi < t2: cls = 2 category = tr('Medium') elif t2 <= mmi: cls = 3 category = tr('High') else: # Not reported for less than level t0 # RMN: We still need to add target_field attribute # So, set it to None attributes[i][self.target_field] = None continue attributes[i][self.target_field] = cls self.affected_buildings[ category][usage][tr('Buildings Affected')] += 1 if self.is_nexis: self.affected_buildings[category][usage][ tr('Buildings value ($M)')] += building_value / 1000000.0 self.affected_buildings[category][usage][ tr('Contents value ($M)')] += contents_value / 1000000.0 self.reorder_dictionaries() # remove un-categorized element removed.reverse() geometry = interpolate_result.get_geometry() for i in range(0, len(removed)): del attributes[removed[i]] del geometry[removed[i]] if len(attributes) < 1: raise ZeroImpactException() # Create style style_classes = [ dict( label=class_1['label'], value=class_1['class'], colour='#ffff00', transparency=1), dict( label=class_2['label'], value=class_2['class'], colour='#ffaa00', transparency=1), dict( label=class_3['label'], value=class_3['class'], colour='#ff0000', transparency=1)] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol' ) impact_data = self.generate_data() extra_keywords = { 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title'), 'target_field': self.target_field, } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=attributes, projection=interpolate_result.get_projection(), geometry=geometry, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A new line layer with inundated roads marked. :type: safe_layer """ target_field = self.target_field # Get parameters from layer's keywords road_class_field = self.exposure.keyword('road_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Get parameters from IF parameter threshold_min = self.parameters['min threshold'].value threshold_max = self.parameters['max threshold'].value if threshold_min > threshold_max: message = tr( 'The minimal threshold is greater than the maximal specified ' 'threshold. Please check the values.') raise GetDataError(message) # reproject self.extent to the hazard projection hazard_crs = self.hazard.layer.crs() hazard_authid = hazard_crs.authid() if hazard_authid == 'EPSG:4326': viewport_extent = self.requested_extent else: geo_crs = QgsCoordinateReferenceSystem() geo_crs.createFromSrid(4326) viewport_extent = extent_to_geo_array( QgsRectangle(*self.requested_extent), geo_crs, hazard_crs) # Clip hazard raster small_raster = align_clip_raster(self.hazard.layer, viewport_extent) # Filter geometry and data using the extent ct = QgsCoordinateTransform( QgsCoordinateReferenceSystem("EPSG:4326"), self.exposure.layer.crs()) extent = ct.transformBoundingBox(QgsRectangle(*self.requested_extent)) request = QgsFeatureRequest() request.setFilterRect(extent) # create template for the output layer line_layer_tmp = create_layer(self.exposure.layer) new_field = QgsField(target_field, QVariant.Int) line_layer_tmp.dataProvider().addAttributes([new_field]) line_layer_tmp.updateFields() # create empty output layer and load it filename = unique_filename(suffix='.shp') QgsVectorFileWriter.writeAsVectorFormat( line_layer_tmp, filename, "utf-8", None, "ESRI Shapefile") line_layer = QgsVectorLayer(filename, "flooded roads", "ogr") # Create vector features from the flood raster # For each raster cell there is one rectangular polygon # Data also get spatially indexed for faster operation index, flood_cells_map = _raster_to_vector_cells( small_raster, threshold_min, threshold_max, self.exposure.layer.crs()) if len(flood_cells_map) == 0: message = tr( 'There are no objects in the hazard layer with "value" > %s. ' 'Please check the value or use other extent.' % ( threshold_min, )) raise GetDataError(message) # Do the heavy work - for each road get flood polygon for that area and # do the intersection/difference to find out which parts are flooded _intersect_lines_with_vector_cells( self.exposure.layer, request, index, flood_cells_map, line_layer, target_field) target_field_index = line_layer.dataProvider().\ fieldNameIndex(target_field) # Generate simple impact report epsg = get_utm_epsg(self.requested_extent[0], self.requested_extent[1]) output_crs = QgsCoordinateReferenceSystem(epsg) transform = QgsCoordinateTransform( self.exposure.layer.crs(), output_crs) classes = [tr('Flooded in the threshold (m)')] self.init_report_var(classes) if line_layer.featureCount() < 1: raise ZeroImpactException() roads_data = line_layer.getFeatures() road_type_field_index = line_layer.fieldNameIndex(road_class_field) for road in roads_data: attributes = road.attributes() usage = attributes[road_type_field_index] usage = main_type(usage, exposure_value_mapping) geom = road.geometry() geom.transform(transform) length = geom.length() affected = False if attributes[target_field_index] == 1: affected = True self.classify_feature(classes[0], usage, length, affected) self.reorder_dictionaries() style_classes = [ dict( label=tr('Not Inundated'), value=0, colour='#1EFC7C', transparency=0, size=0.5), dict( label=tr('Inundated'), value=1, colour='#F31A1C', transparency=0, size=0.5)] style_info = dict( target_field=target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'map_title': self.metadata().key('map_title'), 'legend_title': self.metadata().key('legend_title'), 'target_field': target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Convert QgsVectorLayer to inasafe layer and return it impact_layer = Vector( data=line_layer, name=self.metadata().key('layer_name'), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Ash raster impact to buildings (e.g. from Open Street Map).""" # Range for ash hazard group_parameters = self.parameters['group_threshold'] unaffected_max = group_parameters.value_map[ 'unaffected_threshold'].value very_low_max = group_parameters.value_map['very_low_threshold'].value low_max = group_parameters.value_map['low_threshold'].value medium_max = group_parameters.value_map['moderate_threshold'].value high_max = group_parameters.value_map['high_threshold'].value # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) try: population_field = self.exposure.keyword('population_field') except KeywordNotFoundError: population_field = None # required for real time self.exposure.keyword('name_field') structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') self.init_report_var(self.hazard_classes) unaffected_feats = [] for i in range(total_features): # Get the interpolated depth ash_hazard_zone = float(features[i][self.target_field]) if ash_hazard_zone <= unaffected_max: # current_hash_zone = 0 # not affected unaffected_feats.append(i) continue # not affected elif unaffected_max < ash_hazard_zone <= very_low_max: current_hash_zone = 0 # very low elif very_low_max < ash_hazard_zone <= low_max: current_hash_zone = 1 # low elif low_max < ash_hazard_zone <= medium_max: current_hash_zone = 2 # medium elif medium_max < ash_hazard_zone <= high_max: current_hash_zone = 3 # high elif high_max < ash_hazard_zone: current_hash_zone = 4 # very high # If not a number or a value beside real number. else: # current_hash_zone = 0 unaffected_feats.append(i) continue usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = current_hash_zone category = self.hazard_classes[current_hash_zone] if population_field is not None: population = float(features[i][population_field]) else: population = 1 self.classify_feature(category, usage, population, True) geometries = interpolated_layer.get_geometry() unaffected_feats.reverse() for u in unaffected_feats: features.remove(features[u]) geometries.remove(geometries[u]) self.reorder_dictionaries() style_classes = [ dict(label=self.hazard_classes[0] + ': >%.1f - %.1f cm' % (unaffected_max, very_low_max), value=0, colour='#00FF00', transparency=0, size=1), dict(label=self.hazard_classes[1] + ': >%.1f - %.1f cm' % (very_low_max, low_max), value=1, colour='#FFFF00', transparency=0, size=1), dict(label=self.hazard_classes[2] + ': >%.1f - %.1f cm' % (low_max, medium_max), value=2, colour='#FFB700', transparency=0, size=1), dict(label=self.hazard_classes[3] + ': >%.1f - %.1f cm' % (medium_max, high_max), value=3, colour='#FF6F00', transparency=0, size=1), dict(label=self.hazard_classes[4] + ': <%.1f cm' % high_max, value=4, colour='#FF0000', transparency=0, size=1), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) if not features or len(features) == 0: raise ZeroImpactException() impact_layer = Vector(data=features, projection=interpolated_layer.get_projection(), geometry=geometries, name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Flood impact to buildings (e.g. from Open Street Map).""" threshold = self.parameters['threshold'].value # Flood threshold [m] verify(isinstance(threshold, float), 'Expected thresholds to be a float. Got %s' % str(threshold)) # Determine attribute name for hazard levels hazard_attribute = 'depth' # Interpolate hazard level to building locations interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=hazard_attribute) # Extract relevant exposure data features = interpolated_layer.get_data() total_features = len(interpolated_layer) structure_class_field = self.exposure.keyword('structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') hazard_classes = [tr('Flooded'), tr('Wet'), tr('Dry')] self.init_report_var(hazard_classes) for i in range(total_features): # Get the interpolated depth water_depth = float(features[i]['depth']) if water_depth <= 0: inundated_status = 0 # dry elif water_depth >= threshold: inundated_status = 1 # inundated else: inundated_status = 2 # wet usage = features[i].get(structure_class_field, None) usage = main_type(usage, exposure_value_mapping) # Add calculated impact to existing attributes features[i][self.target_field] = inundated_status category = [ tr('Dry'), tr('Flooded'), tr('Wet')][inundated_status] self.classify_feature(category, usage, True) self.reorder_dictionaries() style_classes = [ dict( label=tr('Dry (<= 0 m)'), value=0, colour='#1EFC7C', transparency=0, size=1 ), dict( label=tr('Wet (0 m - %.1f m)') % threshold, value=2, colour='#FF9900', transparency=0, size=1 ), dict( label=tr('Flooded (>= %.1f m)') % threshold, value=1, colour='#F31A1C', transparency=0, size=1 )] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_title': self.metadata().key('legend_title'), 'legend_units': self.metadata().key('legend_units'), 'buildings_total': total_features, 'buildings_affected': self.total_affected_buildings } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer
def run(self): """Risk plugin for volcano hazard on building/structure. Counts number of building exposed to each volcano hazard zones. :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Get parameters from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') self.name_attribute = self.hazard.keyword('volcano_name_field') self.hazard_class_mapping = self.hazard.keyword('value_map') self.exposure_class_attribute = self.exposure.keyword( 'structure_class_field') exposure_value_mapping = self.exposure.keyword('value_mapping') # Input checks if not self.hazard.layer.is_polygon_data: message = ( 'Input hazard must be a polygon. I got %s with ' 'layer type %s' % (self.hazard.name, self.hazard.layer.get_geometry_name())) raise Exception(message) # Check if hazard_zone_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): message = ( 'Hazard data %s did not contain expected attribute %s ' % (self.hazard.name, self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(message) # Get names of volcanoes considered if self.name_attribute in self.hazard.layer.get_attribute_names(): for row in self.hazard.layer.get_data(): # Run through all polygons and get unique names self.volcano_names.add(row[self.name_attribute]) # Retrieve the classification that is used by the hazard layer. vector_hazard_classification = self.hazard.keyword( 'vector_hazard_classification') # Get the dictionary that contains the definition of the classification vector_hazard_classification = definition(vector_hazard_classification) # Get the list classes in the classification vector_hazard_classes = vector_hazard_classification['classes'] # Initialize OrderedDict of affected buildings hazard_class = [] # Iterate over vector hazard classes for vector_hazard_class in vector_hazard_classes: # Check if the key of class exist in hazard_class_mapping if vector_hazard_class['key'] in self.hazard_class_mapping.keys(): # Replace the key with the name as we need to show the human # friendly name in the report. self.hazard_class_mapping[vector_hazard_class['name']] = \ self.hazard_class_mapping.pop(vector_hazard_class['key']) # Adding the class name as a key in affected_building hazard_class.append(vector_hazard_class['name']) # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer) # Extract relevant exposure data features = interpolated_layer.get_data() self.init_report_var(hazard_class) for i in range(len(features)): # Get the hazard value based on the value mapping in keyword hazard_value = get_key_for_value( features[i][self.hazard_class_attribute], self.hazard_class_mapping) if not hazard_value: hazard_value = self._not_affected_value features[i][self.target_field] = get_string(hazard_value) usage = features[i][self.exposure_class_attribute] usage = main_type(usage, exposure_value_mapping) affected = False if hazard_value in self.affected_buildings.keys(): affected = True self.classify_feature(hazard_value, usage, affected) self.reorder_dictionaries() # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] colours = colours[::-1] # flip colours = colours[:len(self.affected_buildings.keys())] style_classes = [] for i, category_name in enumerate(self.affected_buildings.keys()): style_class = dict() style_class['label'] = tr(category_name) style_class['transparency'] = 0 style_class['value'] = category_name style_class['size'] = 1 if i >= len(self.affected_buildings.keys()): i = len(self.affected_buildings.keys()) - 1 style_class['colour'] = colours[i] style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') impact_data = self.generate_data() extra_keywords = { 'target_field': self.target_field, 'map_title': self.map_title(), 'legend_notes': self.metadata().key('legend_notes'), 'legend_units': self.metadata().key('legend_units'), 'legend_title': self.metadata().key('legend_title') } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=features, projection=interpolated_layer.get_projection(), geometry=interpolated_layer.get_geometry(), name=self.map_title(), keywords=impact_layer_keywords, style_info=style_info ) impact_layer.impact_data = impact_data self._impact = impact_layer return impact_layer