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
class ITBFatalityFunction(FunctionProvider): # noinspection PyUnresolvedReferences """Indonesian Earthquake Fatality Model. This model was developed by Institut Teknologi Bandung (ITB) and implemented by Dr. Hadi Ghasemi, Geoscience Australia. Reference: Indonesian Earthquake Building-Damage and Fatality Models and Post Disaster Survey Guidelines Development, Bali, 27-28 February 2012, 54pp. Algorithm: In this study, the same functional form as Allen (2009) is adopted to express fatality rate as a function of intensity (see Eq. 10 in the report). The Matlab built-in function (fminsearch) for Nelder-Mead algorithm was used to estimate the model parameters. The objective function (L2G norm) that is minimised during the optimisation is the same as the one used by Jaiswal et al. (2010). The coefficients used in the indonesian model are x=0.62275231, y=8.03314466, zeta=2.15 Allen, T. I., Wald, D. J., Earle, P. S., Marano, K. D., Hotovec, A. J., Lin, K., and Hearne, M., 2009. An Atlas of ShakeMaps and population exposure catalog for earthquake loss modeling, Bull. Earthq. Eng. 7, 701-718. Jaiswal, K., and Wald, D., 2010. An empirical model for global earthquake fatality estimation, Earthq. Spectra 26, 1017-1037. Caveats and limitations: The current model is the result of the above mentioned workshop and reflects the best available information. However, the current model has a number of issues listed below and is expected to evolve further over time. 1 - The model is based on limited number of observed fatality rates during 4 past fatal events. 2 - The model clearly over-predicts the fatality rates at intensities higher than VIII. 3 - The model only estimates the expected fatality rate for a given intensity level; however the associated uncertainty for the proposed model is not addressed. 4 - There are few known mistakes in developing the current model: - rounding MMI values to the nearest 0.5, - Implementing Finite-Fault models of candidate events, and - consistency between selected GMPEs with those in use by BMKG. These issues will be addressed by ITB team in the final report. Note: Because of these caveats, decisions should not be made solely on the information presented here and should always be verified by ground truthing and other reliable information sources. :author Hadi Ghasemi :rating 3 :param requires category=='hazard' and \ subcategory=='earthquake' and \ layertype=='raster' and \ unit=='MMI' :param requires category=='exposure' and \ subcategory=='population' and \ layertype=='raster' """ class Metadata(ImpactFunctionMetadata): """Metadata for ITB Fatality function. .. versionadded:: 2.1 We only need to re-implement get_metadata(), all other behaviours are inherited from the abstract base class. """ @staticmethod def get_metadata(): """Return metadata as a dictionary This is a static method. You can use it to get the metadata in dictionary format for an impact function. :returns: A dictionary representing all the metadata for the concrete impact function. :rtype: dict """ dict_meta = { 'id': 'ITBFatalityFunction', 'name': tr('ITB Fatality Function'), 'impact': tr('Die or be displaced'), 'author': 'Hadi Ghasemi', 'date_implemented': 'N/A', 'overview': tr('To assess the impact of earthquake on population based ' 'on earthquake model developed by ITB'), 'categories': { 'hazard': { 'definition': hazard_definition, 'subcategory': hazard_earthquake, 'units': [unit_mmi], 'layer_constraints': [layer_raster_numeric] }, 'exposure': { 'definition': exposure_definition, 'subcategory': exposure_population, 'units': [unit_people_per_pixel], 'layer_constraints': [layer_raster_numeric] } } } return dict_meta title = tr('Die or be displaced') synopsis = tr( 'To assess the impact of earthquake on population based on earthquake ' 'model developed by ITB') citations = tr( ' * Indonesian Earthquake Building-Damage and Fatality Models and ' ' Post Disaster Survey Guidelines Development Bali, 27-28 ' ' February 2012, 54pp.\n' ' * Allen, T. I., Wald, D. J., Earle, P. S., Marano, K. D., ' ' Hotovec, A. J., Lin, K., and Hearne, M., 2009. An Atlas ' ' of ShakeMaps and population exposure catalog for ' ' earthquake loss modeling, Bull. Earthq. Eng. 7, 701-718.\n' ' * Jaiswal, K., and Wald, D., 2010. An empirical model for ' ' global earthquake fatality estimation, Earthq. Spectra ' ' 26, 1017-1037.\n') limitation = tr( ' - The model is based on limited number of observed fatality ' ' rates during 4 past fatal events. \n' ' - The model clearly over-predicts the fatality rates at ' ' intensities higher than VIII.\n' ' - The model only estimates the expected fatality rate ' ' for a given intensity level; however the associated ' ' uncertainty for the proposed model is not addressed.\n' ' - There are few known mistakes in developing the current ' ' model:\n\n' ' * rounding MMI values to the nearest 0.5,\n' ' * Implementing Finite-Fault models of candidate events, and\n' ' * consistency between selected GMPEs with those in use by ' ' BMKG.\n') actions = tr( 'Provide details about the population will be die or displaced') detailed_description = tr( 'This model was developed by Institut Teknologi Bandung (ITB) ' 'and implemented by Dr. Hadi Ghasemi, Geoscience Australia\n' 'Algorithm:\n' 'In this study, the same functional form as Allen (2009) is ' 'adopted o express fatality rate as a function of intensity ' '(see Eq. 10 in the report). The Matlab built-in function ' '(fminsearch) for Nelder-Mead algorithm was used to estimate ' 'the model parameters. The objective function (L2G norm) that ' 'is minimized during the optimisation is the same as the one ' 'used by Jaiswal et al. (2010).\n' 'The coefficients used in the indonesian model are x=0.62275231, ' 'y=8.03314466, zeta=2.15') defaults = get_defaults() parameters = OrderedDict([ ('x', 0.62275231), ('y', 8.03314466), # Model coefficients # Rates of people displaced for each MMI level ('displacement_rate', { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1.0, 7: 1.0, 8: 1.0, 9: 1.0, 10: 1.0 }), ('mmi_range', range(2, 10)), ('step', 0.5), # Threshold below which layer should be transparent ('tolerance', 0.01), ('calculate_displaced_people', True), ('postprocessors', OrderedDict([ ('Gender', { 'on': True }), ('Age', { 'on': True, 'params': OrderedDict([('youth_ratio', defaults['YOUTH_RATIO']), ('adult_ratio', defaults['ADULT_RATIO']), ('elderly_ratio', defaults['ELDERLY_RATIO'])]) }), ('MinimumNeeds', { 'on': True }) ])), ('minimum needs', default_minimum_needs()), ('provenance', default_provenance()) ]) def fatality_rate(self, mmi): """ITB method to compute fatality rate. :param mmi: """ # As per email discussion with Ole, Trevor, Hadi, mmi < 4 will have # a fatality rate of 0 - Tim if mmi < 4: return 0 x = self.parameters['x'] y = self.parameters['y'] # noinspection PyUnresolvedReferences return numpy.power(10.0, x * mmi - y) def run(self, layers): """Indonesian Earthquake Fatality Model. Input: :param layers: List of layers expected to contain, hazard: Raster layer of MMI ground shaking exposure: Raster layer of population count """ displacement_rate = self.parameters['displacement_rate'] # Tolerance for transparency tolerance = self.parameters['tolerance'] # Extract input layers intensity = get_hazard_layer(layers) population = get_exposure_layer(layers) question = get_question(intensity.get_name(), population.get_name(), self) # Extract data grids hazard = intensity.get_data() # Ground Shaking exposure = population.get_data(scaling=True) # Population Density # Calculate people affected by each MMI level # FIXME (Ole): this range is 2-9. Should 10 be included? mmi_range = self.parameters['mmi_range'] number_of_exposed = {} number_of_displaced = {} number_of_fatalities = {} # Calculate fatality rates for observed Intensity values (hazard # based on ITB power model R = numpy.zeros(hazard.shape) for mmi in mmi_range: # Identify cells where MMI is in class i and # count people affected by this shake level I = numpy.where((hazard > mmi - self.parameters['step']) * (hazard <= mmi + self.parameters['step']), exposure, 0) # Calculate expected number of fatalities per level fatality_rate = self.fatality_rate(mmi) F = fatality_rate * I # Calculate expected number of displaced people per level try: D = displacement_rate[mmi] * I except KeyError, e: msg = 'mmi = %i, I = %s, Error msg: %s' % (mmi, str(I), str(e)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Adjust displaced people to disregard fatalities. # Set to zero if there are more fatalities than displaced. D = numpy.where(D > F, D - F, 0) # Sum up numbers for map R += D # Displaced # Generate text with result for this study # This is what is used in the real time system exposure table number_of_exposed[mmi] = numpy.nansum(I.flat) number_of_displaced[mmi] = numpy.nansum(D.flat) # noinspection PyUnresolvedReferences number_of_fatalities[mmi] = numpy.nansum(F.flat) # Set resulting layer to NaN when less than a threshold. This is to # achieve transparency (see issue #126). R[R < tolerance] = numpy.nan # Total statistics total, rounding = population_rounding_full(numpy.nansum(exposure.flat)) # Compute number of fatalities fatalities = population_rounding( numpy.nansum(number_of_fatalities.values())) # As per email discussion with Ole, Trevor, Hadi, total fatalities < 50 # will be rounded down to 0 - Tim if fatalities < 50: fatalities = 0 # Compute number of people displaced due to building collapse displaced = population_rounding( numpy.nansum(number_of_displaced.values())) # Generate impact report table_body = [question] # Add total fatality estimate s = format_int(fatalities) table_body.append( TableRow([tr('Number of fatalities'), s], header=True)) if self.parameters['calculate_displaced_people']: # Add total estimate of people displaced s = format_int(displaced) table_body.append( TableRow([tr('Number of people displaced'), s], header=True)) else: displaced = 0 # Add estimate of total population in area s = format_int(int(total)) table_body.append( TableRow([tr('Total number of people'), s], header=True)) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map table_body = [ question, TableRow([tr('Fatalities'), '%s' % format_int(fatalities)], header=True), TableRow([tr('People displaced'), '%s' % format_int(displaced)], header=True), TableRow(tr('Map shows the estimation of displaced population')) ] total_needs = evacuated_population_needs(displaced, minimum_needs) for frequency, needs in total_needs.items(): table_body.append( TableRow([ tr('Needs should be provided %s' % frequency), tr('Total') ], header=True)) for resource in needs: table_body.append( TableRow([ tr(resource['table name']), format_int(resource['amount']) ])) table_body.append(TableRow(tr('Provenance'), header=True)) table_body.append(TableRow(self.parameters['provenance'])) table_body.append(TableRow(tr('Action Checklist:'), header=True)) if fatalities > 0: table_body.append( tr('Are there enough victim identification ' 'units available for %s people?') % format_int(fatalities)) if displaced > 0: table_body.append( tr('Are there enough shelters and relief items ' 'available for %s people?') % format_int(displaced)) table_body.append( TableRow( tr('If yes, where are they located and ' 'how will we distribute them?'))) table_body.append( TableRow( tr('If no, where can we obtain ' 'additional relief items from and ' 'how will we transport them?'))) # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population: %s') % format_int(total), tr('People are considered to be displaced if ' 'they experience and survive a shake level' 'of more than 5 on the MMI scale '), tr('Minimum needs are defined in BNPB ' 'regulation 7/2008'), tr('The fatality calculation assumes that ' 'no fatalities occur for shake levels below 4 ' 'and fatality counts of less than 50 are ' 'disregarded.'), tr('All values are rounded up to the nearest ' 'integer in order to avoid representing human ' 'lives as fractions.') ]) table_body.append(TableRow(tr('Notes'), header=True)) table_body.append( tr('Fatality model is from ' 'Institute of Teknologi Bandung 2012.')) table_body.append( tr('Population numbers rounded up to the nearest %s.') % rounding) # Result impact_summary = Table(table_body).toNewlineFreeString() impact_table = impact_summary # check for zero impact if numpy.nanmax(R) == 0 == numpy.nanmin(R): table_body = [ question, TableRow([tr('Fatalities'), '%s' % format_int(fatalities)], header=True) ] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = ['#EEFFEE', '#FFFF7F', '#E15500', '#E4001B', '#730000'] classes = create_classes(R.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 30 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('Earthquake impact to population') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population Count') # Create raster object and return raster = Raster(R, projection=population.get_projection(), geotransform=population.get_geotransform(), keywords={ 'impact_summary': impact_summary, 'total_population': total, 'total_fatalities': fatalities, 'fatalities_per_mmi': number_of_fatalities, 'exposed_per_mmi': number_of_exposed, 'displaced_per_mmi': number_of_displaced, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'total_needs': total_needs }, name=tr('Estimated displaced population per cell'), style_info=style_info) return raster
def run(self, layers): """Risk plugin for volcano hazard on building/structure. Counts number of building exposed to each volcano hazard zones. :param layers: List of layers expected to contain. * my_hazard: Hazard layer of volcano * my_exposure: Vector layer of structure data on the same grid as my_hazard :returns: Map of building exposed to volcanic hazard zones. Table with number of buildings affected :rtype: dict """ # Identify hazard and exposure layers my_hazard = get_hazard_layer(layers) # Volcano hazard layer my_exposure = get_exposure_layer(layers) is_point_data = False question = get_question(my_hazard.get_name(), my_exposure.get_name(), self) # Input checks if not my_hazard.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % my_hazard.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s ' 'with layer type %s' % (my_hazard.get_name(), my_hazard.get_geometry_name())) if not (my_hazard.is_polygon_data or my_hazard.is_point_data): raise Exception(msg) if my_hazard.is_point_data: # Use concentric circles radii = self.parameters['distances [km]'] is_point_data = True centers = my_hazard.get_geometry() attributes = my_hazard.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters Z = make_circular_polygon(centers, rad_m, attributes=attributes) # To check category_title = 'Radius' my_hazard = Z category_names = rad_m name_attribute = 'NAME' # As in e.g. the Smithsonian dataset else: # Use hazard map category_title = 'KRB' # FIXME (Ole): Change to English and use translation system category_names = [ 'Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I' ] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map # Get names of volcanoes considered if name_attribute in my_hazard.get_attribute_names(): D = {} for att in my_hazard.get_data(): # Run through all polygons and get unique names D[att[name_attribute]] = None volcano_names = '' for name in D: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') if not category_title in my_hazard.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (my_hazard.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(my_hazard, my_exposure) # Initialise attributes of output dataset with all attributes # from input polygon and a building count of zero new_attributes = my_hazard.get_data() categories = {} for attr in new_attributes: attr[self.target_field] = 0 cat = attr[category_title] categories[cat] = 0 # Count impacted building per polygon and total for attr in P.get_data(): # Update building count for associated polygon poly_id = attr['polygon_id'] if poly_id is not None: new_attributes[poly_id][self.target_field] += 1 # Update building count for each category cat = new_attributes[poly_id][category_title] categories[cat] += 1 # Count totals total = len(my_exposure) # Generate simple impact report blank_cell = '' table_body = [ question, TableRow( [tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('Distance [km]'), tr('Total'), tr('Cumulative')], header=True) ] cum = 0 for name in category_names: # prevent key error count = categories.get(name, 0) cum += count if is_point_data: name = int(name) / 1000 table_body.append( TableRow([name, format_int(count), format_int(cum)])) table_body.append( TableRow( tr('Map shows buildings affected in ' 'each of volcano hazard polygons.'))) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total number of buildings %s in the viewable ' 'area') % format_int(total), tr('Only buildings available in OpenStreetMap ' 'are considered.') ]) impact_summary = Table(table_body).toNewlineFreeString() building_counts = [x[self.target_field] for x in new_attributes] if max(building_counts) == 0 == min(building_counts): table_body = [ question, TableRow([ tr('Number of buildings affected'), '%s' % format_int(cum), blank_cell ], header=True) ] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(building_counts, len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 0: transparency = 100 style_class['min'] = 0 else: transparency = 30 style_class['min'] = classes[i - 1] style_class['transparency'] = transparency style_class['colour'] = colours[i] style_class['max'] = classes[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='graduatedSymbol') # For printing map purpose map_title = tr('Buildings affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(building)') legend_title = tr('Building count') # Create vector layer and return V = Vector(data=new_attributes, projection=my_hazard.get_projection(), geometry=my_hazard.get_geometry(as_geometry_objects=True), name=tr('Buildings affected by volcanic hazard zone'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title }, style_info=style_info) return V
def run(self): """Experimental impact function for flood polygons on roads.""" self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # 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') 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) flooded_keyword = tr('Temporarily closed (m)') self.affected_road_categories = [flooded_keyword] self.affected_road_lengths = OrderedDict([(flooded_keyword, {})]) self.road_lengths = OrderedDict() for road in roads_data: attributes = road.attributes() road_type = attributes[road_type_field_index] if road_type.__class__.__name__ == 'QPyNullVariant': road_type = tr('Other') geom = road.geometry() geom.transform(transform) length = geom.length() if road_type not in self.road_lengths: self.affected_road_lengths[flooded_keyword][road_type] = 0 self.road_lengths[road_type] = 0 self.road_lengths[road_type] += length if attributes[target_field_index] == 1: self.affected_road_lengths[flooded_keyword][ road_type] += length impact_summary = self.html_report() # For printing map purpose map_title = tr('Roads inundated') legend_title = tr('Road inundated status') 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.')) extra_keywords = { 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': self.target_field } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) line_layer = Vector(data=line_layer, name=tr('Flooded roads'), keywords=impact_layer_keywords, style_info=style_info) self._impact = line_layer return line_layer
def run(self): """Risk plugin for flood population evacuation. Counts number of people exposed to flood levels exceeding specified threshold. :returns: Map of population exposed to flood levels exceeding the threshold. Table with number of people evacuated and supplies required. :rtype: tuple """ self.validate() self.prepare() # Determine depths above which people are regarded affected [m] # Use thresholds from inundation layer if specified thresholds = self.parameters['thresholds'].value verify( isinstance(thresholds, list), 'Expected thresholds to be a list. Got %s' % str(thresholds)) # Extract data as numeric arrays data = self.hazard.layer.get_data(nan=True) # Depth if has_no_data(data): self.no_data_warning = True # Calculate impact as population exposed to depths > max threshold population = self.exposure.layer.get_data(nan=True, scaling=True) total = int(numpy.nansum(population)) if has_no_data(population): self.no_data_warning = True # merely initialize impact = None for i, lo in enumerate(thresholds): if i == len(thresholds) - 1: # The last threshold thresholds_name = tr( 'People in >= %.1f m of water') % lo self.impact_category_ordering.append(thresholds_name) self._evacuation_category = thresholds_name impact = medium = numpy.where(data >= lo, population, 0) else: # Intermediate thresholds hi = thresholds[i + 1] thresholds_name = tr( 'People in %.1f m to %.1f m of water' % (lo, hi)) self.impact_category_ordering.append(thresholds_name) medium = numpy.where((data >= lo) * (data < hi), population, 0) # Count val = int(numpy.nansum(medium)) self.affected_population[thresholds_name] = val self.total_population = total self.unaffected_population = total - self.total_affected_population # Carry the no data values forward to the impact layer. impact = numpy.where(numpy.isnan(population), numpy.nan, impact) impact = numpy.where(numpy.isnan(data), numpy.nan, impact) # Count totals evacuated = self.total_evacuated self.minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Result impact_summary = self.html_report() impact_table = impact_summary total_needs = self.total_needs # check for zero impact if numpy.nanmax(impact) == 0 == numpy.nanmin(impact): message = no_population_impact_message(self.question) raise ZeroImpactException(message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes(impact.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() if i == 1: label = create_label(interval_classes[i], 'Low') elif i == 4: label = create_label(interval_classes[i], 'Medium') elif i == 7: label = create_label(interval_classes[i], 'High') else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 0 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose # For printing map purpose map_title = tr('People in need of evacuation') legend_title = tr('Population Count') legend_units = tr('(people per cell)') legend_notes = tr( 'Thousand separator is represented by %s' % get_thousand_separator()) # Create raster object and return raster = Raster( impact, projection=self.hazard.layer.get_projection(), geotransform=self.hazard.layer.get_geotransform(), name=tr('Population which %s') % ( self.impact_function_manager .get_function_title(self).lower()), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'evacuated': evacuated, 'total_needs': total_needs}, style_info=style_info) self._impact = raster return raster
def run(self, layers): """Risk plugin for flood population evacuation :param layers: List of layers expected to contain my_hazard: Raster layer of flood depth my_exposure: Raster layer of population data on the same grid as my_hazard Counts number of people exposed to flood levels exceeding specified threshold. :returns: Map of population exposed to flood levels exceeding the threshold. Table with number of people evacuated and supplies required. :rtype: tuple """ # Identify hazard and exposure layers my_hazard = get_hazard_layer(layers) # Flood inundation [m] my_exposure = get_exposure_layer(layers) question = get_question(my_hazard.get_name(), my_exposure.get_name(), self) # Determine depths above which people are regarded affected [m] # Use thresholds from inundation layer if specified thresholds = self.parameters['thresholds [m]'] verify(isinstance(thresholds, list), 'Expected thresholds to be a list. Got %s' % str(thresholds)) # Extract data as numeric arrays data = my_hazard.get_data(nan=0.0) # Depth # Calculate impact as population exposed to depths > max threshold population = my_exposure.get_data(nan=0.0, scaling=True) # Calculate impact to intermediate thresholds counts = [] # merely initialize impact = None for i, lo in enumerate(thresholds): if i == len(thresholds) - 1: # The last threshold impact = medium = numpy.where(data >= lo, population, 0) else: # Intermediate thresholds hi = thresholds[i + 1] medium = numpy.where((data >= lo) * (data < hi), population, 0) # Count val = int(numpy.sum(medium)) # Don't show digits less than a 1000 val = round_thousand(val) counts.append(val) # Count totals evacuated = counts[-1] total = int(numpy.sum(population)) # Don't show digits less than a 1000 total = round_thousand(total) # Calculate estimated minimum needs # The default value of each logistic is based on BNPB Perka 7/2008 # minimum bantuan minimum_needs = self.parameters['minimum needs'] tot_needs = evacuated_population_weekly_needs(evacuated, minimum_needs) # Generate impact report for the pdf map # noinspection PyListCreation table_body = [ question, TableRow([(tr('People in %.1f m of water') % thresholds[-1]), '%s%s' % (format_int(evacuated), ('*' if evacuated >= 1000 else ''))], header=True), TableRow(tr('* Number is rounded to the nearest 1000')), TableRow(tr('Map shows population density needing evacuation')), TableRow( tr('Table below shows the weekly minimum needs for all ' 'evacuated people')), TableRow([tr('Needs per week'), tr('Total')], header=True), [tr('Rice [kg]'), format_int(tot_needs['rice'])], [ tr('Drinking Water [l]'), format_int(tot_needs['drinking_water']) ], [tr('Clean Water [l]'), format_int(tot_needs['water'])], [tr('Family Kits'), format_int(tot_needs['family_kits'])], [tr('Toilets'), format_int(tot_needs['toilets'])] ] table_body.append(TableRow(tr('Action Checklist:'), header=True)) table_body.append(TableRow(tr('How will warnings be disseminated?'))) table_body.append(TableRow(tr('How will we reach stranded people?'))) table_body.append(TableRow(tr('Do we have enough relief items?'))) table_body.append( TableRow( tr('If yes, where are they located and how ' 'will we distribute them?'))) table_body.append( TableRow( tr('If no, where can we obtain additional relief items from and how ' 'will we transport them to here?'))) # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population: %s') % format_int(total), tr('People need evacuation if flood levels exceed %(eps).1f m') % { 'eps': thresholds[-1] }, tr('Minimum needs are defined in BNPB regulation 7/2008'), tr('All values are rounded up to the nearest integer in order to ' 'avoid representing human lives as fractions.') ]) if len(counts) > 1: table_body.append(TableRow(tr('Detailed breakdown'), header=True)) for i, val in enumerate(counts[:-1]): s = (tr('People in %(lo).1f m to %(hi).1f m of water: %(val)i') % { 'lo': thresholds[i], 'hi': thresholds[i + 1], 'val': format_int(val) }) table_body.append(TableRow(s)) # Result impact_summary = Table(table_body).toNewlineFreeString() impact_table = impact_summary # check for zero impact if numpy.nanmax(impact) == 0 == numpy.nanmin(impact): table_body = [ question, TableRow([(tr('People in %.1f m of water') % thresholds[-1]), '%s' % format_int(evacuated)], header=True) ] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(impact.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() if i == 1: label = create_label(interval_classes[i], 'Low') elif i == 4: label = create_label(interval_classes[i], 'Medium') elif i == 7: label = create_label(interval_classes[i], 'High') else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 0 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People in need of evacuation') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population density') # Create raster object and return raster = Raster(impact, projection=my_hazard.get_projection(), geotransform=my_hazard.get_geotransform(), name=tr('Population which %s') % (get_function_title(self).lower()), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'evacuated': evacuated, 'total_needs': tot_needs }, style_info=style_info) return raster
def run(self, layers): """Risk plugin for volcano population evacuation Input layers: List of layers expected to contain my_hazard: Vector polygon layer of volcano impact zones my_exposure: Raster layer of population data on the same grid as my_hazard Counts number of people exposed to volcano event. Return Map of population exposed to the volcano hazard zone. Table with number of people evacuated and supplies required. """ # Identify hazard and exposure layers my_hazard = get_hazard_layer(layers) # Volcano KRB my_exposure = get_exposure_layer(layers) question = get_question(my_hazard.get_name(), my_exposure.get_name(), self) # Input checks if not my_hazard.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % my_hazard.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (my_hazard.get_name(), my_hazard.get_geometry_name())) if not (my_hazard.is_polygon_data or my_hazard.is_point_data): raise Exception(msg) if my_hazard.is_point_data: # Use concentric circles radii = self.parameters['distance [km]'] centers = my_hazard.get_geometry() attributes = my_hazard.get_data() rad_m = [x * 1000 for x in radii] # Convert to meters my_hazard = make_circular_polygon(centers, rad_m, attributes=attributes) category_title = 'Radius' category_header = tr('Distance [km]') category_names = radii name_attribute = 'NAME' # As in e.g. the Smithsonian dataset else: # Use hazard map category_title = 'KRB' category_header = tr('Category') # FIXME (Ole): Change to English and use translation system category_names = [ 'Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I' ] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map attributes = my_hazard.get_data() # Get names of volcanos considered if name_attribute in my_hazard.get_attribute_names(): D = {} for att in my_hazard.get_data(): # Run through all polygons and get unique names D[att[name_attribute]] = None volcano_names = '' for name in D: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') if not category_title in my_hazard.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (my_hazard.get_name(), category_title)) raise InaSAFEError(msg) # Run interpolation function for polygon2raster P = assign_hazard_values_to_exposure_data(my_hazard, my_exposure, attribute_name='population') # Initialise attributes of output dataset with all attributes # from input polygon and a population count of zero new_attributes = my_hazard.get_data() categories = {} for attr in new_attributes: attr[self.target_field] = 0 cat = attr[category_title] categories[cat] = 0 # Count affected population per polygon and total evacuated = 0 for attr in P.get_data(): # Get population at this location pop = float(attr['population']) # Update population count for associated polygon poly_id = attr['polygon_id'] new_attributes[poly_id][self.target_field] += pop # Update population count for each category cat = new_attributes[poly_id][category_title] categories[cat] += pop # Count totals total = int(numpy.sum(my_exposure.get_data(nan=0))) # Don't show digits less than a 1000 total = round_thousand(total) # Count number and cumulative for each zone cum = 0 pops = {} cums = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name # prevent key error pop = int(categories.get(key, 0)) pop = round_thousand(pop) cum += pop cum = round_thousand(cum) pops[name] = pop cums[name] = cum # Use final accumulation as total number needing evac evacuated = cum # Calculate estimated needs based on BNPB Perka # 7/2008 minimum bantuan # FIXME (Ole): Refactor into one function to be shared rice = int(evacuated * 2.8) drinking_water = int(evacuated * 17.5) water = int(evacuated * 67) family_kits = int(evacuated / 5) toilets = int(evacuated / 20) # Generate impact report for the pdf map blank_cell = '' table_body = [ question, TableRow( [tr('Volcanos considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([ tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell ], header=True), TableRow( [category_header, tr('Total'), tr('Cumulative')], header=True) ] for name in category_names: table_body.append( TableRow( [name, format_int(pops[name]), format_int(cums[name])])) table_body.extend([ TableRow( tr('Map shows population affected in ' 'each of volcano hazard polygons.')), TableRow([tr('Needs per week'), tr('Total'), blank_cell], header=True), [tr('Rice [kg]'), format_int(rice), blank_cell], [tr('Drinking Water [l]'), format_int(drinking_water), blank_cell], [tr('Clean Water [l]'), format_int(water), blank_cell], [tr('Family Kits'), format_int(family_kits), blank_cell], [tr('Toilets'), format_int(toilets), blank_cell] ]) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int(total), tr('People need evacuation if they are within the ' 'volcanic hazard zones.') ]) population_counts = [x[self.target_field] for x in new_attributes] impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if numpy.nanmax(population_counts) == 0 == numpy.nanmin( population_counts): table_body = [ question, TableRow([ tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell ], header=True) ] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(population_counts, len(colours)) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 0: transparency = 100 style_class['min'] = 0 else: transparency = 30 style_class['min'] = classes[i - 1] style_class['transparency'] = transparency style_class['colour'] = colours[i] style_class['max'] = classes[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='graduatedSymbol') # For printing map purpose map_title = tr('People affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people)') legend_title = tr('Population count') # Create vector layer and return V = Vector(data=new_attributes, projection=my_hazard.get_projection(), geometry=my_hazard.get_geometry(as_geometry_objects=True), name=tr('Population affected by volcanic hazard zone'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title }, style_info=style_info) return V
def run(self, layers=None): """Plugin for impact of population as derived by categorised hazard. :param layers: List of layers expected to contain * hazard_layer: Raster layer of categorised hazard * exposure_layer: Raster layer of population data Counts number of people exposed to each category of the hazard :returns: Map of population exposed to high category Table with number of people in each category """ self.validate() self.prepare(layers) thresholds = self.parameters['Categorical thresholds'] # Thresholds must contain 3 thresholds if len(thresholds) != 3: raise FunctionParametersError( 'The thresholds must consist of 3 values.') # Thresholds must monotonically increasing monotonically_increasing_flag = all( x < y for x, y in zip(thresholds, thresholds[1:])) if not monotonically_increasing_flag: raise FunctionParametersError( 'Each threshold should be larger than the previous.') # The 3 categories low_t = thresholds[0] medium_t = thresholds[1] high_t = thresholds[2] # Identify hazard and exposure layers hazard_layer = self.hazard # Categorised Hazard exposure_layer = self.exposure # Population Raster # Extract data as numeric arrays hazard_data = hazard_layer.get_data(nan=True) # Category no_data_warning = False if has_no_data(hazard_data): no_data_warning = True # Calculate impact as population exposed to each category exposure_data = exposure_layer.get_data(nan=True, scaling=True) if has_no_data(exposure_data): no_data_warning = True # Make 3 data for each zone. Get the value of the exposure if the # exposure is in the hazard zone, else just assign 0 low_exposure = numpy.where(hazard_data < low_t, exposure_data, 0) medium_exposure = numpy.where( (hazard_data >= low_t) & (hazard_data < medium_t), exposure_data, 0) high_exposure = numpy.where( (hazard_data >= medium_t) & (hazard_data <= high_t), exposure_data, 0) impacted_exposure = low_exposure + medium_exposure + high_exposure # Count totals total = int(numpy.nansum(exposure_data)) low_total = int(numpy.nansum(low_exposure)) medium_total = int(numpy.nansum(medium_exposure)) high_total = int(numpy.nansum(high_exposure)) total_impact = high_total + medium_total + low_total # Check for zero impact if total_impact == 0: table_body = [ self.question, TableRow( [tr('People impacted'), '%s' % format_int(total_impact)], header=True) ] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Don't show digits less than a 1000 total = population_rounding(total) total_impact = population_rounding(total_impact) low_total = population_rounding(low_total) medium_total = population_rounding(medium_total) high_total = population_rounding(high_total) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] table_body = self._tabulate(high_total, low_total, medium_total, self.question, total_impact) impact_table = Table(table_body).toNewlineFreeString() table_body, total_needs = self._tabulate_notes(minimum_needs, table_body, total, total_impact, no_data_warning) impact_summary = Table(table_body).toNewlineFreeString() map_title = tr('People in each hazard areas (low, medium, high)') # Style for impact layer colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(impacted_exposure.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() if i == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 0 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # Create raster object and return raster_layer = Raster( data=impacted_exposure, projection=hazard_layer.get_projection(), geotransform=hazard_layer.get_geotransform(), name=tr('Population might %s') % (self.impact_function_manager.get_function_title(self).lower()), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'total_needs': total_needs }, style_info=style_info) self._impact = raster_layer return raster_layer
def run(self, layers=None): """Run volcano population evacuation Impact Function. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer of volcano impact zones * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ self.validate() self.prepare(layers) # Parameters hazard_zone_attribute = self.parameters['hazard zone attribute'] name_attribute = self.parameters['volcano name attribute'] # Identify hazard and exposure layers hazard_layer = self.hazard exposure_layer = self.exposure nan_warning = False if has_no_data(exposure_layer.get_data(nan=True)): nan_warning = True # Input checks if not hazard_layer.is_polygon_data: msg = ('Input hazard must be a polygon layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) raise Exception(msg) # Check if hazard_zone_attribute exists in hazard_layer if hazard_zone_attribute not in hazard_layer.get_attribute_names(): msg = ('Hazard data %s did not contain expected attribute %s ' % ( hazard_layer.get_name(), hazard_zone_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(msg) features = hazard_layer.get_data() category_header = tr('Category') hazard_zone_categories = list( set(hazard_layer.get_data(hazard_zone_attribute))) # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in features: volcano_name_list.append(row[name_attribute]) volcano_names = '' for hazard_zone in volcano_name_list: volcano_names += '%s, ' % hazard_zone volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field) # Initialise total affected per category affected_population = {} for hazard_zone in hazard_zone_categories: affected_population[hazard_zone] = 0 # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this category category = row[hazard_zone_attribute] affected_population[category] += population # Count totals total_population = population_rounding( int(numpy.nansum(exposure_layer.get_data()))) # Count number and cumulative for each zone total_affected_population = 0 cumulative_affected_population = {} for hazard_zone in hazard_zone_categories: population = int(affected_population.get(hazard_zone, 0)) total_affected_population += population cumulative_affected_population[hazard_zone] = \ total_affected_population minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map blank_cell = '' table_body = [ self.question, TableRow( [tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow( [tr('People needing evacuation'), '%s' % format_int( population_rounding(total_affected_population)), blank_cell], header=True), TableRow( [category_header, tr('Total'), tr('Cumulative')], header=True)] for hazard_zone in hazard_zone_categories: table_body.append( TableRow( [hazard_zone, format_int( population_rounding( affected_population[hazard_zone])), format_int( population_rounding( cumulative_affected_population[hazard_zone]))])) table_body.extend([ TableRow(tr( 'Map shows the number of people affected in each of volcano ' 'hazard polygons.'))]) total_needs = evacuated_population_needs( total_affected_population, minimum_needs) for frequency, needs in total_needs.items(): table_body.append(TableRow( [ tr('Needs should be provided %s' % frequency), tr('Total') ], header=True)) for resource in needs: table_body.append(TableRow([ tr(resource['table name']), format_int(resource['amount'])])) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int( total_population), tr('People need evacuation if they are within the ' 'volcanic hazard zones.')]) if nan_warning: table_body.extend([ tr('The population layer contained `no data`. This missing ' 'data was carried through to the impact layer.'), tr('`No data` values in the impact layer were treated as 0 ' 'when counting the affected or total population.') ]) impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if total_affected_population == 0: table_body = [ self.question, TableRow( [tr('People needing evacuation'), '%s' % format_int(total_affected_population), blank_cell], header=True)] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes( covered_exposure_layer.get_data().flat[:], len(colours)) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People affected by volcanic hazard zone'), keywords={'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'total_needs': total_needs}, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self): """Experimental impact function.""" self.validate() self.prepare() # 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') # 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 lines from E and extent 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( QgsCoordinateReferenceSystem('EPSG:%i' % self._requested_extent_crs), self.hazard.layer.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) features = [] for feature in self.exposure.layer.getFeatures(request): building_geom = feature.geometry() affected = False # get tentative list of intersecting hazard features # only based on intersection of bounding boxes ids = hazard_index.intersects(building_geom.boundingBox()) for fid in ids: # run (slow) exact intersection test if hazard_geometries[fid].intersects(building_geom): affected = True break f = QgsFeature() f.setGeometry(building_geom) f.setAttributes(feature.attributes()) f[target_field_index] = 1 if affected else 0 features.append(f) # 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 self.buildings = {} self.affected_buildings = OrderedDict([(tr('Flooded'), {})]) buildings_data = building_layer.getFeatures() building_type_field_index = building_layer.fieldNameIndex( self.exposure_class_attribute) for building in buildings_data: record = building.attributes() building_type = record[building_type_field_index] if building_type in [None, 'NULL', 'null', 'Null']: building_type = 'Unknown type' if building_type not in self.buildings: self.buildings[building_type] = 0 for category in self.affected_buildings.keys(): self.affected_buildings[category][ building_type] = OrderedDict([ (tr('Buildings Affected'), 0) ]) self.buildings[building_type] += 1 if record[target_field_index] == 1: self.affected_buildings[tr('Flooded')][building_type][tr( 'Buildings Affected')] += 1 # Lump small entries and 'unknown' into 'other' category self._consolidate_to_other() impact_summary = self.html_report() # For printing map purpose map_title = tr('Buildings inundated') legend_title = tr('Structure inundated status') 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.')) building_layer = Vector(data=building_layer, name=tr('Flooded buildings'), keywords={ 'impact_summary': impact_summary, 'map_title': map_title, 'legend_title': legend_title, 'target_field': self.target_field, 'buildings_total': self.total_buildings, 'buildings_affected': self.total_affected_buildings }, style_info=style_info) self._impact = building_layer return building_layer
def run(self, layers): """Risk plugin for volcano population evacuation. :param layers: List of layers expected to contain where two layers should be present. * hazard_layer: Vector polygon layer of volcano impact zones * exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to volcano event. :returns: Map of population exposed to the volcano hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer * RadiiException - When radii are not valid (they need to be monotonically increasing) """ # Identify hazard and exposure layers hazard_layer = get_hazard_layer(layers) # Volcano KRB exposure_layer = get_exposure_layer(layers) question = get_question( hazard_layer.get_name(), exposure_layer.get_name(), self) # Input checks if not hazard_layer.is_vector: msg = ('Input hazard %s was not a vector layer as expected ' % hazard_layer.get_name()) raise Exception(msg) msg = ('Input hazard must be a polygon or point layer. I got %s with ' 'layer type %s' % (hazard_layer.get_name(), hazard_layer.get_geometry_name())) if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data): raise Exception(msg) data_table = hazard_layer.get_data() if hazard_layer.is_point_data: # Use concentric circles radii = self.parameters['distance [km]'] category_title = 'Radius' category_header = tr('Distance [km]') category_names = radii name_attribute = 'NAME' # As in e.g. the Smithsonian dataset centers = hazard_layer.get_geometry() rad_m = [x * 1000 for x in radii] # Convert to meters hazard_layer = buffer_points( centers, rad_m, category_title, data_table=data_table) else: # Use hazard map category_title = 'KRB' category_header = tr('Category') # FIXME (Ole): Change to English and use translation system category_names = ['Kawasan Rawan Bencana III', 'Kawasan Rawan Bencana II', 'Kawasan Rawan Bencana I'] name_attribute = 'GUNUNG' # As in e.g. BNPB hazard map # Get names of volcanoes considered if name_attribute in hazard_layer.get_attribute_names(): volcano_name_list = [] # Run through all polygons and get unique names for row in data_table: volcano_name_list.append(row[name_attribute]) volcano_names = '' for name in volcano_name_list: volcano_names += '%s, ' % name volcano_names = volcano_names[:-2] # Strip trailing ', ' else: volcano_names = tr('Not specified in data') # Check if category_title exists in hazard_layer if category_title not in hazard_layer.get_attribute_names(): msg = ('Hazard data %s did not contain expected ' 'attribute %s ' % (hazard_layer.get_name(), category_title)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Find the target field name that has no conflict with default target attribute_names = hazard_layer.get_attribute_names() new_target_field = get_non_conflicting_attribute_name( self.target_field, attribute_names) self.target_field = new_target_field # Run interpolation function for polygon2raster interpolated_layer = assign_hazard_values_to_exposure_data( hazard_layer, exposure_layer, attribute_name=self.target_field) # Initialise data_table of output dataset with all data_table # from input polygon and a population count of zero new_data_table = hazard_layer.get_data() categories = {} for row in new_data_table: row[self.target_field] = 0 category = row[category_title] categories[category] = 0 # Count affected population per polygon and total for row in interpolated_layer.get_data(): # Get population at this location population = float(row[self.target_field]) # Update population count for associated polygon poly_id = row['polygon_id'] new_data_table[poly_id][self.target_field] += population # Update population count for each category category = new_data_table[poly_id][category_title] categories[category] += population # Count totals total_population = population_rounding( int(numpy.sum(exposure_layer.get_data(nan=0)))) # Count number and cumulative for each zone cumulative = 0 all_categories_population = {} all_categories_cumulative = {} for name in category_names: if category_title == 'Radius': key = name * 1000 # Convert to meters else: key = name # prevent key error population = int(categories.get(key, 0)) cumulative += population # I'm not sure whether this is the best place to apply rounding? all_categories_population[name] = population_rounding(population) all_categories_cumulative[name] = population_rounding(cumulative) # Use final accumulation as total number needing evacuation evacuated = population_rounding(cumulative) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map blank_cell = '' table_body = [question, TableRow([tr('Volcanoes considered'), '%s' % volcano_names, blank_cell], header=True), TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True), TableRow([category_header, tr('Total'), tr('Cumulative')], header=True)] for name in category_names: table_body.append( TableRow([name, format_int(all_categories_population[name]), format_int(all_categories_cumulative[name])])) table_body.extend([ TableRow(tr( 'Map shows the number of people affected in each of volcano ' 'hazard polygons.'))]) total_needs = evacuated_population_needs( evacuated, minimum_needs) for frequency, needs in total_needs.items(): table_body.append(TableRow( [ tr('Needs should be provided %s' % frequency), tr('Total') ], header=True)) for resource in needs: table_body.append(TableRow([ tr(resource['table name']), format_int(resource['amount'])])) impact_table = Table(table_body).toNewlineFreeString() # Extend impact report for on-screen display table_body.extend( [TableRow(tr('Notes'), header=True), tr('Total population %s in the exposure layer') % format_int( total_population), tr('People need evacuation if they are within the ' 'volcanic hazard zones.')]) population_counts = [x[self.target_field] for x in new_data_table] impact_summary = Table(table_body).toNewlineFreeString() # check for zero impact if numpy.nanmax(population_counts) == 0 == numpy.nanmin( population_counts): table_body = [ question, TableRow([tr('People needing evacuation'), '%s' % format_int(evacuated), blank_cell], header=True)] my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes(population_counts, len(colours)) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 0: transparency = 100 style_class['min'] = 0 else: transparency = 30 style_class['min'] = classes[i - 1] style_class['transparency'] = transparency style_class['colour'] = colours[i] style_class['max'] = classes[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='graduatedSymbol') # For printing map purpose map_title = tr('People affected by volcanic hazard zone') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population') # Create vector layer and return impact_layer = Vector( data=new_data_table, projection=hazard_layer.get_projection(), geometry=hazard_layer.get_geometry(as_geometry_objects=True), name=tr('People affected by volcanic hazard zone'), keywords={'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'total_needs': total_needs}, style_info=style_info) 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): """Earthquake impact to buildings (e.g. from OpenStreetMap).""" self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') 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) # Extract relevant exposure data # Try to get the value from keyword, if not exist, it will not fail, # but use the old get_osm_building_usage try: structure_class_field = self.exposure.keyword( 'structure_class_field') except KeywordNotFoundError: structure_class_field = None attributes = interpolate_result.get_data() interpolate_size = len(interpolate_result) # Building breakdown self.buildings = {} # Impacted building breakdown self.affected_buildings = OrderedDict([ (tr('High'), {}), (tr('Medium'), {}), (tr('Low'), {}), ]) 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 if (structure_class_field in attribute_names and structure_class_field): usage = attributes[i].get(structure_class_field, None) else: usage = get_osm_building_usage(attribute_names, attributes[i]) if usage is None or usage == 0: usage = 'unknown' 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 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 # 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() # 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() impact_table = impact_summary = self.html_report() # 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') # For printing map purpose map_title = tr('Building affected by earthquake') legend_notes = tr( 'The level of the impact is according to the threshold the user ' 'input.') legend_units = tr('(mmi)') legend_title = tr('Impact level') extra_keywords = { 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'target_field': self.target_field, } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return result_layer = Vector(data=attributes, projection=interpolate_result.get_projection(), geometry=geometry, name=tr('Estimated buildings affected'), keywords=impact_layer_keywords, style_info=style_info) msg = 'Created vector layer %s' % str(result_layer) LOGGER.debug(msg) self._impact = result_layer return result_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): """Plugin for impact of population as derived by continuous hazard. Hazard is reclassified into 3 classes based on the extrema provided as impact function parameters. Counts number of people exposed to each category of the hazard :returns: Map of population exposed to high category Table with number of people in each category """ self.validate() self.prepare() thresholds = [ p.value for p in self.parameters['Categorical thresholds'].value ] # Thresholds must contain 3 thresholds if len(thresholds) != 3: raise FunctionParametersError( 'The thresholds must consist of 3 values.') # Thresholds must monotonically increasing monotonically_increasing_flag = all( x < y for x, y in zip(thresholds, thresholds[1:])) if not monotonically_increasing_flag: raise FunctionParametersError( 'Each threshold should be larger than the previous.') # The 3 categories low_t = thresholds[0] medium_t = thresholds[1] high_t = thresholds[2] # Extract data as numeric arrays hazard_data = self.hazard.layer.get_data(nan=True) # Category if has_no_data(hazard_data): self.no_data_warning = True # Calculate impact as population exposed to each category exposure_data = self.exposure.layer.get_data(nan=True, scaling=True) if has_no_data(exposure_data): self.no_data_warning = True # Make 3 data for each zone. Get the value of the exposure if the # exposure is in the hazard zone, else just assign 0 low_exposure = numpy.where(hazard_data < low_t, exposure_data, 0) medium_exposure = numpy.where( (hazard_data >= low_t) & (hazard_data < medium_t), exposure_data, 0) high_exposure = numpy.where( (hazard_data >= medium_t) & (hazard_data <= high_t), exposure_data, 0) impacted_exposure = low_exposure + medium_exposure + high_exposure # Count totals self.total_population = int(numpy.nansum(exposure_data)) self.affected_population[tr('Population in high hazard areas')] = int( numpy.nansum(high_exposure)) self.affected_population[tr( 'Population in medium hazard areas')] = int( numpy.nansum(medium_exposure)) self.affected_population[tr('Population in low hazard areas')] = int( numpy.nansum(low_exposure)) self.unaffected_population = (self.total_population - self.total_affected_population) # check for zero impact if self.total_affected_population == 0: message = no_population_impact_message(self.question) raise ZeroImpactException(message) # Don't show digits less than a 1000 self.minimum_needs = [ parameter.serialize() for parameter in filter_needs_parameters( self.parameters['minimum needs']) ] total_needs = self.total_needs impact_table = impact_summary = self.html_report() # Style for impact layer colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(impacted_exposure.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() if i == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 0 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People in each hazard areas (low, medium, high)') legend_title = tr('Number of People') legend_units = tr('(people per cell)') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) # Create raster object and return raster_layer = Raster( data=impacted_exposure, projection=self.hazard.layer.get_projection(), geotransform=self.hazard.layer.get_geotransform(), name=tr('Population might %s') % (self.impact_function_manager.get_function_title(self).lower()), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'total_needs': total_needs }, style_info=style_info) self._impact = raster_layer return raster_layer
def run(self): """Risk plugin for flood population evacuation. Counts number of people exposed to areas identified as flood prone :returns: Map of population exposed to flooding Table with number of people evacuated and supplies required. :rtype: tuple """ # 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 population.')) # Get the IF parameters self._evacuation_percentage = ( self.parameters['evacuation_percentage'].value) # Check that hazard is polygon type if not self.hazard.layer.is_polygon_data: message = ( 'Input hazard must be a polygon layer. I got %s with layer ' 'type %s' % ( self.hazard.name, self.hazard.layer.get_geometry_name())) raise Exception(message) if has_no_data(self.exposure.layer.get_data(nan=True)): self.no_data_warning = True # Check that affected field exists in hazard layer if (self.hazard_class_attribute in self.hazard.layer.get_attribute_names()): self.use_affected_field = True # Run interpolation function for polygon2raster interpolated_layer, covered_exposure = \ assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field) # Data for manipulating the covered_exposure layer new_covered_exposure_data = covered_exposure.get_data() covered_exposure_top_left = numpy.array([ covered_exposure.get_geotransform()[0], covered_exposure.get_geotransform()[3]]) covered_exposure_dimension = numpy.array([ covered_exposure.get_geotransform()[1], covered_exposure.get_geotransform()[5]]) # Count affected population per polygon, per category and total total_affected_population = 0 for attr in interpolated_layer.get_data(): affected = False if self.use_affected_field: row_affected_value = attr[self.hazard_class_attribute] if row_affected_value is not None: affected = get_key_for_value( row_affected_value, self.hazard_class_mapping) else: # assume that every polygon is affected (see #816) affected = self.wet if affected == self.wet: # Get population at this location population = attr[self.target_field] if not numpy.isnan(population): population = float(population) total_affected_population += population else: # If it's not affected, set the value of the impact layer to 0 grid_point = attr['grid_point'] index = numpy.floor( (grid_point - covered_exposure_top_left) / ( covered_exposure_dimension)).astype(int) new_covered_exposure_data[index[1]][index[0]] = 0 # Estimate number of people in need of evacuation if self.use_affected_field: affected_population = tr( 'People within hazard field ("%s") of value "%s"') % ( self.hazard_class_attribute, ','.join([ unicode(hazard_class) for hazard_class in self.hazard_class_mapping[self.wet] ])) else: affected_population = tr('People within any hazard polygon.') self.affected_population[affected_population] = ( total_affected_population) self.total_population = int( numpy.nansum(self.exposure.layer.get_data(scaling=False))) self.unaffected_population = ( self.total_population - self.total_affected_population) self.minimum_needs = [ parameter.serialize() for parameter in filter_needs_parameters(self.parameters['minimum needs']) ] # Create style colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes( new_covered_exposure_data.flat[:], len(colours)) # check for zero impact if total_affected_population == 0: message = no_population_impact_message(self.question) raise ZeroImpactException(message) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = 0 style_classes.append(style_class) # Override style info with new classes and name style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') 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'), 'affected_population': total_affected_population, 'total_population': self.total_population, 'total_needs': self.total_needs } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create raster layer and return impact_layer = Raster( data=new_covered_exposure_data, projection=covered_exposure.get_projection(), geotransform=covered_exposure.get_geotransform(), 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 classified population evacuation Impact Function. Counts number of people exposed to each hazard zones. :returns: Map of population exposed to each hazard zone. The returned dict will include a table with number of people evacuated and supplies required. :rtype: dict :raises: * Exception - When hazard layer is not vector layer """ self.validate() self.prepare() # Value from layer's keywords self.hazard_class_attribute = self.hazard.keyword('field') # Input checks msg = ('Input hazard must be a polygon layer. I got %s with ' 'layer type %s' % (self.hazard.name, self.hazard.layer.get_geometry_name())) if not self.hazard.layer.is_polygon_data: raise Exception(msg) # Check if hazard_class_attribute exists in hazard_layer if (self.hazard_class_attribute not in self.hazard.layer.get_attribute_names()): msg = ('Hazard data %s does not contain expected hazard ' 'zone attribute "%s". Please change it in the option. ' % (self.hazard.name, self.hazard_class_attribute)) # noinspection PyExceptionInherit raise InaSAFEError(msg) # Get unique hazard zones from the layer attribute self.hazard_zones = list( set(self.hazard.layer.get_data(self.hazard_class_attribute))) # Interpolated layer represents grid cell that lies in the polygon interpolated_layer, covered_exposure_layer = \ assign_hazard_values_to_exposure_data( self.hazard.layer, self.exposure.layer, attribute_name=self.target_field ) # Initialise total population affected by each hazard zone for hazard_zone in self.hazard_zones: self.affected_population[hazard_zone] = 0 # Count total affected population per hazard zone for row in interpolated_layer.get_data(): # Get population at this location population = row[self.target_field] if not numpy.isnan(population): population = float(population) # Update population count for this hazard zone hazard_zone = row[self.hazard_class_attribute] self.affected_population[hazard_zone] += population # Count total population from exposure layer self.total_population = int( numpy.nansum(self.exposure.layer.get_data())) # Count total affected population total_affected_population = self.total_affected_population self.unaffected_population = (self.total_population - total_affected_population) self.minimum_needs = [ parameter.serialize() for parameter in filter_needs_parameters( self.parameters['minimum needs']) ] # check for zero impact if total_affected_population == 0: message = no_population_impact_message(self.question) raise ZeroImpactException(message) impact_table = impact_summary = self.html_report() # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(covered_exposure_layer.get_data().flat[:], len(colours)) interval_classes = humanize_class(classes) # Define style info for output polygons showing population counts style_classes = [] for i in xrange(len(colours)): style_class = dict() style_class['label'] = create_label(interval_classes[i]) if i == 1: label = create_label( interval_classes[i], tr('Low Population [%i people/cell]' % classes[i])) elif i == 4: label = create_label( interval_classes[i], tr('Medium Population [%i people/cell]' % classes[i])) elif i == 7: label = create_label( interval_classes[i], tr('High Population [%i people/cell]' % classes[i])) else: label = create_label(interval_classes[i]) if i == 0: transparency = 100 else: transparency = 0 style_class['label'] = label style_class['quantity'] = classes[i] style_class['colour'] = colours[i] style_class['transparency'] = transparency style_classes.append(style_class) # Override style info with new classes and name style_info = dict(target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People impacted by each hazard zone') legend_title = tr('Population') legend_units = tr('(people per cell)') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) # Create vector layer and return impact_layer = Raster( data=covered_exposure_layer.get_data(), projection=covered_exposure_layer.get_projection(), geotransform=covered_exposure_layer.get_geotransform(), name=tr('People impacted by each hazard zone'), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'target_field': self.target_field, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title }, style_info=style_info) self._impact = impact_layer return impact_layer
def run(self): """Run the impact function. :returns: A vector layer with affected areas marked. :type: safe_layer """ hazard_layer = self.hazard.layer exposure = self.exposure.layer # Thresholds for tsunami hazard zone breakdown. group_parameters = self.parameters['group_threshold'] ver_low_unit = group_parameters.value_map['very_low_threshold'].unit unit_abbrev = ver_low_unit.abbreviation unaffected_threshold = group_parameters.value_map[ 'unaffected_threshold'] unaffected_max = 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 ranges = ranges_according_thresholds_list( [None, unaffected_max, very_low_max, low_max, medium_max, high_max, None]) hazard_value_to_class = {} for i, interval in enumerate(ranges): hazard_value_to_class[interval] = self.hazard_classes[i] # Get parameters from layer's keywords class_field = self.exposure.keyword('field') # reproject self.extent to the hazard projection hazard_crs = 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) small_raster = align_clip_raster(hazard_layer, viewport_extent) # Create vector features from the flood raster hazard_class_attribute = 'hazard' vector_file_path = reclassify_polygonize( small_raster.source(), ranges, name_field=hazard_class_attribute) hazard = QgsVectorLayer(vector_file_path, 'ash vector', 'ogr') # prepare objects for re-projection of geometries crs_wgs84 = QgsCoordinateReferenceSystem('EPSG:4326') hazard_to_exposure = QgsCoordinateTransform( hazard.crs(), exposure.crs()) wgs84_to_hazard = QgsCoordinateTransform( crs_wgs84, hazard.crs()) wgs84_to_exposure = QgsCoordinateTransform( crs_wgs84, exposure.crs()) extent = QgsRectangle( self.requested_extent[0], self.requested_extent[1], self.requested_extent[2], self.requested_extent[3]) extent_hazard = wgs84_to_hazard.transformBoundingBox(extent) extent_exposure = wgs84_to_exposure.transformBoundingBox(extent) extent_exposure_geom = QgsGeometry.fromRect(extent_exposure) # make spatial index of hazard hazard_index = QgsSpatialIndex() hazard_features = {} for f in hazard.getFeatures(QgsFeatureRequest(extent_hazard)): f.geometry().transform(hazard_to_exposure) hazard_index.insertFeature(f) hazard_features[f.id()] = QgsFeature(f) # create impact layer filename = unique_filename(suffix='.shp') impact_fields = exposure.dataProvider().fields() impact_fields.append(QgsField(self.target_field, QVariant.String)) writer = QgsVectorFileWriter( filename, 'utf-8', impact_fields, QGis.WKBPolygon, exposure.crs()) # iterate over all exposure polygons and calculate the impact _calculate_landcover_impact( exposure, extent_exposure, extent_exposure_geom, hazard_class_attribute, hazard_features, hazard_index, hazard_value_to_class, impact_fields, writer) del writer impact_layer = QgsVectorLayer(filename, 'Impacted Land Cover', 'ogr') # find unaffected features unaffected_feats = [] target_field_index = impact_layer.fieldNameIndex(self.target_field) for f in impact_layer.getFeatures(): haz_class = f.attributes()[target_field_index] if haz_class == self.hazard_classes[0]: unaffected_feats.append(f.id()) impact_layer.dataProvider().deleteFeatures(unaffected_feats) if impact_layer.featureCount() == 0: raise ZeroImpactException() zone_field = None if self.aggregator: zone_field = self.aggregator.exposure_aggregation_field impact_data = LandCoverReportMixin( question=self.question, impact_layer=impact_layer, target_field=self.target_field, ordered_columns=self.hazard_classes, affected_columns=self.affected_hazard_columns, land_cover_field=class_field, zone_field=zone_field ).generate_data() # Define style for the impact layer style_classes = [ dict( label=self.hazard_classes[1] + ': %.1f - %.1f %s' % ( unaffected_max, very_low_max, unit_abbrev), value=self.hazard_classes[1], colour='#2C6BA4', border_color='#000000', transparency=0), dict( label=self.hazard_classes[2] + ': %.1f - %.1f %s' % ( very_low_max + 0.1, low_max, unit_abbrev), value=self.hazard_classes[2], colour='#00A4D8', border_color='#000000', transparency=0), dict( label=self.hazard_classes[3] + ': %.1f - %.1f %s' % ( low_max + 0.1, medium_max, unit_abbrev), value=self.hazard_classes[3], colour='#FFEF36', border_color='#000000', transparency=0), dict( label=self.hazard_classes[4] + ': %.1f - %.1f %s' % ( medium_max + 0.1, high_max, unit_abbrev), value=self.hazard_classes[4], colour='#EFA951', border_color='#000000', transparency=0), dict( label=self.hazard_classes[5] + ': > %.1f %s' % ( high_max, unit_abbrev), value=self.hazard_classes[5], colour='#d62631', border_color='#000000', transparency=0), ] style_info = dict( target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') extra_keywords = { 'map_title': self.map_title(), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector( data=impact_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): """Risk plugin for classified polygon hazard on land cover. Counts area of land cover types exposed to hazard zones. :returns: Impact layer :rtype: Vector """ # Identify hazard and exposure layers hazard = self.hazard.layer exposure = self.exposure.layer type_attr = self.exposure.keyword('field') self.hazard_class_attribute = self.hazard.keyword('field') hazard_value_to_class = {} self.hazard_class_mapping = self.hazard.keyword('value_map') for key, values in self.hazard_class_mapping.items(): for value in values: hazard_value_to_class[value] = self.hazard_columns[key] # prepare objects for re-projection of geometries crs_wgs84 = QgsCoordinateReferenceSystem('EPSG:4326') hazard_to_exposure = QgsCoordinateTransform(hazard.crs(), exposure.crs()) wgs84_to_hazard = QgsCoordinateTransform(crs_wgs84, hazard.crs()) wgs84_to_exposure = QgsCoordinateTransform(crs_wgs84, exposure.crs()) extent = QgsRectangle(self.requested_extent[0], self.requested_extent[1], self.requested_extent[2], self.requested_extent[3]) extent_hazard = wgs84_to_hazard.transformBoundingBox(extent) extent_exposure = wgs84_to_exposure.transformBoundingBox(extent) extent_exposure_geom = QgsGeometry.fromRect(extent_exposure) # make spatial index of hazard hazard_index = QgsSpatialIndex() hazard_features = {} for f in hazard.getFeatures(QgsFeatureRequest(extent_hazard)): f.geometry().transform(hazard_to_exposure) hazard_index.insertFeature(f) hazard_features[f.id()] = QgsFeature(f) # create impact layer filename = unique_filename(suffix='.shp') impact_fields = exposure.dataProvider().fields() impact_fields.append(QgsField(self.target_field, QVariant.String)) writer = QgsVectorFileWriter(filename, 'utf-8', impact_fields, QGis.WKBPolygon, exposure.crs()) # Iterate over all exposure polygons and calculate the impact. _calculate_landcover_impact(exposure, extent_exposure, extent_exposure_geom, self.hazard_class_attribute, hazard_features, hazard_index, hazard_value_to_class, impact_fields, writer) del writer impact_layer = QgsVectorLayer(filename, 'Impacted Land Cover', 'ogr') if impact_layer.featureCount() == 0: raise ZeroImpactException() zone_field = None if self.aggregator: zone_field = self.aggregator.exposure_aggregation_field # This is not the standard way to use mixins # Martin preferred to call it directly - normally it is called with # multiple inheritance. Thats ok but we need to monkey patch the # notes function as it is not overloaded by this class mixin = LandCoverReportMixin( question=self.question, impact_layer=impact_layer, target_field=self.target_field, ordered_columns=self.hazard_columns.values(), affected_columns=self.affected_hazard_columns, land_cover_field=type_attr, zone_field=zone_field) mixin.notes = self.notes impact_data = mixin.generate_data() # Define style for the impact layer style_classes = [ dict(label=self.hazard_columns['low'], value=self.hazard_columns['low'], colour='#acffb6', border_color='#000000', transparency=0, size=0.5), dict(label=self.hazard_columns['medium'], value=self.hazard_columns['medium'], colour='#ffe691', border_color='#000000', transparency=0, size=0.5), dict(label=self.hazard_columns['high'], value=self.hazard_columns['high'], colour='#F31A1C', border_color='#000000', transparency=0, size=0.5), ] style_info = dict(target_field=self.target_field, style_classes=style_classes, style_type='categorizedSymbol') extra_keywords = { 'map_title': self.map_title(), 'target_field': self.target_field } impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create vector layer and return impact_layer = Vector(data=impact_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, layers=None): """Risk plugin for flood population evacuation. :param layers: List of layers expected to contain hazard_layer: Raster layer of flood depth exposure_layer: Raster layer of population data on the same grid as hazard_layer Counts number of people exposed to flood levels exceeding specified threshold. :returns: Map of population exposed to flood levels exceeding the threshold. Table with number of people evacuated and supplies required. :rtype: tuple """ self.validate() self.prepare(layers) # Identify hazard and exposure layers hazard_layer = self.hazard # Flood inundation exposure_layer = self.exposure # Determine depths above which people are regarded affected [m] # Use thresholds from inundation layer if specified thresholds = self.parameters['thresholds [m]'] verify( isinstance(thresholds, list), 'Expected thresholds to be a list. Got %s' % str(thresholds)) # Extract data as numeric arrays data = hazard_layer.get_data(nan=True) # Depth no_data_warning = False if has_no_data(data): no_data_warning = True # Calculate impact as population exposed to depths > max threshold population = exposure_layer.get_data(nan=True, scaling=True) if has_no_data(population): no_data_warning = True # Calculate impact to intermediate thresholds counts = [] # merely initialize impact = None for i, lo in enumerate(thresholds): if i == len(thresholds) - 1: # The last threshold impact = medium = numpy.where(data >= lo, population, 0) else: # Intermediate thresholds hi = thresholds[i + 1] medium = numpy.where((data >= lo) * (data < hi), population, 0) # Count val = int(numpy.nansum(medium)) counts.append(val) # Carry the no data values forward to the impact layer. impact = numpy.where(numpy.isnan(population), numpy.nan, impact) impact = numpy.where(numpy.isnan(data), numpy.nan, impact) # Count totals evacuated, rounding_evacuated = population_rounding_full(counts[-1]) total = int(numpy.nansum(population)) # Don't show digits less than a 1000 total = population_rounding(total) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] # Generate impact report for the pdf map # noinspection PyListCreation table_body, total_needs = self._tabulate( counts, evacuated, minimum_needs, self.question, rounding_evacuated, thresholds, total, no_data_warning) # Result impact_summary = Table(table_body).toNewlineFreeString() impact_table = impact_summary # check for zero impact if numpy.nanmax(impact) == 0 == numpy.nanmin(impact): table_body = self._tabulate_zero_impact( evacuated, self.question, table_body, thresholds) my_message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(my_message) # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000'] classes = create_classes(impact.flat[:], len(colours)) interval_classes = humanize_class(classes) style_classes = [] for i in xrange(len(colours)): style_class = dict() if i == 1: label = create_label(interval_classes[i], 'Low') elif i == 4: label = create_label(interval_classes[i], 'Medium') elif i == 7: label = create_label(interval_classes[i], 'High') else: label = create_label(interval_classes[i]) style_class['label'] = label style_class['quantity'] = classes[i] if i == 0: transparency = 100 else: transparency = 0 style_class['transparency'] = transparency style_class['colour'] = colours[i] style_classes.append(style_class) style_info = dict( target_field=None, style_classes=style_classes, style_type='rasterStyle') # For printing map purpose map_title = tr('People in need of evacuation') legend_notes = tr( 'Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Population Count') # Create raster object and return raster = Raster( impact, projection=hazard_layer.get_projection(), geotransform=hazard_layer.get_geotransform(), name=tr('Population which %s') % ( self.impact_function_manager .get_function_title(self).lower()), keywords={ 'impact_summary': impact_summary, 'impact_table': impact_table, 'map_title': map_title, 'legend_notes': legend_notes, 'legend_units': legend_units, 'legend_title': legend_title, 'evacuated': evacuated, 'total_needs': total_needs}, style_info=style_info) self._impact = raster return raster