class FloodEvacuationRasterHazardFunction(ContinuousRHContinuousRE, PopulationExposureReportMixin): # noinspection PyUnresolvedReferences """Risk plugin for flood population evacuation.""" _metadata = FloodEvacuationRasterHazardMetadata() def __init__(self): """Constructor.""" super(FloodEvacuationRasterHazardFunction, self).__init__() self.impact_function_manager = ImpactFunctionManager() # AG: Use the proper minimum needs, update the parameters self.parameters = add_needs_parameters(self.parameters) # Initialize instance attributes for readability (pylint) self.no_data_warning = False 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 def _tabulate_zero_impact(self): thresholds = self.parameters['thresholds'].value message = m.Message() table = m.Table(style_class='table table-condensed table-striped') row = m.Row() label = m.ImportantText( tr('People in %.1f m of water') % thresholds[-1]) content = '%s' % format_int(self.total_evacuated) row.add(m.Cell(label)) row.add(m.Cell(content)) table.add(row) table.caption = self.question message.add(table) message = message.to_html(suppress_newlines=True) return message 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() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # 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 # Put the deepest area in top #2385 self.impact_category_ordering.reverse() 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] style_class['transparency'] = 0 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()) 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, 'evacuated': evacuated, 'total_needs': total_needs } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # 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_layer_keywords, style_info=style_info) self._impact = raster return raster
class ClassifiedRasterHazardPopulationFunction(ImpactFunction): # noinspection PyUnresolvedReferences """Plugin for impact of population as derived by classified hazard.""" _metadata = ClassifiedRasterHazardPopulationMetadata() def __init__(self): super(ClassifiedRasterHazardPopulationFunction, self).__init__() self.impact_function_manager = ImpactFunctionManager() # AG: Use the proper minimum needs, update the parameters self.parameters = add_needs_parameters(self.parameters) def _tabulate(self, high, low, medium, minimum_needs, no_impact, question, total_impact): # Generate impact report for the pdf map table_body = [ question, TableRow([ tr('Total Population Affected '), '%s' % format_int(total_impact) ], header=True), TableRow([ tr('Population in High hazard class areas '), '%s' % format_int(high) ]), TableRow([ tr('Population in Medium hazard class areas '), '%s' % format_int(medium) ]), TableRow([ tr('Population in Low hazard class areas '), '%s' % format_int(low) ]), TableRow( [tr('Population Not Affected'), '%s' % format_int(no_impact)]), TableRow( tr('Table below shows the minimum needs for all ' 'evacuated people')) ] total_needs = evacuated_population_needs(total_impact, 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']) ])) return table_body, total_needs def _tabulate_action_checklist(self, table_body, total, no_data_warning): 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('Map shows the numbers of people in high, medium, and low ' 'hazard class areas'), tr('Total population: %s') % format_int(total) ]) if no_data_warning: table_body.extend([ tr('The layers 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.') ]) return table_body def run(self, layers=None): """Plugin for impact of population as derived by classified hazard. Input :param layers: List of layers expected to contain * hazard_layer: Raster layer of classified hazard * exposure_layer: Raster layer of population data Counts number of people exposed to each class of the hazard Return Map of population exposed to high class Table with number of people in each class """ self.validate() self.prepare(layers) # The 3 classes # TODO (3.2): shouldnt these be defined in keywords rather? TS low_class = self.parameters['low_hazard_class'] medium_class = self.parameters['medium_hazard_class'] high_class = self.parameters['high_hazard_class'] # The classes must be different to each other unique_classes_flag = all(x != y for x, y in list( itertools.combinations([low_class, medium_class, high_class], 2))) if not unique_classes_flag: raise FunctionParametersError( 'There is hazard class that has the same value with other ' 'class. Please check the parameters.') # Identify hazard and exposure layers hazard_layer = self.hazard # Classified Hazard exposure_layer = self.exposure # Population Raster # Extract data as numeric arrays hazard_data = hazard_layer.get_data(nan=True) # Class no_data_warning = False if has_no_data(hazard_data): no_data_warning = True # Calculate impact as population exposed to each class population = exposure_layer.get_data(scaling=True) # Get all population data that falls in each hazard class high_hazard_population = numpy.where(hazard_data == high_class, population, 0) medium_hazard_population = numpy.where(hazard_data == medium_class, population, 0) low_hazard_population = numpy.where(hazard_data == low_class, population, 0) affected_population = (high_hazard_population + medium_hazard_population + low_hazard_population) # Carry the no data values forward to the impact layer. affected_population = numpy.where(numpy.isnan(population), numpy.nan, affected_population) affected_population = numpy.where(numpy.isnan(hazard_data), numpy.nan, affected_population) # Count totals total_population = int(numpy.nansum(population)) total_high_population = int(numpy.nansum(high_hazard_population)) total_medium_population = int(numpy.nansum(medium_hazard_population)) total_low_population = int(numpy.nansum(low_hazard_population)) total_affected = int(numpy.nansum(affected_population)) total_not_affected = total_population - total_affected # check for zero impact if total_affected == 0: table_body = [ self.question, TableRow( [tr('People affected'), '%s' % format_int(total_affected)], header=True) ] message = Table(table_body).toNewlineFreeString() raise ZeroImpactException(message) minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] table_body, total_needs = self._tabulate( population_rounding(total_high_population), population_rounding(total_low_population), population_rounding(total_medium_population), minimum_needs, population_rounding(total_not_affected), self.question, population_rounding(total_affected)) impact_table = Table(table_body).toNewlineFreeString() table_body = self._tabulate_action_checklist( table_body, population_rounding(total_population), no_data_warning) impact_summary = Table(table_body).toNewlineFreeString() # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(affected_population.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('Population affected by each class') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) legend_units = tr('(people per cell)') legend_title = tr('Number of People') # Create raster object and return raster_layer = Raster( data=affected_population, projection=exposure_layer.get_projection(), geotransform=exposure_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, 'total_needs': total_needs }, style_info=style_info) self._impact = raster_layer return raster_layer
class ClassifiedRasterHazardPopulationFunction(ClassifiedRHContinuousRE, PopulationExposureReportMixin): # noinspection PyUnresolvedReferences """Plugin for impact of population as derived by classified hazard.""" _metadata = ClassifiedRasterHazardPopulationMetadata() def __init__(self): super(ClassifiedRasterHazardPopulationFunction, 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 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 """ 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 they are in a ' 'hazard zone.')) 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 value.')) message.add(checklist) return message def run(self): """Plugin for impact of population as derived by classified hazard. Counts number of people exposed to each class of the hazard Return Map of population exposed to high class Table with number of people in each class """ self.validate() self.prepare() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') # The 3 classes # TODO (3.2): shouldnt these be defined in keywords rather? TS categorical_hazards = self.parameters['Categorical hazards'].value low_class = categorical_hazards[0].value medium_class = categorical_hazards[1].value high_class = categorical_hazards[2].value # The classes must be different to each other unique_classes_flag = all(x != y for x, y in list( itertools.combinations([low_class, medium_class, high_class], 2))) if not unique_classes_flag: raise FunctionParametersError( 'There is hazard class that has the same value with other ' 'class. Please check the parameters.') # Extract data as numeric arrays hazard_data = self.hazard.layer.get_data(nan=True) # Class if has_no_data(hazard_data): self.no_data_warning = True # Calculate impact as population exposed to each class population = self.exposure.layer.get_data(scaling=True) # Get all population data that falls in each hazard class high_hazard_population = numpy.where(hazard_data == high_class, population, 0) medium_hazard_population = numpy.where(hazard_data == medium_class, population, 0) low_hazard_population = numpy.where(hazard_data == low_class, population, 0) affected_population = (high_hazard_population + medium_hazard_population + low_hazard_population) # Carry the no data values forward to the impact layer. affected_population = numpy.where(numpy.isnan(population), numpy.nan, affected_population) affected_population = numpy.where(numpy.isnan(hazard_data), numpy.nan, affected_population) # Count totals self.total_population = int(numpy.nansum(population)) self.affected_population[tr( 'Population in High hazard class areas')] = int( numpy.nansum(high_hazard_population)) self.affected_population[tr( 'Population in Medium hazard class areas')] = int( numpy.nansum(medium_hazard_population)) self.affected_population[tr( 'Population in Low hazard class areas')] = int( numpy.nansum(low_hazard_population)) 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) self.minimum_needs = [ parameter.serialize() for parameter in self.parameters['minimum needs'] ] total_needs = self.total_needs impact_table = impact_summary = self.html_report() # Create style colours = [ '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600', '#FF0000', '#7A0000' ] classes = create_classes(affected_population.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] style_class['transparency'] = 0 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('Number of people affected in each class') legend_title = tr('Number of People') legend_units = tr('(people per cell)') legend_notes = tr('Thousand separator is represented by %s' % get_thousand_separator()) 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, 'total_needs': total_needs } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # Create raster object and return raster_layer = Raster( data=affected_population, projection=self.exposure.layer.get_projection(), geotransform=self.exposure.layer.get_geotransform(), name=tr('People that might %s') % (self.impact_function_manager.get_function_title(self).lower()), keywords=impact_layer_keywords, style_info=style_info) self._impact = raster_layer return raster_layer
class TsunamiEvacuationFunction(ImpactFunction): # 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) def _tabulate(self, counts, evacuated, minimum_needs, question, rounding, thresholds, total, no_data_warning): # noinspection PyListCreation table_body = [ question, TableRow([(tr('People in %.1f m of water') % thresholds[-1]), '%s*' % format_int(evacuated)], header=True), TableRow( tr('* Number is rounded up to the nearest %s') % rounding), TableRow(tr('Map shows the numbers of people needing evacuation')) ] 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']) ])) 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 tsunami 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[0]) }) table_body.append(TableRow(s)) if no_data_warning: table_body.extend([ tr('The layers 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.') ]) return table_body, total_needs def run(self, layers=None): """Risk plugin for tsunami population evacuation. :param layers: List of layers expected to contain hazard_layer: Raster layer of tsunami depth exposure_layer: Raster layer of population data on the same grid as hazard_layer 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(layers) # Identify hazard and exposure layers hazard_layer = self.hazard # Tsunami inundation [m] 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)) # Sensible rounding val, rounding = population_rounding_full(val) counts.append([val, rounding]) # 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 = 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 table_body, total_needs = self._tabulate(counts, evacuated, minimum_needs, self.question, rounding, 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.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') # 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
class ContinuousHazardPopulationFunction(ContinuousRHContinuousRE, PopulationExposureReportMixin): # noinspection PyUnresolvedReferences """Plugin for impact of population as derived by continuous hazard.""" _metadata = ContinuousHazardPopulationMetadata() def __init__(self): super(ContinuousHazardPopulationFunction, 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 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 """ 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 they are in a ' 'hazard zone.')) checklist.add( tr('Map shows the numbers of people in high, medium, ' 'and low hazard class areas.')) 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 value.')) message.add(checklist) return message 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() self.provenance.append_step( 'Calculating Step', 'Impact function is calculating the impact.') 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] style_class['transparency'] = 0 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()) 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, 'total_needs': total_needs } self.set_if_provenance() impact_layer_keywords = self.generate_impact_keywords(extra_keywords) # 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_layer_keywords, style_info=style_info) self._impact = raster_layer return raster_layer
class ContinuousHazardPopulationFunction(ImpactFunction): # noinspection PyUnresolvedReferences """Plugin for impact of population as derived by continuous hazard.""" _metadata = ContinuousHazardPopulationMetadata() def __init__(self): super(ContinuousHazardPopulationFunction, self).__init__() self.impact_function_manager = ImpactFunctionManager() # AG: Use the proper minimum needs, update the parameters self.parameters = add_needs_parameters(self.parameters) def _tabulate(self, high, low, medium, question, total_impact): # Generate impact report for the pdf map table_body = [ question, TableRow([tr('People impacted '), '%s' % format_int(total_impact)], header=True), TableRow( [tr('People in high hazard area '), '%s' % format_int(high)], header=True), TableRow([ tr('People in medium hazard area '), '%s' % format_int(medium) ], header=True), TableRow([tr('People in low hazard area'), '%s' % format_int(low)], header=True) ] return table_body def _tabulate_notes(self, minimum_needs, table_body, total, total_impact, no_data_warning): # Extend impact report for on-screen display table_body.extend([ TableRow(tr('Notes'), header=True), tr('Map shows population count in high, medium, and low hazard ' 'area.'), tr('Total population: %s') % format_int(total), TableRow( tr('Table below shows the minimum needs for all ' 'affected people')) ]) if no_data_warning: table_body.extend([ tr('The layers 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.') ]) total_needs = evacuated_population_needs(total_impact, 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']) ])) return table_body, total_needs 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