Source code for safe.impact_functions.inundation.tsunami_population_evacuation_raster.impact_function

# coding=utf-8
"""Tsunami Evacuation Impact Function."""
import numpy

from safe.impact_functions.bases.continuous_rh_continuous_re import \
    ContinuousRHContinuousRE
from safe.impact_functions.core import (
    population_rounding,
    has_no_data
)
from safe.impact_functions.impact_function_manager import ImpactFunctionManager
from safe.impact_functions.inundation\
    .tsunami_population_evacuation_raster.metadata_definitions import \
    TsunamiEvacuationMetadata
from safe.storage.raster import Raster
from safe.utilities.i18n import tr
from safe.common.utilities import (
    verify,
    humanize_class,
    create_classes,
    create_label,
    get_thousand_separator)

from safe.common.exceptions import ZeroImpactException
from safe.gui.tools.minimum_needs.needs_profile import add_needs_parameters, \
    filter_needs_parameters, get_needs_provenance_value
from safe.impact_reports.population_exposure_report_mixin import \
    PopulationExposureReportMixin
import safe.messaging as m
from safe.messaging import styles


# noinspection PyClassHasNoInit
[docs]class TsunamiEvacuationFunction( ContinuousRHContinuousRE, PopulationExposureReportMixin): # noinspection PyUnresolvedReferences """Impact function for tsunami evacuation.""" _metadata = TsunamiEvacuationMetadata() def __init__(self): super(TsunamiEvacuationFunction, self).__init__() self.impact_function_manager = ImpactFunctionManager() # AG: Use the proper minimum needs, update the parameters self.parameters = add_needs_parameters(self.parameters) self.no_data_warning = False
[docs] def notes(self): """Return the notes section of the report. :return: The notes that should be attached to this impact report. :rtype: safe.messaging.Message """ thresholds = self.parameters['thresholds'].value if get_needs_provenance_value(self.parameters) is None: needs_provenance = '' else: needs_provenance = tr(get_needs_provenance_value(self.parameters)) message = m.Message(style_class='container') message.add( m.Heading(tr('Notes and assumptions'), **styles.INFO_STYLE)) checklist = m.BulletedList() checklist.add(tr( 'Total population in the analysis area: %s' ) % population_rounding(self.total_population)) checklist.add(tr( '<sup>1</sup>People need evacuation if flood levels ' 'exceed %(eps).1f m.') % {'eps': thresholds[-1]}) checklist.add(needs_provenance) if self.no_data_warning: checklist.add(tr( 'The layers contained "no data" values. This missing data ' 'was carried through to the impact layer.')) checklist.add(tr( '"No data" values in the impact layer were treated as 0 ' 'when counting the affected or total population.')) checklist.add(tr( 'All values are rounded up to the nearest integer in ' 'order to avoid representing human lives as fractions.')) checklist.add(tr( 'Population rounding is applied to all population ' 'values, which may cause discrepancies when adding values.')) message.add(checklist) return message
[docs] def run(self): """Risk plugin for tsunami population evacuation. Counts number of people exposed to tsunami levels exceeding specified threshold. :returns: Map of population exposed to tsunami 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) 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 impact = medium = numpy.where(data >= lo, population, 0) self.impact_category_ordering.append(thresholds_name) self._evacuation_category = thresholds_name else: # Intermediate thresholds hi = thresholds[i + 1] thresholds_name = tr( 'People in %.1f m to %.1f m of water' % (lo, hi)) medium = numpy.where((data >= lo) * (data < hi), population, 0) # Count val = int(numpy.nansum(medium)) self.affected_population[thresholds_name] = 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 self.total_population = int(numpy.nansum(population)) 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']) ] impact_table = impact_summary = self.html_report() # check for zero impact if numpy.nanmax(impact) == 0 == numpy.nanmin(impact): message = m.Message() message.add(self.question) message.add(tr('No people in %.1f m of water') % thresholds[-1]) message = message.to_html(suppress_newlines=True) 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') 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': self.total_evacuated, 'total_needs': self.total_needs}, style_info=style_info) self._impact = raster return raster