예제 #1
0
 def test_separator(self):
     """Test decimal and thousand separator
     """
     os.environ['LANG'] = 'en'
     assert ',' == get_thousand_separator()
     assert '.' == get_decimal_separator()
     os.environ['LANG'] = 'id'
     assert '.' == get_thousand_separator()
     assert ',' == get_decimal_separator()
예제 #2
0
 def test_separator(self):
     """Test decimal and thousand separator
     """
     os.environ['LANG'] = 'en'
     assert ',' == get_thousand_separator()
     assert '.' == get_decimal_separator()
     os.environ['LANG'] = 'id'
     assert '.' == get_thousand_separator()
     assert ',' == get_decimal_separator()
예제 #3
0
    def run(self):
        """Risk plugin for volcano hazard on building/structure.

        Counts number of building exposed to each volcano hazard zones.

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        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.name_attribute = self.hazard.keyword('volcano_name_field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            message = (
                'Input hazard must be a polygon. I got %s with '
                'layer type %s' %
                (self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_zone_attribute exists in hazard_layer
        if (self.hazard_class_attribute
                not in self.hazard.layer.get_attribute_names()):
            message = (
                'Hazard data %s did not contain expected attribute %s ' %
                (self.hazard.name, self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Get names of volcanoes considered
        if self.name_attribute in self.hazard.layer.get_attribute_names():
            volcano_name_list = set()
            for row in self.hazard.layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[self.name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)
        else:
            self.volcano_names = tr('Not specified in data')

        # Retrieve the classification that is used by the hazard layer.
        vector_hazard_classification = self.hazard.keyword(
            'vector_hazard_classification')
        # Get the dictionary that contains the definition of the classification
        vector_hazard_classification = definition(vector_hazard_classification)
        # Get the list classes in the classification
        vector_hazard_classes = vector_hazard_classification['classes']
        # Initialize OrderedDict of affected buildings
        self.affected_buildings = OrderedDict()
        # Iterate over vector hazard classes
        for vector_hazard_class in vector_hazard_classes:
            # Check if the key of class exist in hazard_class_mapping
            if vector_hazard_class['key'] in self.hazard_class_mapping.keys():
                # Replace the key with the name as we need to show the human
                # friendly name in the report.
                self.hazard_class_mapping[vector_hazard_class['name']] = \
                    self.hazard_class_mapping.pop(vector_hazard_class['key'])
                # Adding the class name as a key in affected_building
                self.affected_buildings[vector_hazard_class['name']] = {}

        # Run interpolation function for polygon2raster
        interpolated_layer = assign_hazard_values_to_exposure_data(
            self.hazard.layer, self.exposure.layer)

        # Extract relevant exposure data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        self.buildings = {}

        for i in range(len(features)):
            # Get the hazard value based on the value mapping in keyword
            hazard_value = get_key_for_value(
                features[i][self.hazard_class_attribute],
                self.hazard_class_mapping)
            if not hazard_value:
                hazard_value = self._not_affected_value
            features[i][self.target_field] = get_string(hazard_value)

            if (self.exposure_class_attribute
                    and self.exposure_class_attribute in attribute_names):
                usage = features[i][self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, features[i])

            if usage in [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')

            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][usage] = OrderedDict([
                        (tr('Buildings Affected'), 0)
                    ])

            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][tr(
                    'Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        # 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()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()

        # Create style
        colours = [
            '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600',
            '#FF0000', '#7A0000'
        ]
        colours = colours[::-1]  # flip

        colours = colours[:len(self.affected_buildings.keys())]

        style_classes = []

        i = 0
        for category_name in self.affected_buildings.keys():
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(self.affected_buildings.keys()):
                i = len(self.affected_buildings.keys()) - 1
            style_class['colour'] = colours[i]
            i += 1

            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')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        extra_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
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create vector layer and return
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by volcanic hazard zone'),
            keywords=impact_layer_keywords,
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #4
0
    def as_dict():
        """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('Earthquake ITB fatality function'),
            'impact':
            tr('Die or be displaced'),
            'title':
            tr('Die or be displaced'),
            'function_type':
            'old-style',
            'author':
            'Hadi Ghasemi',
            'date_implemented':
            'N/A',
            'overview':
            tr('Estimates the number of fatalities resulting from an '
               'earthquake. Uses data from an Indonesian database of '
               'earthquake events to calculate fatality rates. Based on the '
               'earthquake model for Indonesia developed by ITB.'),
            '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 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 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'),
            'hazard_input':
            '',
            'exposure_input':
            '',
            'output':
            '',
            'actions':
            tr('Provide details about the population including '
               'estimates for mortalities.'),
            'limitations': [
                tr('The model is based on a limited number of observed '
                   'fatality rates during four previous fatal events.'),
                tr('The model clearly over-predicts the fatality rates at '
                   'intensities higher than VIII.'),
                tr('The model only estimates the expected fatality rate '
                   'for a given intensity level. The associated '
                   'uncertainty for the proposed model is not addressed.'),
                tr('There are few known issues in the  current model:\n\n'
                   '* rounding MMI values to the nearest 0.5,\n'
                   '* Implemention of Finite-Fault models of candidate '
                   '  events, and\n'
                   '* consistency between selected GMPEs with those in '
                   '  use by BMKG.\n')
            ],
            'citations': [{
                'text':
                tr('Indonesian Earthquake Building-Damage and Fatality '
                   'Models and Post Disaster Survey Guidelines '
                   'Development Bali, 27-28 February 2012, 54pp.'),
                'link':
                None
            }, {
                'text':
                tr('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.'),
                'link':
                None
            }, {
                'text':
                tr('Jaiswal, K., and Wald, D., 2010. An empirical model '
                   'for global earthquake fatality estimation, Earthq. '
                   'Spectra 26, 1017-1037.'),
                'link':
                None
            }],
            'legend_title':
            tr('Population Count'),
            'legend_units':
            tr('(people per cell)'),
            'legend_notes':
            tr('Thousand separator is represented by %s' %
               get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode':
                    layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': [hazard_earthquake],
                    'continuous_hazard_units': [unit_mmi],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters':
            OrderedDict([('postprocessors',
                          OrderedDict([
                              ('Gender', default_gender_postprocessor()),
                              ('Age', age_postprocessor()),
                              ('MinimumNeeds', minimum_needs_selector()),
                          ])), ('minimum needs', default_minimum_needs())])
        }
        return dict_meta
예제 #5
0
    def as_dict():
        """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': 'ClassifiedPolygonHazardPopulationFunction',
            'name': tr('Classified polygon hazard on population'),
            'impact': tr('Be impacted'),
            'title': tr('Be impacted'),
            'function_type': 'old-style',
            'author': 'Akbar Gumbira ([email protected])',
            'date_implemented': 'N/A',
            'hazard_input': tr(
                'A hazard vector layer must be a polygon layer that has a '
                'hazard zone attribute.'),
            'exposure_input': tr(
                'An exposure raster layer where each cell represents '
                'the population count for that cell.'),
            'output': tr(
                'A vector layer containing polygons matching the hazard areas'
                'and an attribute representing the number of people affected '
                'for each area.'),
            'actions': tr(
                'Provide details about the number of people that are '
                'within each hazard zone.'),
            'limitations': [],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            'legend_title': tr('Population'),
            'legend_units': tr('(people per cell)'),
            'legend_notes': tr(
                'Thousand separator is represented by  %s' %
                get_thousand_separator()),
            'overview': tr(
                'To assess the number of people that may be impacted by '
                'each hazard zone.'),
            'detailed_description': '',
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [layer_geometry_polygon],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': hazard_all,
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [
                        generic_vector_hazard_classes
                    ],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #6
0
    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
예제 #7
0
    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.
                * hazard_layer: Hazard layer of volcano
                * exposure_layer: Vector layer of structure data on
                the same grid as hazard_layer

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        # Identify hazard and exposure layers
        hazard_layer = get_hazard_layer(layers)  # Volcano hazard layer
        exposure_layer = get_exposure_layer(layers)
        is_point_data = False

        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)

        if hazard_layer.is_point_data:
            # Use concentric circles
            radii = self.parameters['distances [km]']
            is_point_data = True

            centers = hazard_layer.get_geometry()
            attributes = hazard_layer.get_data()
            rad_m = [x * 1000 for x in radii]  # Convert to meters
            hazard_layer = buffer_points(centers, rad_m, data_table=attributes)
            # To check
            category_title = 'Radius'
            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 hazard_layer.get_attribute_names():
            volcano_name_list = []
            for row in hazard_layer.get_data():
                # Run through all polygons and get unique names
                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 not category_title 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)

        # Initialise attributes of output dataset with all attributes
        # from input polygon and a building 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 impacted building per polygon and total
        for row in interpolated_layer.get_data():
            # Update building count for associated polygon
            poly_id = row['polygon_id']
            if poly_id is not None:
                new_data_table[poly_id][self.target_field] += 1

                # Update building count for each category
                category = new_data_table[poly_id][category_title]
                categories[category] += 1

        # Count totals
        total = len(exposure_layer)

        # 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)]

        cumulative = 0
        for name in category_names:
            # prevent key error
            count = categories.get(name, 0)
            cumulative += count
            if is_point_data:
                name = int(name) / 1000
            table_body.append(TableRow([name, format_int(count),
                                        format_int(cumulative)]))

        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_data_table]

        if max(building_counts) == 0 == min(building_counts):
            table_body = [
                question,
                TableRow([tr('Number of buildings affected'),
                          '%s' % format_int(cumulative), 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']

        # Create Classes
        classes = create_classes(building_counts, len(colours))
        # Create Interval Classes
        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:
                style_class['min'] = 0
            else:
                style_class['min'] = classes[i - 1]
            style_class['transparency'] = 30
            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
        impact_layer = Vector(
            data=new_data_table,
            projection=hazard_layer.get_projection(),
            geometry=hazard_layer.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 impact_layer
예제 #8
0
    def as_dict():
        """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': 'FloodEvacuationRasterHazardFunction',
            'name': tr('Raster flood on population'),
            'impact': tr('Need evacuation'),
            'title': tr('Need evacuation'),
            'function_type': 'old-style',
            'author': 'AIFDR',
            'date_implemented': 'N/A',
            'overview': tr(
                'To assess the impacts of flood inundation in raster '
                'format on population.'),
            'detailed_description': tr(
                'The population subject to inundation exceeding a '
                'threshold (default 1m) is calculated and returned as a '
                'raster layer. In addition the total number of affected '
                'people and the required needs based on the user '
                'defined minimum needs are reported. The threshold can be '
                'changed and even contain multiple numbers in which case '
                'evacuation and needs are calculated using the largest number '
                'with population breakdowns provided for the smaller numbers. '
                'The population raster is resampled to the resolution of the '
                'hazard raster and is rescaled so that the resampled '
                'population counts reflect estimates of population count '
                'per resampled cell. The resulting impact layer has the '
                'same resolution and reflects population count per cell '
                'which are affected by inundation.'),
            'hazard_input': tr(
                'A hazard raster layer where each cell represents flood '
                'depth (in meters).'),
            'exposure_input': tr(
                'An exposure raster layer where each cell represent '
                'population count.'),
            'output': tr(
                'Raster layer contains population affected and the minimum '
                'needs based on number of the population affected.'),
            'actions': tr(
                'Provide details about how many people would likely need '
                'to be evacuated, where they are located and what '
                'resources would be required to support them.'),
            'limitations': [
                tr('The default threshold of 1 meter was selected based '
                   'on consensus, not hard evidence.')
            ],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            '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()),
            'layer_name': tr('Population which need evacuation'),
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': [hazard_flood],
                    'continuous_hazard_units': [unit_feet, unit_metres],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('thresholds', threshold()),
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #9
0
class ITBFatalityFunction(ImpactFunction):
    # 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.
    """

    _metadata = ITBFatalityMetadata()

    def __init__(self):
        super(ITBFatalityFunction, self).__init__()

        # AG: Use the proper minimum needs, update the parameters
        self.parameters = add_needs_parameters(self.parameters)
        self.hardcoded_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)
        ])

    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.hardcoded_parameters['x']
        y = self.hardcoded_parameters['y']
        # noinspection PyUnresolvedReferences
        return numpy.power(10.0, x * mmi - y)

    def run(self, layers=None):
        """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
        """
        self.validate()
        self.prepare(layers)

        displacement_rate = self.hardcoded_parameters['displacement_rate']

        # Tolerance for transparency
        tolerance = self.hardcoded_parameters['tolerance']

        # Extract input layers
        intensity = self.hazard
        population = self.exposure

        # 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.hardcoded_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
        mask = 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
            mmi_matches = numpy.where(
                (hazard > mmi - self.hardcoded_parameters['step']) * (
                    hazard <= mmi + self.hardcoded_parameters['step']),
                exposure, 0)

            # Calculate expected number of fatalities per level
            fatality_rate = self.fatality_rate(mmi)

            fatalities = fatality_rate * mmi_matches

            # Calculate expected number of displaced people per level
            try:
                displacements = displacement_rate[mmi] * mmi_matches
            except KeyError, e:
                msg = 'mmi = %i, mmi_matches = %s, Error msg: %s' % (
                    mmi, str(mmi_matches), str(e))
                # noinspection PyExceptionInherit
                raise InaSAFEError(msg)

            # Adjust displaced people to disregard fatalities.
            # Set to zero if there are more fatalities than displaced.
            displacements = numpy.where(
                displacements > fatalities, displacements - fatalities, 0)

            # Sum up numbers for map
            mask += displacements   # 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(mmi_matches.flat)
            number_of_displaced[mmi] = numpy.nansum(displacements.flat)
            # noinspection PyUnresolvedReferences
            number_of_fatalities[mmi] = numpy.nansum(fatalities.flat)

        # Set resulting layer to NaN when less than a threshold. This is to
        # achieve transparency (see issue #126).
        mask[mask < 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 = [self.question]

        # Add total fatality estimate
        s = format_int(fatalities)
        table_body.append(TableRow([tr('Number of fatalities'), s],
                                   header=True))

        if self.hardcoded_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 = [
            self.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(mask) == 0 == numpy.nanmin(mask):
            table_body = [
                self.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(mask.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(
            mask,
            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)
        self._impact = raster
        return raster
예제 #10
0
    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
        """
        self.validate()
        self.prepare()

        self.provenance.append_step(
            'Calculating Step', 'Impact function is calculating the impact.')

        # Value from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Retrieve the classification that is used by the hazard layer.
        vector_hazard_classification = self.hazard.keyword(
            'vector_hazard_classification')
        # Get the dictionary that contains the definition of the classification
        vector_hazard_classification = definition(vector_hazard_classification)
        # Get the list classes in the classification
        vector_hazard_classes = vector_hazard_classification['classes']
        # Initialize OrderedDict of affected buildings
        self.affected_buildings = OrderedDict()
        # Iterate over vector hazard classes
        for vector_hazard_class in vector_hazard_classes:
            # Check if the key of class exist in hazard_class_mapping
            if vector_hazard_class['key'] in self.hazard_class_mapping.keys():
                # Replace the key with the name as we need to show the human
                # friendly name in the report.
                self.hazard_class_mapping[vector_hazard_class['name']] = \
                    self.hazard_class_mapping.pop(vector_hazard_class['key'])
                # Adding the class name as a key in affected_building
                self.affected_buildings[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.buildings = {}

        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()

        attribute_names = [
            field.name() for field in interpolated_layer.pendingFields()
        ]
        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}

            if (self.exposure_class_attribute
                    and self.exposure_class_attribute in attribute_names):
                usage = feature[self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, feature)

            if usage is None:
                usage = tr('Unknown')
            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.hazard_class_mapping.keys():
                    self.affected_buildings[category][usage] = OrderedDict([
                        (tr('Buildings Affected'), 0)
                    ])
            self.buildings[usage] += 1
            if hazard_value in self.hazard_class_mapping.keys():
                self.affected_buildings[hazard_value][usage][tr(
                    'Buildings Affected')] += 1

        interpolated_layer.dataProvider().changeAttributeValues(changed_values)

        # Lump small entries and 'unknown' into 'other' category
        # 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()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()

        # Create style
        categories = self.affected_buildings.keys()
        categories.append(self._not_affected_value)
        colours = color_ramp(len(categories))
        style_classes = []

        i = 0
        for hazard_zone in 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)
            i += 1

        # Override style info with new classes and name
        style_info = dict(target_field=self.target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by each hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        extra_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
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create vector layer and return
        impact_layer = Vector(
            data=interpolated_layer,
            name=tr('Buildings affected by each hazard zone'),
            keywords=impact_layer_keywords,
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #11
0
    def as_dict():
        """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':
            'VolcanoPointPopulationFunction',
            'name':
            tr('Point volcano on population'),
            'impact':
            tr('Be impacted'),
            'title':
            tr('Be impacted'),
            'function_type':
            'old-style',
            'author':
            'AIFDR',
            'date_implemented':
            'N/A',
            'hazard_input':
            tr('A point vector layer.'),
            'exposure_input':
            tr('An exposure raster layer where each cell represent '
               'population count.'),
            'output':
            tr('Vector layer contains people affected and the minimum '
               'needs based on the number of people affected.'),
            'actions':
            tr('Provide details about how many people would likely '
               'be affected by each hazard zone.'),
            'limitations': [],
            'citations': [{
                'text': None,
                'link': None
            }],
            'legend_title':
            tr('Population'),
            'legend_units':
            tr('(people per cell)'),
            'legend_notes':
            tr('Thousand separator is represented by  %s' %
               get_thousand_separator()),
            'overview':
            tr('To assess the impacts of volcano eruption on '
               'population.'),
            'detailed_description':
            '',
            'layer_requirements': {
                'hazard': {
                    'layer_mode':
                    layer_mode_classified,
                    'layer_geometries': [layer_geometry_point],
                    'hazard_categories': [
                        hazard_category_multiple_event,
                        hazard_category_single_event
                    ],
                    'hazard_types': [hazard_volcano],
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': [volcano_name_field]
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters':
            OrderedDict([
                # The radii
                ('distances', distance()),
                ('postprocessors',
                 OrderedDict([
                     ('Gender', default_gender_postprocessor()),
                     ('Age', age_postprocessor()),
                     ('MinimumNeeds', minimum_needs_selector()),
                 ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #12
0
    def run(self):
        """Risk plugin for volcano hazard on building/structure.

        Counts number of building exposed to each volcano hazard zones.

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare()

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        name_attribute = self.hazard.keyword('volcano_name_field')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            message = (
                'Input hazard must be a polygon. I got %s with '
                'layer type %s' %
                (self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_zone_attribute exists in hazard_layer
        if (self.hazard_class_attribute not in
                self.hazard.layer.get_attribute_names()):
            message = (
                'Hazard data %s did not contain expected attribute %s ' %
                (self.hazard.name, self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Get names of volcanoes considered
        if name_attribute in self.hazard.layer.get_attribute_names():
            volcano_name_list = set()
            for row in self.hazard.layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)
        else:
            self.volcano_names = tr('Not specified in data')

        # Run interpolation function for polygon2raster
        interpolated_layer = assign_hazard_values_to_exposure_data(
            self.hazard.layer, self.exposure.layer)

        # Extract relevant exposure data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        # Hazard zone categories from hazard layer
        hazard_zone_categories = list(
            set(self.hazard.layer.get_data(self.hazard_class_attribute)))

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for hazard_category in hazard_zone_categories:
            self.affected_buildings[hazard_category] = {}

        for i in range(len(features)):
            hazard_value = features[i][self.hazard_class_attribute]
            if not hazard_value:
                hazard_value = self._not_affected_value
            features[i][self.target_field] = hazard_value
            if (self.exposure_class_attribute and
                    self.exposure_class_attribute in attribute_names):
                usage = features[i][self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, features[i])
            if usage in [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')
            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        usage] = OrderedDict([
                            (tr('Buildings Affected'), 0)])
            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()
        category_names = hazard_zone_categories
        category_names.append(self._not_affected_value)

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip

        colours = colours[:len(category_names)]

        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            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')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        # Create vector layer and return
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            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)

        self._impact = impact_layer
        return impact_layer
예제 #13
0
    def run(self, layers=None):
        """Run classified population evacuation Impact Function.

        :param layers: List of layers expected to contain where two layers
            should be present.

            * hazard_layer: Vector polygon layer
            * exposure_layer: Raster layer of population data on the same grid
                as hazard_layer

        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(layers)

        # Parameters
        hazard_zone_attribute = self.parameters['hazard zone attribute']

        # Identify hazard and exposure layers
        hazard_layer = self.hazard
        exposure_layer = self.exposure

        # 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 does not contain expected hazard '
                   'zone attribute "%s". Please change it in the option. ' %
                   (hazard_layer.get_name(), hazard_zone_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(msg)

        # Get unique hazard zones from the layer attribute
        self.hazard_zones = list(
            set(hazard_layer.get_data(hazard_zone_attribute)))

        # 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

        # Interpolated layer represents grid cell that lies in the polygon
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                hazard_layer,
                exposure_layer,
                attribute_name=self.target_field
            )

        # Initialise total population affected by each hazard zone
        affected_population = {}
        for hazard_zone in self.hazard_zones:
            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[hazard_zone_attribute]
                affected_population[hazard_zone] += population

        # Count total population from exposure layer
        total_population = population_rounding(
            int(numpy.nansum(exposure_layer.get_data())))

        # Count total affected population
        total_affected_population = reduce(
            lambda x, y: x + y,
            [population for population in affected_population.values()])

        # check for zero impact
        if total_affected_population == 0:
            table_body = [
                self.question,
                TableRow([
                    tr('People impacted'),
                    '%s' % format_int(total_affected_population)
                ],
                         header=True)
            ]
            message = Table(table_body).toNewlineFreeString()
            raise ZeroImpactException(message)

        # Generate impact report for the pdf map
        blank_cell = ''
        table_body = [
            self.question,
            TableRow([
                tr('People impacted'),
                '%s' %
                format_int(population_rounding(total_affected_population)),
                blank_cell
            ],
                     header=True)
        ]

        for hazard_zone in self.hazard_zones:
            table_body.append(
                TableRow([
                    hazard_zone,
                    format_int(
                        population_rounding(affected_population[hazard_zone]))
                ]))

        table_body.extend([
            TableRow(
                tr('Map shows the number of people impacted in each of the '
                   'hazard zones.'))
        ])

        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('"nodata" values in the exposure layer are treated as 0 '
               'when counting the affected or total population')
        ])

        impact_summary = Table(table_body).toNewlineFreeString()

        # 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_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 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
예제 #14
0
    def as_dict():
        """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': 'FloodEvacuationRasterHazardFunction',
            'name': tr('Raster flood on population'),
            'impact': tr('Need evacuation'),
            'title': tr('Need evacuation'),
            'function_type': 'old-style',
            'author': 'AIFDR',
            'date_implemented': 'N/A',
            'overview': tr(
                'To assess the impacts of flood inundation in raster '
                'format on population.'),
            'detailed_description': tr(
                'The population subject to inundation exceeding a '
                'threshold (default 1m) is calculated and returned as a '
                'raster layer. In addition the total number of affected '
                'people and the required needs based on the user '
                'defined minimum needs are reported. The threshold can be '
                'changed and even contain multiple numbers in which case '
                'evacuation and needs are calculated using the largest number '
                'with population breakdowns provided for the smaller numbers. '
                'The population raster is resampled to the resolution of the '
                'hazard raster and is rescaled so that the resampled '
                'population counts reflect estimates of population count '
                'per resampled cell. The resulting impact layer has the '
                'same resolution and reflects population count per cell '
                'which are affected by inundation.'),
            'hazard_input': tr(
                'A hazard raster layer where each cell represents flood '
                'depth (in meters).'),
            'exposure_input': tr(
                'An exposure raster layer where each cell represent '
                'population count.'),
            'output': tr(
                'Raster layer contains population affected and the minimum '
                'needs based on number of the population affected.'),
            'actions': tr(
                'Provide details about how many people would likely need '
                'to be evacuated, where they are located and what '
                'resources would be required to support them.'),
            'limitations': [
                tr('The default threshold of 1 meter was selected based '
                   'on consensus, not hard evidence.')
            ],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            'legend_title': tr('Population Count'),
            'legend_units': tr('(people per cell)'),
            'legend_notes': tr(
                'Thousand separator is represented by %s' %
                get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': [hazard_flood],
                    'continuous_hazard_units': [unit_feet, unit_metres],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('thresholds', threshold()),
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #15
0
    def as_dict():
        """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': 'VolcanoPolygonPopulationFunction',
            'name': tr('Polygon volcano on population'),
            'impact': tr('Need evacuation'),
            'title': tr('Need evacuation'),
            'function_type': 'old-style',
            'author': 'AIFDR',
            'date_implemented': 'N/A',
            'hazard_input': tr(
                'The hazard layer must be a polygon layer. This layer '
                'must have an attribute representing the volcano hazard '
                'zone that can be specified in the impact function option. '
                'There are three classes low, medium, and high. The default '
                'values are "Kawasan Rawan Bencana I" for low, "Kawasan Rawan '
                'Bencana II" for medium, and "Kawasan  Rawan Bencana III for '
                'high." If you want to see the name of the volcano in the '
                'result, you need to specify the volcano name attribute in '
                'the Impact Function options.'),
            'exposure_input': tr(
                'An exposure raster layer where each cell represents a '
                'population count for that cell.'),
            'output': tr(
                'A vector layer containing people affected per hazard zone '
                'and the minimum needs based on the number of people '
                'affected.'),
            'actions': tr(
                'Provide details about the number of people that are within '
                'each hazard zone.'),
            'limitations': [],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            'map_title': tr('People affected by Volcano Hazard Zones'),
            'legend_title': tr('Population'),
            'legend_units': tr('(people per cell)'),
            'legend_notes': tr(
                'Thousand separator is represented by  %s' %
                get_thousand_separator()),
            'layer_name': tr('People affected by volcano hazard zones'),
            'overview': tr(
                'To assess the impact of a volcano eruption on people.'),
            'detailed_description': '',
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [layer_geometry_polygon],
                    'hazard_categories': [
                        hazard_category_multiple_event,
                        hazard_category_single_event
                    ],
                    'hazard_types': [hazard_volcano],
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [
                        volcano_vector_hazard_classes],
                    'raster_hazard_classifications': [],
                    'additional_keywords': [
                        volcano_name_field]
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #16
0
    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
예제 #17
0
    def run(self):
        """Counts number of building exposed to each volcano hazard zones.

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare()

        self.provenance.append_step(
            'Calculating Step',
            'Impact function is calculating the impact.')

        # Hazard Zone Attribute
        hazard_zone_attribute = 'radius'

        # Parameters
        radii = self.parameters['distances'].value

        # Get parameters from layer's keywords
        volcano_name_attribute = self.hazard.keyword('volcano_name_field')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Input checks
        if not self.hazard.layer.is_point_data:
            message = (
                'Input hazard must be a vector point layer. I got %s '
                'with layer type %s' % (
                    self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Make hazard layer by buffering the point
        centers = self.hazard.layer.get_geometry()
        features = self.hazard.layer.get_data()
        hazard_layer = buffer_points(
            centers,
            radii,
            hazard_zone_attribute,
            data_table=features)
        # Category names for the impact zone
        category_names = radii
        # In kilometers
        self._affected_categories_volcano = [
            tr('Radius %.1f km') % key for key in radii[::]]

        # Get names of volcanoes considered
        if volcano_name_attribute in hazard_layer.get_attribute_names():
            volcano_name_list = set()
            for row in hazard_layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[volcano_name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)

        # Find the target field name that has no conflict with the attribute
        # names in the hazard layer
        hazard_attribute_names = hazard_layer.get_attribute_names()
        target_field = get_non_conflicting_attribute_name(
            self.target_field, hazard_attribute_names)

        # Run interpolation function for polygon2polygon
        interpolated_layer = assign_hazard_values_to_exposure_data(
            hazard_layer, self.exposure.layer)

        # Extract relevant interpolated layer data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for category in radii:
            self.affected_buildings[category] = {}

        # Iterate the interpolated building layer
        for i in range(len(features)):
            hazard_value = features[i][hazard_zone_attribute]
            if not hazard_value:
                hazard_value = self._not_affected_value
            features[i][target_field] = hazard_value

            # Count affected buildings by usage type if available
            if (self.exposure_class_attribute and
                    self.exposure_class_attribute in attribute_names):
                usage = features[i][self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, features[i])

            if usage is [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')

            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        usage] = OrderedDict([
                            (tr('Buildings Affected'), 0)])

            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Adding 'km'
        affected_building_keys = self.affected_buildings.keys()
        for key in affected_building_keys:
            self.affected_buildings[tr('Radius %.1f km' % key)] = \
                self.affected_buildings.pop(key)

        # Lump small entries and 'unknown' into 'other' category
        # 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()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip
        colours = colours[:len(category_names)]
        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr('Radius %s km') % tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            style_classes.append(style_class)

        # Override style info with new classes and name
        style_info = dict(
            target_field=target_field,
            style_classes=style_classes,
            style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic buffered point')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr(
            'Thousand separator is represented by %s' %
            get_thousand_separator())

        extra_keywords = {
            'impact_summary': impact_summary,
            'impact_table': impact_table,
            'target_field': target_field,
            'map_title': map_title,
            'legend_notes': legend_notes,
            'legend_units': legend_units,
            'legend_title': legend_title
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create vector layer and return
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by volcanic buffered point'),
            keywords=impact_layer_keywords,
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #18
0
    def run(self, layers=None):
        """Run classified population evacuation Impact Function.

        :param layers: List of layers expected to contain where two layers
            should be present.

            * hazard_layer: Vector polygon layer
            * exposure_layer: Raster layer of population data on the same grid
                as hazard_layer

        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(layers)

        # Parameters
        hazard_zone_attribute = self.parameters['hazard zone attribute']

        # Identify hazard and exposure layers
        hazard_layer = self.hazard
        exposure_layer = self.exposure

        # 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 does not contain expected hazard '
                   'zone attribute "%s". Please change it in the option. ' %
                   (hazard_layer.get_name(), hazard_zone_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(msg)

        # Get unique hazard zones from the layer attribute
        self.hazard_zones = list(
            set(hazard_layer.get_data(hazard_zone_attribute)))

        # 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

        # Interpolated layer represents grid cell that lies in the polygon
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                hazard_layer,
                exposure_layer,
                attribute_name=self.target_field
            )

        # Initialise total population affected by each hazard zone
        affected_population = {}
        for hazard_zone in self.hazard_zones:
            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[hazard_zone_attribute]
                affected_population[hazard_zone] += population

        # Count total population from exposure layer
        total_population = population_rounding(
            int(numpy.nansum(exposure_layer.get_data())))

        # Count total affected population
        total_affected_population = reduce(
            lambda x, y: x + y,
            [population for population in affected_population.values()])

        # check for zero impact
        if total_affected_population == 0:
            table_body = [
                self.question,
                TableRow(
                    [tr('People impacted'),
                     '%s' % format_int(total_affected_population)],
                    header=True)]
            message = Table(table_body).toNewlineFreeString()
            raise ZeroImpactException(message)

        # Generate impact report for the pdf map
        blank_cell = ''
        table_body = [
            self.question,
            TableRow(
                [
                    tr('People impacted'),
                    '%s' % format_int(
                        population_rounding(total_affected_population)),
                    blank_cell],
                header=True)]

        for hazard_zone in self.hazard_zones:
            table_body.append(
                TableRow(
                    [
                        hazard_zone,
                        format_int(
                            population_rounding(
                                affected_population[hazard_zone]))
                    ]))

        table_body.extend([
            TableRow(tr(
                'Map shows the number of people impacted in each of the '
                'hazard zones.'))])

        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('"nodata" values in the exposure layer are treated as 0 '
                'when counting the affected or total population')]
        )

        impact_summary = Table(table_body).toNewlineFreeString()

        # 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_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 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
예제 #19
0
    def run(self, layers=None):
        """Counts number of building exposed to each volcano hazard zones.

        :param layers: List of layers expected to contain.
                * hazard_layer: Hazard layer of volcano
                * exposure_layer: Vector layer of structure data on
                the same grid as hazard_layer

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare(layers)
        # Target Field
        target_field = 'zone'
        # Hazard Zone Attribute
        hazard_zone_attribute = 'radius'
        # Not Affected Value
        not_affected_value = 'Not Affected'

        # Parameters
        radii = self.parameters['distances [km]']
        volcano_name_attribute = self.parameters['volcano name attribute']

        # Identify hazard and exposure layers
        hazard_layer = self.hazard  # Volcano hazard layer
        exposure_layer = self.exposure  # Building exposure layer

        # Input checks
        if not hazard_layer.is_point_data:
            message = (
                'Input hazard must be a vector point layer. I got %s '
                'with layer type %s' %
                (hazard_layer.get_name(), hazard_layer.get_geometry_name()))
            raise Exception(message)

        # Make hazard layer by buffering the point
        centers = hazard_layer.get_geometry()
        features = hazard_layer.get_data()
        radii_meter = [x * 1000 for x in radii]  # Convert to meters
        hazard_layer = buffer_points(centers,
                                     radii_meter,
                                     hazard_zone_attribute,
                                     data_table=features)
        # Category names for the impact zone
        category_names = radii_meter
        category_names.append(not_affected_value)

        # Get names of volcanoes considered
        if volcano_name_attribute in hazard_layer.get_attribute_names():
            volcano_name_list = set()
            for row in hazard_layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[volcano_name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)
        else:
            self.volcano_names = tr('Not specified in data')

        # Find the target field name that has no conflict with the attribute
        # names in the hazard layer
        hazard_attribute_names = hazard_layer.get_attribute_names()
        target_field = get_non_conflicting_attribute_name(
            target_field, hazard_attribute_names)

        # Run interpolation function for polygon2polygon
        interpolated_layer = assign_hazard_values_to_exposure_data(
            hazard_layer, exposure_layer, attribute_name=None)

        # Extract relevant interpolated layer data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for category in radii_meter:
            self.affected_buildings[category] = {}

        # Iterate the interpolated building layer
        for i in range(len(features)):
            hazard_value = features[i][hazard_zone_attribute]
            if not hazard_value:
                hazard_value = not_affected_value
            features[i][target_field] = hazard_value

            # Count affected buildings by usage type if available
            usage = get_osm_building_usage(attribute_names, features[i])
            if usage is [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')

            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][usage] = OrderedDict([
                        (tr('Buildings Affected'), 0)
                    ])

            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][tr(
                    'Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.generate_html_report()

        # Create style
        colours = [
            '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600',
            '#FF0000', '#7A0000'
        ]
        colours = colours[::-1]  # flip
        colours = colours[:len(category_names)]
        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            style_classes.append(style_class)

        # Override style info with new classes and name
        style_info = dict(target_field=target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic buffered point')
        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
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by volcanic buffered point'),
            keywords={
                'impact_summary': impact_summary,
                'impact_table': impact_table,
                'target_field': 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
예제 #20
0
    def as_dict():
        """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('Earthquake ITB fatality function'),
            'impact': tr('Die or be displaced'),
            'title': tr('Die or be displaced'),
            'function_type': 'old-style',
            'author': 'Hadi Ghasemi',
            'date_implemented': 'N/A',
            'overview': tr(
                'Estimates the number of fatalities resulting from an '
                'earthquake. Uses data from an Indonesian database of '
                'earthquake events to calculate fatality rates. Based on the '
                'earthquake model for Indonesia developed by ITB.'),
            '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 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 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'),
            'hazard_input': '',
            'exposure_input': '',
            'output': '',
            'actions': tr(
                'Provide details about the population including '
                'estimates for mortalities.'),
            'limitations': [
                tr('The model is based on a limited number of observed '
                   'fatality rates during four previous fatal events.'),
                tr('The model clearly over-predicts the fatality rates at '
                   'intensities higher than VIII.'),
                tr('The model only estimates the expected fatality rate '
                   'for a given intensity level. The associated '
                   'uncertainty for the proposed model is not addressed.'),
                tr('There are few known issues in the  current model:\n\n'
                   '* rounding MMI values to the nearest 0.5,\n'
                   '* Implemention of Finite-Fault models of candidate '
                   '  events, and\n'
                   '* consistency between selected GMPEs with those in '
                   '  use by BMKG.\n')
            ],
            'citations': [
                {
                    'text': tr(
                        'Indonesian Earthquake Building-Damage and Fatality '
                        'Models and Post Disaster Survey Guidelines '
                        'Development Bali, 27-28 February 2012, 54pp.'),
                    'link': None
                },
                {
                    'text': tr(
                        '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.'),
                    'link': None
                },
                {
                    'text': tr(
                        'Jaiswal, K., and Wald, D., 2010. An empirical model '
                        'for global earthquake fatality estimation, Earthq. '
                        'Spectra 26, 1017-1037.'),
                    'link': None
                }
            ],
            'legend_title': tr('Population Count'),
            'legend_units': tr('(people per cell)'),
            'legend_notes': tr(
                'Thousand separator is represented by %s' %
                get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': [hazard_earthquake],
                    'continuous_hazard_units': [unit_mmi],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                    ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #21
0
    def run(self):
        """Run volcano population evacuation Impact Function.

        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()

        # Parameters
        self.hazard_class_attribute = self.hazard.keyword('field')
        name_attribute = self.hazard.keyword('volcano_name_field')

        if has_no_data(self.exposure.layer.get_data(nan=True)):
            self.no_data_warning = True

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            msg = ('Input hazard must be a polygon layer. I got %s with '
                   'layer type %s' % (self.hazard.layer.get_name(),
                                      self.hazard.layer.get_geometry_name()))
            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 did not contain expected attribute %s ' % (
                self.hazard.layer.get_name(), self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(msg)

        features = self.hazard.layer.get_data()
        hazard_zone_categories = list(
            set(self.hazard.layer.get_data(self.hazard_class_attribute)))

        # Get names of volcanoes considered
        if name_attribute in self.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])

            self.volcano_names = ', '.join(set(volcano_name_list))

        # Run interpolation function for polygon2raster
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                self.hazard.layer,
                self.exposure.layer,
                attribute_name=self.target_field)

        # Initialise total affected per category
        for hazard_zone in hazard_zone_categories:
            self.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[self.hazard_class_attribute]
                self.affected_population[category] += population

        # Count totals
        self.total_population = int(
            numpy.nansum(self.exposure.layer.get_data()))
        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 self.total_affected_population == 0:
            message = no_population_impact_message(self.question)
            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 Volcano Hazard Zones')
        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 affected by volcano hazard zones'),
            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': self.total_needs},
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
    def run(self, layers):
        """Risk plugin for flood population evacuation

        Input
          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.

        Return
          Map of population exposed to flood levels exceeding the threshold
          Table with number of people evacuated and supplies required
        """

        # 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
        D = my_hazard.get_data(nan=0.0)  # Depth

        # Calculate impact as population exposed to depths > max threshold
        P = my_exposure.get_data(nan=0.0, scaling=True)

        # Calculate impact to intermediate thresholds
        counts = []
        # merely initialize
        my_impact = None
        for i, lo in enumerate(thresholds):
            if i == len(thresholds) - 1:
                # The last threshold
                my_impact = M = numpy.where(D >= lo, P, 0)
            else:
                # Intermediate thresholds
                hi = thresholds[i + 1]
                M = numpy.where((D >= lo) * (D < hi), P, 0)

            # Count
            val = int(numpy.sum(M))

            # Don't show digits less than a 1000
            val = round_thousand(val)
            counts.append(val)

        # Count totals
        evacuated = counts[-1]
        total = int(numpy.sum(P))
        # 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']
        mn_rice = minimum_needs['Rice']
        mn_drinking_water = minimum_needs['Drinking Water']
        mn_water = minimum_needs['Water']
        mn_family_kits = minimum_needs['Family Kits']
        mn_toilets = minimum_needs['Toilets']

        rice = int(evacuated * mn_rice)
        drinking_water = int(evacuated * mn_drinking_water)
        water = int(evacuated * mn_water)
        family_kits = int(evacuated * mn_family_kits)
        toilets = int(evacuated * mn_toilets)

        # Generate impact report for the pdf map
        table_body = [
            question,
            TableRow([(tr('People in %.1f m of water') % thresholds[-1]),
                      '%s*' % format_int(evacuated)],
                     header=True),
            TableRow(tr('* Number is rounded to the nearest 1000'),
                     header=False),
            TableRow(tr('Map shows population density needing evacuation')),
            TableRow([tr('Needs per week'), tr('Total')], header=True),
            [tr('Rice [kg]'), format_int(rice)],
            [tr('Drinking Water [l]'),
             format_int(drinking_water)],
            [tr('Clean Water [l]'), format_int(water)],
            [tr('Family Kits'), format_int(family_kits)],
            [tr('Toilets'), format_int(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 fractionals.')
        ])

        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, header=False))

        # Result
        impact_summary = Table(table_body).toNewlineFreeString()
        impact_table = impact_summary

        # check for zero impact
        if numpy.nanmax(my_impact) == 0 == numpy.nanmin(my_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(my_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
        R = Raster(my_impact,
                   projection=my_hazard.get_projection(),
                   geotransform=my_hazard.get_geotransform(),
                   name=tr('Population which %s') % get_function_title(self),
                   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
                   },
                   style_info=style_info)
        return R
예제 #23
0
    def run(self):
        """Risk plugin for volcano hazard on building/structure.

        Counts number of building exposed to each volcano hazard zones.

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare()

        # Get parameters from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        name_attribute = self.hazard.keyword('volcano_name_field')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            message = (
                'Input hazard must be a polygon. I got %s with '
                'layer type %s' %
                (self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_zone_attribute exists in hazard_layer
        if (self.hazard_class_attribute not in
                self.hazard.layer.get_attribute_names()):
            message = (
                'Hazard data %s did not contain expected attribute %s ' %
                (self.hazard.name, self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Get names of volcanoes considered
        if name_attribute in self.hazard.layer.get_attribute_names():
            volcano_name_list = set()
            for row in self.hazard.layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)
        else:
            self.volcano_names = tr('Not specified in data')

        # Run interpolation function for polygon2raster
        interpolated_layer = assign_hazard_values_to_exposure_data(
            self.hazard.layer, self.exposure.layer)

        # Extract relevant exposure data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        # Hazard zone categories from hazard layer
        hazard_zone_categories = list(
            set(self.hazard.layer.get_data(self.hazard_class_attribute)))

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for hazard_category in hazard_zone_categories:
            self.affected_buildings[hazard_category] = {}

        for i in range(len(features)):
            hazard_value = features[i][self.hazard_class_attribute]
            if not hazard_value:
                hazard_value = self._not_affected_value
            features[i][self.target_field] = hazard_value
            if (self.exposure_class_attribute and
                    self.exposure_class_attribute in attribute_names):
                usage = features[i][self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, features[i])
            if usage in [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')
            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        usage] = OrderedDict([
                            (tr('Buildings Affected'), 0)])
            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.generate_html_report()
        category_names = hazard_zone_categories
        category_names.append(self._not_affected_value)

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip

        colours = colours[:len(category_names)]

        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            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')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        # Create vector layer and return
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            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)

        self._impact = impact_layer
        return impact_layer
예제 #24
0
    def run(self):
        """Run volcano population evacuation Impact Function.

        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()

        self.provenance.append_step(
            'Calculating Step', 'Impact function is calculating the impact.')

        # Parameters
        self.hazard_class_attribute = self.hazard.keyword('field')
        name_attribute = self.hazard.keyword('volcano_name_field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')

        if has_no_data(self.exposure.layer.get_data(nan=True)):
            self.no_data_warning = True

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            message = tr(
                'Input hazard must be a polygon layer. I got %s with layer '
                'type %s' % (self.hazard.layer.get_name(),
                             self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_class_attribute exists in hazard_layer
        if (self.hazard_class_attribute
                not in self.hazard.layer.get_attribute_names()):
            message = tr(
                'Hazard data %s did not contain expected attribute '
                '%s ' %
                (self.hazard.layer.get_name(), self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        features = self.hazard.layer.get_data()

        # Get names of volcanoes considered
        if name_attribute in self.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])

            self.volcano_names = ', '.join(set(volcano_name_list))

        # Retrieve the classification that is used by the hazard layer.
        vector_hazard_classification = self.hazard.keyword(
            'vector_hazard_classification')
        # Get the dictionary that contains the definition of the classification
        vector_hazard_classification = definition(vector_hazard_classification)
        # Get the list classes in the classification
        vector_hazard_classes = vector_hazard_classification['classes']
        # Initialize OrderedDict of affected buildings
        self.affected_population = OrderedDict()
        # Iterate over vector hazard classes
        for vector_hazard_class in vector_hazard_classes:
            # Check if the key of class exist in hazard_class_mapping
            if vector_hazard_class['key'] in self.hazard_class_mapping.keys():
                # Replace the key with the name as we need to show the human
                # friendly name in the report.
                self.hazard_class_mapping[vector_hazard_class['name']] = \
                    self.hazard_class_mapping.pop(vector_hazard_class['key'])
                # Adding the class name as a key in affected_building
                self.affected_population[vector_hazard_class['name']] = 0

        # Run interpolation function for polygon2raster
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                self.hazard.layer,
                self.exposure.layer,
                attribute_name=self.target_field)

        # 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 hazard zone
                hazard_value = get_key_for_value(
                    row[self.hazard_class_attribute],
                    self.hazard_class_mapping)
                if not hazard_value:
                    hazard_value = self._not_affected_value
                self.affected_population[hazard_value] += population

        # Count totals
        self.total_population = int(
            numpy.nansum(self.exposure.layer.get_data()))
        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 self.total_affected_population == 0:
            message = no_population_impact_message(self.question)
            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])

            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')

        # For printing map purpose
        map_title = tr('People affected by Volcano Hazard Zones')
        legend_title = tr('Population')
        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,
            'target_field': self.target_field,
            'map_title': map_title,
            'legend_notes': legend_notes,
            'legend_units': legend_units,
            'legend_title': legend_title,
            'total_needs': self.total_needs
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # 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 volcano hazard zones'),
            keywords=impact_layer_keywords,
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #25
0
    def run(self):
        """Risk plugin for classified polygon hazard on building/structure.

        Counts number of building exposed to each hazard zones.

        :returns: Map of building exposed to each hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare()

        # Value from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword('field')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        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
        self.hazard_zones = self.hazard.layer.uniqueValues(
            hazard_zone_attribute_index)

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for hazard_zone in self.hazard_zones:
            self.affected_buildings[hazard_zone] = {}

        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()

        attribute_names = [
            field.name() for field in interpolated_layer.pendingFields()]
        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():
            hazard_value = feature[self.hazard_class_attribute]
            if not hazard_value:
                hazard_value = self._not_affected_value
            changed_values[feature.id()] = {target_field_index: hazard_value}

            if (self.exposure_class_attribute and
                    self.exposure_class_attribute in attribute_names):
                usage = feature[self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, feature)

            if usage is None:
                usage = tr('Unknown')
            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][usage] = OrderedDict(
                        [(tr('Buildings Affected'), 0)])
            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        interpolated_layer.dataProvider().changeAttributeValues(changed_values)

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()

        # Create style
        categories = self.hazard_zones
        categories.append(self._not_affected_value)
        colours = color_ramp(len(categories))
        style_classes = []

        i = 0
        for hazard_zone in self.hazard_zones:
            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)
            i += 1

        # Override style info with new classes and name
        style_info = dict(target_field=self.target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by each hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        # Create vector layer and return
        impact_layer = Vector(
            data=interpolated_layer,
            name=tr('Buildings affected 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
예제 #26
0
    def run(self, layers):
        """Risk plugin for volcano hazard on building/structure

        Input
          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

        Counts number of building exposed to each volcano hazard zones.

        Return
          Map of building exposed to volcanic hazard zones
          Table with number of buildings affected
        """

        # 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 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)
            # 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("Volcanos 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, layers):
        """Risk plugin for volcano population evacuation

        :param layers: List of layers expected to contain where two layers
            should be present.

            * 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.

        :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
        """

        # 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))
            # noinspection PyExceptionInherit
            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

        tot_needs = evacuated_population_weekly_needs(evacuated)

        # 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(tot_needs['rice']), blank_cell],
            [
                tr('Drinking Water [l]'),
                format_int(tot_needs['drinking_water']), blank_cell
            ],
            [
                tr('Clean Water [l]'),
                format_int(tot_needs['water']), blank_cell
            ],
            [
                tr('Family Kits'),
                format_int(tot_needs['family_kits']), blank_cell
            ], [tr('Toilets'),
                format_int(tot_needs['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
예제 #28
0
    def run(self):
        """Run volcano point population evacuation Impact Function.

        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()

        # Parameters
        radii = self.parameters['distances'].value

        # Get parameters from layer's keywords
        volcano_name_attribute = self.hazard.keyword('volcano_name_field')

        # Input checks
        if not self.hazard.layer.is_point_data:
            msg = (
                'Input hazard must be a polygon or point layer. I got %s with '
                'layer type %s' % (
                    self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(msg)

        data_table = self.hazard.layer.get_data()

        # Use concentric circles
        category_title = 'Radius'

        centers = self.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)

        # Get names of volcanoes considered
        if volcano_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[volcano_name_attribute])

            volcano_names = ''
            for radius in volcano_name_list:
                volcano_names += '%s, ' % radius
            self.volcano_names = volcano_names[:-2]  # Strip trailing ', '

        # Run interpolation function for polygon2raster
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                hazard_layer,
                self.exposure.layer,
                attribute_name=self.target_field
            )

        # Initialise affected population per categories
        for radius in rad_m:
            category = 'Distance %s km ' % format_int(radius)
            self.affected_population[category] = 0

        if has_no_data(self.exposure.layer.get_data(nan=True)):
            self.no_data_warning = True
        # 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 = 'Distance %s km ' % format_int(
                    row[category_title])
                self.affected_population[category] += population

        # Count totals
        self.total_population = population_rounding(
            int(numpy.nansum(self.exposure.layer.get_data())))

        self.minimum_needs = [
            parameter.serialize() for parameter in
            filter_needs_parameters(self.parameters['minimum needs'])
        ]

        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 affected by the buffered point volcano')
        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 affected by the buffered point volcano'),
            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': self.total_needs},
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #29
0
    def run(self):
        """Indonesian Earthquake Fatality Model."""
        self.validate()
        self.prepare()

        displacement_rate = self.hardcoded_parameters['displacement_rate']

        # Extract data grids
        hazard = self.hazard.layer.get_data()   # Ground Shaking
        # Population Density
        exposure = self.exposure.layer.get_data(scaling=True)

        # Calculate people affected by each MMI level
        # FIXME (Ole): this range is 2-9. Should 10 be included?
        mmi_range = self.hardcoded_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
        mask = 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
            step = self.hardcoded_parameters['step']
            mmi_matches = numpy.where(
                (hazard > mmi - step) * (
                    hazard <= mmi + step),
                exposure, 0)

            # Calculate expected number of fatalities per level
            exposed = numpy.nansum(mmi_matches)
            fatalities = self.fatality_rate(mmi) * exposed

            # Calculate expected number of displaced people per level
            displacements = displacement_rate[mmi] * (exposed - fatalities)

            # Adjust displaced people to disregard fatalities.
            # Set to zero if there are more fatalities than displaced.
            # displacements = numpy.where(
            #    displacements > fatalities, displacements - fatalities, 0)

            # Sum up numbers for map
            # We need to use matrices here and not just numbers #2235
            mask += mmi_matches * (1 - self.fatality_rate(mmi))   # Displaced

            # Generate text with result for this study
            # This is what is used in the real time system exposure table
            number_of_exposed[mmi] = exposed
            number_of_displaced[mmi] = displacements
            # noinspection PyUnresolvedReferences
            number_of_fatalities[mmi] = fatalities

        # Total statistics
        self.total_population = numpy.nansum(number_of_exposed.values())
        self.total_fatalities = numpy.nansum(number_of_fatalities.values())
        total_displaced = numpy.nansum(number_of_displaced.values())

        # As per email discussion with Ole, Trevor, Hadi, total fatalities < 50
        # will be rounded down to 0 - Tim
        # Needs to revisit but keep it alive for the time being - Hyeuk, Jono
        if self.total_fatalities < 50:
            self.total_fatalities = 0

        affected_population = self.affected_population
        affected_population[tr('Number of fatalities')] = self.total_fatalities
        affected_population[
            tr('Number of people displaced')] = total_displaced
        self.unaffected_population = (
            self.total_population - total_displaced - self.total_fatalities)
        self._evacuation_category = tr('Number of people displaced')

        self.minimum_needs = [
            parameter.serialize() for parameter in
            filter_needs_parameters(self.parameters['minimum needs'])
        ]
        total_needs = self.total_needs

        # Result
        impact_summary = self.generate_html_report()
        impact_table = impact_summary

        # Create style
        colours = ['#EEFFEE', '#FFFF7F', '#E15500', '#E4001B', '#730000']
        classes = create_classes(mask.flat[:], len(colours))
        interval_classes = humanize_class(classes)
        style_classes = []
        for i in xrange(len(interval_classes)):
            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_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(
            mask,
            projection=self.exposure.layer.get_projection(),
            geotransform=self.exposure.layer.get_geotransform(),
            keywords={
                'impact_summary': impact_summary,
                'exposed_per_mmi': number_of_exposed,
                'total_population': self.total_population,
                'total_fatalities': population_rounding(self.total_fatalities),
                'total_fatalities_raw': self.total_fatalities,
                'fatalities_per_mmi': number_of_fatalities,
                'total_displaced': population_rounding(total_displaced),
                '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)
        self._impact = raster
        return raster
    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]']

            centers = hazard_layer.get_geometry()
            rad_m = [x * 1000 for x in radii]  # Convert to meters
            hazard_layer = buffer_points(centers, rad_m, data_table=data_table)

            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

        # 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 not category_title 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 = int(numpy.sum(exposure_layer.get_data(nan=0)))

        # Don't show digits less than a 1000
        total = round_thousand(total)

        # 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))

            population = round_thousand(population)

            cumulative += population
            cumulative = round_thousand(cumulative)

            all_categories_population[name] = population
            all_categories_cumulative[name] = cumulative

        # Use final accumulation as total number needing evacuation
        evacuated = cumulative

        # Calculate estimated minimum needs
        minimum_needs = self.parameters['minimum needs']
        total_needs = evacuated_population_weekly_needs(
            evacuated, 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.')),
            TableRow(
                [tr('Needs per week'), tr('Total'), blank_cell], header=True),
            [tr('Rice [kg]'), format_int(total_needs['rice']), blank_cell], [
                tr('Drinking Water [l]'),
                format_int(total_needs['drinking_water']),
                blank_cell],
            [tr('Clean Water [l]'), format_int(total_needs['water']),
                blank_cell],
            [tr('Family Kits'), format_int(total_needs['family_kits']),
                blank_cell],
            [tr('Toilets'), format_int(total_needs['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_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)')
        legend_title = tr('Population count')

        # 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},
            style_info=style_info)
        return impact_layer
예제 #31
0
    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
예제 #32
0
    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
        """
        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')

        # 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'])
        ]

        impact_table = impact_summary = self.html_report()

        # 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')

        # For printing map purpose
        map_title = tr('People affected by flood prone areas')
        legend_title = tr('Population Count')
        legend_units = tr('(people per polygon)')
        legend_notes = tr(
            'Thousand separator is represented by %s' %
            get_thousand_separator())

        extra_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,
            'affected_population': total_affected_population,
            'total_population': self.total_population,
            'total_needs': self.total_needs
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create vector layer and return
        impact_layer = Raster(
            data=new_covered_exposure_data,
            projection=covered_exposure.get_projection(),
            geotransform=covered_exposure.get_geotransform(),
            name=tr('People affected by flood prone areas'),
            keywords=impact_layer_keywords,
            style_info=style_info)
        self._impact = impact_layer
        return impact_layer
예제 #33
0
    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.
                * hazard_layer: Hazard layer of volcano
                * exposure_layer: Vector layer of structure data on
                the same grid as hazard_layer

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        # Parameters
        not_affected_value = self.parameters['Not affected value']
        radii = self.parameters['distances [km]']
        target_field = self.parameters['target field']
        name_attribute = self.parameters['name attribute']
        hazard_zone_attribute = self.parameters['hazard zone attribute']

        # Identify hazard and exposure layers
        hazard_layer = get_hazard_layer(layers)  # Volcano hazard layer
        exposure_layer = get_exposure_layer(layers)  # Building exposure layer

        # Get question
        question = get_question(
            hazard_layer.get_name(), exposure_layer.get_name(), self)

        # Input checks
        if not hazard_layer.is_vector:
            message = ('Input hazard %s  was not a vector layer as expected '
                       % hazard_layer.get_name())
            raise Exception(message)

        if not (hazard_layer.is_polygon_data or hazard_layer.is_point_data):
            message = (
                '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()))
            raise Exception(message)

        if hazard_layer.is_point_data:
            # Use concentric circles
            centers = hazard_layer.get_geometry()
            attributes = hazard_layer.get_data()
            radii_meter = [x * 1000 for x in radii]  # Convert to meters
            hazard_layer = buffer_points(
                centers,
                radii_meter,
                hazard_zone_attribute,
                data_table=attributes)
            # To check
            category_names = radii_meter
        else:
            # FIXME (Ole): Change to English and use translation system
            # FIXME (Ismail) : Or simply use the values from the hazard layer
            category_names = ['Kawasan Rawan Bencana III',
                              'Kawasan Rawan Bencana II',
                              'Kawasan Rawan Bencana I']

        category_names.append(not_affected_value)

        # Get names of volcanoes considered
        if name_attribute in hazard_layer.get_attribute_names():
            volcano_name_list = set()
            for row in hazard_layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[name_attribute])
            volcano_names = ', '.join(volcano_name_list)
        else:
            volcano_names = tr('Not specified in data')

        # Check if category_title exists in hazard_layer
        if hazard_zone_attribute not in hazard_layer.get_attribute_names():
            message = (
                'Hazard data %s did not contain expected attribute %s ' %
                (hazard_layer.get_name(), hazard_zone_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Find the target field name that has no conflict with default
        # target
        attribute_names = hazard_layer.get_attribute_names()
        target_field = get_non_conflicting_attribute_name(
            target_field, attribute_names)

        # Run interpolation function for polygon2raster
        interpolated_layer = assign_hazard_values_to_exposure_data(
            hazard_layer, exposure_layer, attribute_name=None)

        # Extract relevant exposure data
        attribute_names = interpolated_layer.get_attribute_names()
        attribute_names_lower = [
            attribute_name.lower() for attribute_name in attribute_names]
        attributes = interpolated_layer.get_data()
        interpolate_size = len(interpolated_layer)

        building_per_category = {}
        building_usages = []
        other_sum = {}

        for category_name in category_names:
            building_per_category[category_name] = {}
            building_per_category[category_name]['total'] = 0
            other_sum[category_name] = 0

        # Building attribute that should be looked up to get the usage
        building_type_attributes = [
            'type',
            'amenity',
            'building_t',
            'office',
            'tourism',
            'leisure',
            'use',
        ]

        for i in range(interpolate_size):
            hazard_value = attributes[i][hazard_zone_attribute]
            if not hazard_value:
                hazard_value = not_affected_value
            attributes[i][target_field] = hazard_value

            if hazard_value in building_per_category.keys():
                building_per_category[hazard_value]['total'] += 1
            elif not hazard_value:
                building_per_category[not_affected_value]['total'] += 1
            else:
                building_per_category[hazard_value] = {}
                building_per_category[hazard_value]['total'] = 1

            # Count affected buildings by usage type if available
            usage = None
            for building_type_attribute in building_type_attributes:
                if (
                        building_type_attribute in attribute_names_lower and (
                            usage is None or usage == 0)):
                    attribute_index = attribute_names_lower.index(
                        building_type_attribute)
                    field_name = attribute_names[attribute_index]
                    usage = attributes[i][field_name]

            if (
                    'building' in attribute_names_lower and (
                        usage is None or usage == 0)):
                attribute_index = attribute_names_lower.index('building')
                field_name = attribute_names[attribute_index]
                usage = attributes[i][field_name]
                if usage == 'yes':
                    usage = 'building'

            if usage is None or usage == 0:
                usage = tr('unknown')

            if usage not in building_usages:
                building_usages.append(usage)
                for building in building_per_category.values():
                    building[usage] = 0

            building_per_category[hazard_value][usage] += 1

        # Generate simple impact report
        blank_cell = ''
        table_body = [question,
                      TableRow([tr('Volcanoes considered'),
                                '%s' % volcano_names, blank_cell],
                               header=True)]

        table_headers = [tr('Building type')]
        table_headers += [tr(x) for x in category_names]
        table_headers += [tr('Total')]

        table_body += [TableRow(table_headers, header=True)]

        for building_usage in building_usages:
            building_usage_good = building_usage.replace('_', ' ')
            building_usage_good = building_usage_good.capitalize()

            building_sum = sum([
                building_per_category[category_name][building_usage] for
                category_name in category_names
            ])

            # Filter building type that has no less than 25 items
            if building_sum >= 25:
                row = [tr(building_usage_good)]
                building_sum = 0
                for category_name in category_names:
                    building_sub_sum = building_per_category[category_name][
                        building_usage]
                    row.append(format_int(building_sub_sum))
                    building_sum += building_sub_sum

                row.append(format_int(building_sum))
                table_body.append(row)

            else:
                for category_name in category_names:
                    if category_name in other_sum.keys():
                        other_sum[category_name] += building_per_category[
                            category_name][building_usage]
                    else:
                        other_sum[category_name] = building_per_category[
                            category_name][building_usage]

        # Adding others building type to the report.
        other_row = [tr('Other')]
        other_building_total = 0
        for category_name in category_names:
            other_building_sum = other_sum[category_name]
            other_row.append(format_int(other_building_sum))
            other_building_total += other_building_sum

        other_row.append(format_int(other_building_total))
        table_body.append(other_row)

        all_row = [tr('Total')]
        all_row += [format_int(building_per_category[category_name]['total'])
                    for category_name in category_names]
        total = sum([building_per_category[category_name]['total'] for
                     category_name in category_names])
        all_row += [format_int(total)]

        table_body.append(TableRow(all_row, header=True))

        table_body += [TableRow(tr('Map shows buildings affected in each of '
                                   'volcano hazard polygons.'))]

        impact_table = Table(table_body).toNewlineFreeString()
        impact_summary = impact_table

        # 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.')])

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip

        colours = colours[:len(category_names)]

        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            style_classes.append(style_class)

        # Override style info with new classes and name
        style_info = dict(target_field=target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # 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
        impact_layer = Vector(
            data=attributes,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.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': target_field,
                      'map_title': map_title,
                      'legend_notes': legend_notes,
                      'legend_units': legend_units,
                      'legend_title': legend_title},
            style_info=style_info)
        return impact_layer
    def as_dict():
        """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': 'VolcanoPointPopulationFunction',
            'name': tr('Point volcano on population'),
            'impact': tr('Be impacted'),
            'title': tr('Be impacted'),
            'function_type': 'old-style',
            'author': 'AIFDR',
            'date_implemented': 'N/A',
            'hazard_input': tr(
                'A point vector layer.'),
            'exposure_input': tr(
                'An exposure raster layer where each cell represent '
                'population count.'),
            'output': tr(
                'Vector layer contains people affected and the minimum '
                'needs based on the number of people affected.'),
            'actions': tr(
                'Provide details about how many people would likely '
                'be affected by each hazard zone.'),
            'limitations': [],
            'citations': [],
            'map_title': tr('People affected by the buffered point volcano'),
            'legend_title': tr('Population'),
            'legend_units': tr('(people per cell)'),
            'legend_notes': tr(
                'Thousand separator is represented by  %s' %
                get_thousand_separator()),
            'layer_name': tr('People affected by the buffered point volcano'),
            'overview': tr(
                'To assess the impacts of volcano eruption on '
                'population.'),
            'detailed_description': '',
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [layer_geometry_point],
                    'hazard_categories': [
                        hazard_category_multiple_event,
                        hazard_category_single_event
                    ],
                    'hazard_types': [hazard_volcano],
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': [volcano_name_field]
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                # The radii
                ('distances', distance()),
                ('postprocessors', OrderedDict([
                    ('Gender', default_gender_postprocessor()),
                    ('Age', age_postprocessor()),
                    ('MinimumNeeds', minimum_needs_selector()),
                ])),
                ('minimum needs', default_minimum_needs())
            ])
        }
        return dict_meta
예제 #35
0
    def run(self):
        """Risk plugin for volcano hazard on building/structure.

        Counts number of building exposed to each volcano hazard zones.

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        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.name_attribute = self.hazard.keyword('volcano_name_field')
        self.hazard_class_mapping = self.hazard.keyword('value_map')
        # Try to get the value from keyword, if not exist, it will not fail,
        # but use the old get_osm_building_usage
        try:
            self.exposure_class_attribute = self.exposure.keyword(
                'structure_class_field')
        except KeywordNotFoundError:
            self.exposure_class_attribute = None

        # Input checks
        if not self.hazard.layer.is_polygon_data:
            message = (
                'Input hazard must be a polygon. I got %s with '
                'layer type %s' %
                (self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_zone_attribute exists in hazard_layer
        if (self.hazard_class_attribute not in
                self.hazard.layer.get_attribute_names()):
            message = (
                'Hazard data %s did not contain expected attribute %s ' %
                (self.hazard.name, self.hazard_class_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Get names of volcanoes considered
        if self.name_attribute in self.hazard.layer.get_attribute_names():
            volcano_name_list = set()
            for row in self.hazard.layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[self.name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)
        else:
            self.volcano_names = tr('Not specified in data')

        # Retrieve the classification that is used by the hazard layer.
        vector_hazard_classification = self.hazard.keyword(
            'vector_hazard_classification')
        # Get the dictionary that contains the definition of the classification
        vector_hazard_classification = definition(vector_hazard_classification)
        # Get the list classes in the classification
        vector_hazard_classes = vector_hazard_classification['classes']
        # Initialize OrderedDict of affected buildings
        self.affected_buildings = OrderedDict()
        # Iterate over vector hazard classes
        for vector_hazard_class in vector_hazard_classes:
            # Check if the key of class exist in hazard_class_mapping
            if vector_hazard_class['key'] in self.hazard_class_mapping.keys():
                # Replace the key with the name as we need to show the human
                # friendly name in the report.
                self.hazard_class_mapping[vector_hazard_class['name']] = \
                    self.hazard_class_mapping.pop(vector_hazard_class['key'])
                # Adding the class name as a key in affected_building
                self.affected_buildings[vector_hazard_class['name']] = {}

        # Run interpolation function for polygon2raster
        interpolated_layer = assign_hazard_values_to_exposure_data(
            self.hazard.layer, self.exposure.layer)

        # Extract relevant exposure data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        self.buildings = {}

        for i in range(len(features)):
            # Get the hazard value based on the value mapping in keyword
            hazard_value = get_key_for_value(
                    features[i][self.hazard_class_attribute],
                    self.hazard_class_mapping)
            if not hazard_value:
                hazard_value = self._not_affected_value
            features[i][self.target_field] = get_string(hazard_value)

            if (self.exposure_class_attribute and
                    self.exposure_class_attribute in attribute_names):
                usage = features[i][self.exposure_class_attribute]
            else:
                usage = get_osm_building_usage(attribute_names, features[i])

            if usage in [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')

            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        usage] = OrderedDict([
                            (tr('Buildings Affected'), 0)])

            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        # 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()

        # Generate simple impact report
        impact_summary = impact_table = self.html_report()

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip

        colours = colours[:len(self.affected_buildings.keys())]

        style_classes = []

        i = 0
        for category_name in self.affected_buildings.keys():
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(self.affected_buildings.keys()):
                i = len(self.affected_buildings.keys()) - 1
            style_class['colour'] = colours[i]
            i += 1

            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')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic hazard zone')
        legend_title = tr('Building count')
        legend_units = tr('(building)')
        legend_notes = tr('Thousand separator is represented by %s' %
                          get_thousand_separator())

        extra_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
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create vector layer and return
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by volcanic hazard zone'),
            keywords=impact_layer_keywords,
            style_info=style_info
        )

        self._impact = impact_layer
        return impact_layer
예제 #36
0
    def as_dict():
        """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': 'ClassifiedPolygonHazardBuildingFunction',
            'name': tr('Classified polygon hazard on buildings'),
            'impact': tr('Be affected'),
            'title': tr('Be affected'),
            'function_type': 'qgis2.0',
            'author': 'Akbar Gumbira ([email protected])',
            'date_implemented': '17/04/2015',
            'overview': tr(
                'To assess the impact of each hazard zone on buildings.'),
            'detailed_description': '',
            'hazard_input': tr(
                'The hazard layer must be a polygon layer. This layer '
                'must have an attribute representing the hazard '
                'zone that can be specified in the impact function options.'),
            'exposure_input': tr(
                'Vector polygon layer extracted from OSM where each '
                'polygon represents the footprint of a building.'),
            'output': tr(
                'A vector layer of buildings with each tagged according to '
                'the hazard zone in which it falls.'),
            'actions': tr(
                'Provide details about how many buildings fall within '
                'each hazard zone.'),
            'limitations': [],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            'map_title': tr('Buildings affected'),
            'legend_title': tr('Building count'),
            'legend_units': tr('(building)'),
            'legend_notes': tr(
                'Thousand separator is represented by %s' %
                get_thousand_separator()),
            'layer_name': tr('Buildings affected'),
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [layer_geometry_polygon],
                    'hazard_categories': [
                        hazard_category_multiple_event,
                        hazard_category_single_event
                    ],
                    'hazard_types': hazard_all,
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [
                        generic_vector_hazard_classes],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [
                        layer_geometry_point,
                        layer_geometry_polygon
                    ],
                    'exposure_types': [exposure_structure],
                    'exposure_units': [],
                    'exposure_class_fields': [structure_class_field],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('postprocessors', OrderedDict([(
                    'BuildingType',
                    building_type_postprocessor())]))
            ])
        }
        return dict_meta
    def run(self, layers):
        """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
        """

        # Identify hazard and exposure layers
        hazard_layer = get_hazard_layer(layers)  # Tsunami inundation [m]
        exposure_layer = get_exposure_layer(layers)

        question = get_question(
            hazard_layer.get_name(), exposure_layer.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 = hazard_layer.get_data(nan=0.0)  # Depth

        # Calculate impact as population exposed to depths > max threshold
        population = exposure_layer.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))

            # Sensible rounding
            val, rounding = population_rounding_full(val)
            counts.append([val, rounding])

        # Count totals
        evacuated, rounding = counts[-1]
        total = int(numpy.sum(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 = [
            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))

        # 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')

        # Create raster object and return
        raster = Raster(
            impact,
            projection=hazard_layer.get_projection(),
            geotransform=hazard_layer.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': total_needs},
            style_info=style_info)
        return raster
예제 #38
0
    def as_dict():
        """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': 'VolcanoPolygonBuildingFunction',
            'name': tr('Polygon volcano on buildings'),
            'impact': tr('Be affected'),
            'title': tr('Be affected'),
            'function_type': 'old-style',
            'author': 'AIFDR',
            'date_implemented': 'N/A',
            'overview': tr(
                'To assess the impacts of volcano eruption on building.'),
            'detailed_description': '',
            'hazard_input': tr(
                'The hazard layer must be a polygon layer. This layer '
                'must have an attribute representing the volcano hazard '
                'zone that can be specified in the impact function option. '
                'There are three classes low, medium, and high. The default '
                'values are "Kawasan Rawan Bencana I" for low, "Kawasan Rawan '
                'Bencana II" for medium, and "Kawasan  Rawan Bencana III for '
                'high." If you want to see the name of the volcano in the '
                'result, you need to specify the volcano name attribute in '
                'the Impact Function options.'),
            'exposure_input': tr(
                'Vector polygon layer extracted from OSM where each '
                'polygon represents the footprint of a building.'),
            'output': tr(
                'Vector layer contains Map of building exposed to '
                'volcanic hazard zones for each Kawasan Rawan Bencana.'),
            'actions': tr(
                'Provide details about the number of buildings that are '
                'within each hazard zone.'),
            'limitations': [],
            'citations': [
                {
                    'text': None,
                    'link': None
                }
            ],
            'legend_title': tr('Building count'),
            'legend_units': tr('(building)'),
            'legend_notes': tr(
                'Thousand separator is represented by %s' %
                get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [layer_geometry_polygon],
                    'hazard_categories': [
                        hazard_category_multiple_event,
                        hazard_category_single_event
                    ],
                    'hazard_types': [hazard_volcano],
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [
                        volcano_vector_hazard_classes],
                    'raster_hazard_classifications': [],
                    'additional_keywords': [
                        volcano_name_field]
                },
                'exposure': {
                    'layer_mode': layer_mode_classified,
                    'layer_geometries': [
                        layer_geometry_polygon,
                        layer_geometry_point
                    ],
                    'exposure_types': [exposure_structure],
                    'exposure_units': [],
                    'exposure_class_fields': [structure_class_field],
                    'additional_keywords': []
                }
            },
            'parameters': OrderedDict([
                ('postprocessors', OrderedDict([(
                    'BuildingType',
                    building_type_postprocessor())]))
            ])
        }
        return dict_meta
예제 #39
0
    def run(self, layers):
        """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
        """

        # Identify hazard and exposure layers
        hazard_layer = get_hazard_layer(layers)  # Tsunami inundation [m]
        exposure_layer = get_exposure_layer(layers)

        question = get_question(hazard_layer.get_name(),
                                exposure_layer.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 = hazard_layer.get_data(nan=0.0)  # Depth

        # Calculate impact as population exposed to depths > max threshold
        population = exposure_layer.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))

            # Sensible rounding
            val, rounding = population_rounding_full(val)
            counts.append([val, rounding])

        # Count totals
        evacuated, rounding = counts[-1]
        total = int(numpy.sum(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 = [
            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))

        # 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')

        # Create raster object and return
        raster = Raster(impact,
                        projection=hazard_layer.get_projection(),
                        geotransform=hazard_layer.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': total_needs
                        },
                        style_info=style_info)
        return raster
예제 #40
0
    def as_dict():
        """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':
            'TsunamiEvacuationFunction',
            'name':
            tr('Tsunami evacuation'),
            'impact':
            tr('Need evacuation'),
            'title':
            tr('Need evacuation'),
            'function_type':
            'old-style',
            'author':
            'AIFDR',
            'date_implemented':
            'N/A',
            'overview':
            tr('To assess the impacts of tsunami inundation in raster '
               'format on population.'),
            'detailed_description':
            tr('The population subject to inundation exceeding a '
               'threshold (default 0.7m) is calculated and returned as '
               'a raster layer. In addition the total number and the '
               'required needs in terms of the BNPB (Perka 7) are '
               'reported. The threshold can be changed and even contain '
               'multiple numbers in which case evacuation and needs are '
               'calculated using the largest number with population '
               'breakdowns provided for the smaller numbers. The '
               'population raster is resampled to the resolution of the '
               'hazard raster and is rescaled so that the resampled '
               'population counts reflect estimates of population count '
               'per resampled cell. The resulting impact layer has the '
               'same resolution and reflects population count per cell '
               'which are affected by inundation.'),
            'hazard_input':
            tr('A hazard raster layer where each cell represents tsunami '
               'depth (in meters).'),
            'exposure_input':
            tr('An exposure raster layer where each cell represent '
               'population count.'),
            'output':
            tr('Raster layer contains population affected and the minimum '
               'needs based on number of the population affected.'),
            'actions':
            tr('Provide details about how many people would likely need '
               'to be evacuated, where they are located and what '
               'resources would be required to support them.'),
            'limitations': [
                tr('The default threshold of 0.7 meter was selected based on '
                   'consensus, not hard evidence.')
            ],
            'citations': [{
                'text':
                tr('Papadopoulos, Gerassimos A., and Fumihiko Imamura. '
                   '"A proposal for a new tsunami intensity scale." '
                   'ITS 2001 proceedings, no. 5-1, pp. 569-577. 2001.'),
                'link':
                None
            }, {
                'text':
                tr('Hamza Latief. pers com. Default impact threshold for '
                   'tsunami impact on people should be 0.7m. This is '
                   'less than a flood threshold because in a tsunami, '
                   'the water is moving with force.'),
                'link':
                None
            }],
            'legend_title':
            tr('Population'),
            'legend_units':
            tr('(people per cell)'),
            'legend_notes':
            tr('Thousand separator is represented by %s' %
               get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode':
                    layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types': [hazard_tsunami],
                    'continuous_hazard_units': [unit_feet, unit_metres],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications': [],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units': [count_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters':
            OrderedDict([('thresholds', threshold()),
                         ('postprocessors',
                          OrderedDict([
                              ('Gender', default_gender_postprocessor()),
                              ('Age', age_postprocessor()),
                              ('MinimumNeeds', minimum_needs_selector()),
                          ])), ('minimum needs', default_minimum_needs())])
        }
        return dict_meta
예제 #41
0
    def run(self, layers):
        """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
        """

        # The 3 classes
        low_t = self.parameters['low_hazard_class']
        medium_t = self.parameters['medium_hazard_class']
        high_t = self.parameters['high_hazard_class']

        # Identify hazard and exposure layers
        hazard_layer = get_hazard_layer(layers)  # Classified Hazard
        exposure_layer = get_exposure_layer(layers)  # Population Raster

        question = get_question(hazard_layer.get_name(),
                                exposure_layer.get_name(), self)

        # Extract data as numeric arrays
        data = hazard_layer.get_data(nan=0.0)  # Class

        # Calculate impact as population exposed to each class
        population = exposure_layer.get_data(nan=0.0, scaling=True)
        if high_t == 0:
            hi = numpy.where(0, population, 0)
        else:
            hi = numpy.where(data == high_t, population, 0)
        if medium_t == 0:
            med = numpy.where(0, population, 0)
        else:
            med = numpy.where(data == medium_t, population, 0)
        if low_t == 0:
            lo = numpy.where(0, population, 0)
        else:
            lo = numpy.where(data == low_t, population, 0)
        if high_t == 0:
            impact = numpy.where((data == low_t) + (data == medium_t),
                                 population, 0)
        elif medium_t == 0:
            impact = numpy.where((data == low_t) + (data == high_t),
                                 population, 0)
        elif low_t == 0:
            impact = numpy.where((data == medium_t) + (data == high_t),
                                 population, 0)
        else:
            impact = numpy.where(
                (data == low_t) + (data == medium_t) + (data == high_t),
                population, 0)

        # Count totals
        total = int(numpy.sum(population))
        high = int(numpy.sum(hi))
        medium = int(numpy.sum(med))
        low = int(numpy.sum(lo))
        total_impact = int(numpy.sum(impact))

        # Perform population rounding based on number of people
        no_impact = population_rounding(total - total_impact)
        total = population_rounding(total)
        total_impact = population_rounding(total_impact)
        high = population_rounding(high)
        medium = population_rounding(medium)
        low = population_rounding(low)

        minimum_needs = [
            parameter.serialize()
            for parameter in self.parameters['minimum needs']
        ]

        # 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'])
                    ]))

        impact_table = Table(table_body).toNewlineFreeString()

        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)
        ])
        impact_summary = Table(table_body).toNewlineFreeString()

        # 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 = 30
            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('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(impact,
                              projection=hazard_layer.get_projection(),
                              geotransform=hazard_layer.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,
                                  'total_needs': total_needs
                              },
                              style_info=style_info)
        return raster_layer
예제 #42
0
class ITBFatalityFunction(FunctionProvider):
    """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'

    """

    title = tr('Die or be displaced')
    synopsis = tr(
        'To asses 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']),
                              ('elder_ratio', defaults['ELDER_RATIO'])])
             }), ('MinimumNeeds', {
                 'on': True
             })
         ])),
        ('minimum needs', default_minimum_needs())
    ])

    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']
        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,

                my_hazard: Raster layer of MMI ground shaking

                my_exposure: Raster layer of population density
        """

        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
        my_hazard = intensity.get_data()  # Ground Shaking
        my_exposure = population.get_data(scaling=True)  # Population Density

        # Calculate population 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 (my_hazard
        # based on ITB power model
        R = numpy.zeros(my_hazard.shape)
        for mmi in mmi_range:
            # Identify cells where MMI is in class i and
            # count population affected by this shake level
            I = numpy.where((my_hazard > mmi - self.parameters['step']) *
                            (my_hazard <= mmi + self.parameters['step']),
                            my_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 = int(round(numpy.nansum(my_exposure.flat) / 1000) * 1000)

        # Compute number of fatalities
        fatalities = int(
            round(numpy.nansum(number_of_fatalities.values()) / 1000)) * 1000
        # 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 = int(
            round(numpy.nansum(number_of_displaced.values()) / 1000)) * 1000

        # 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))

        # Calculate estimated needs based on BNPB Perka 7/2008 minimum bantuan
        # FIXME: Refactor and share
        minimum_needs = self.parameters['minimum needs']
        needs = evacuated_population_weekly_needs(displaced, 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 density estimate of '
                   'displaced population')),
            TableRow([tr('Needs per week'), tr('Total')], header=True),
            [tr('Rice [kg]'), format_int(needs['rice'])],
            [tr('Drinking Water [l]'),
             format_int(needs['drinking_water'])],
            [tr('Clean Water [l]'),
             format_int(needs['water'])],
            [tr('Family Kits'),
             format_int(needs['family_kits'])],
            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 fractionals.')
        ])

        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 to nearest 1000.'))

        # 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 density')

        # Create raster object and return
        L = Raster(R,
                   projection=population.get_projection(),
                   geotransform=population.get_geotransform(),
                   keywords={
                       'impact_summary': impact_summary,
                       'total_population': total,
                       'total_fatalities': fatalities,
                       'fatalites_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
                   },
                   name=tr('Estimated displaced population per cell'),
                   style_info=style_info)

        return L
예제 #43
0
    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:
            table_body = [
                self.question,
                TableRow(
                    [tr('People impacted'),
                     '%s' % format_int(total_affected_population)],
                    header=True)]
            message = Table(table_body).toNewlineFreeString()
            raise ZeroImpactException(message)

        impact_table = impact_summary = self.generate_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
예제 #44
0
    def run(self):
        """Run volcano point population evacuation Impact Function.

        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()

        self.provenance.append_step(
            'Calculating Step', 'Impact function is calculating the impact.')

        # Parameters
        radii = self.parameters['distances'].value

        # Get parameters from layer's keywords
        volcano_name_attribute = self.hazard.keyword('volcano_name_field')

        # Input checks
        if not self.hazard.layer.is_point_data:
            msg = (
                'Input hazard must be a polygon or point layer. I got %s with '
                'layer type %s' %
                (self.hazard.name, self.hazard.layer.get_geometry_name()))
            raise Exception(msg)

        data_table = self.hazard.layer.get_data()

        # Use concentric circles
        category_title = 'Radius'

        centers = self.hazard.layer.get_geometry()
        hazard_layer = buffer_points(centers,
                                     radii,
                                     category_title,
                                     data_table=data_table)

        # Get names of volcanoes considered
        if volcano_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[volcano_name_attribute])

            volcano_names = ''
            for radius in volcano_name_list:
                volcano_names += '%s, ' % radius
            self.volcano_names = volcano_names[:-2]  # Strip trailing ', '

        # Run interpolation function for polygon2raster
        interpolated_layer, covered_exposure_layer = \
            assign_hazard_values_to_exposure_data(
                hazard_layer,
                self.exposure.layer,
                attribute_name=self.target_field
            )

        # Initialise affected population per categories
        for radius in radii:
            category = 'Radius %s km ' % format_int(radius)
            self.affected_population[category] = 0

        if has_no_data(self.exposure.layer.get_data(nan=True)):
            self.no_data_warning = True
        # 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 = 'Radius %s km ' % format_int(row[category_title])
                self.affected_population[category] += population

        # Count totals
        self.total_population = population_rounding(
            int(numpy.nansum(self.exposure.layer.get_data())))

        self.minimum_needs = [
            parameter.serialize() for parameter in filter_needs_parameters(
                self.parameters['minimum needs'])
        ]

        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])

            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')

        # For printing map purpose
        map_title = tr('People affected by the buffered point volcano')
        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
        extra_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': self.total_needs
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        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 the buffered point volcano'),
            keywords=impact_layer_keywords,
            style_info=style_info)

        self._impact = impact_layer
        return impact_layer
예제 #45
0
    def run(self, layers):
        """Risk plugin for volcano hazard on building/structure

        Input
          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

        Counts number of building exposed to each volcano hazard zones.

        Return
          Map of building exposed to volcanic hazard zones
          Table with number of buildings affected
        """

        # 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 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))
            # 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('Volcanos 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
예제 #46
0
    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
예제 #47
0
    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
예제 #48
0
    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()

        # 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]
            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('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())

        # 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_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
예제 #49
0
    def as_dict():
        """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':
            'ClassifiedRasterHazardPopulationFunction',
            'name':
            tr('Classified raster hazard on population'),
            'impact':
            tr('Be affected'),
            'title':
            tr('Be affected'),
            'function_type':
            'old-style',
            'author':
            'Dianne Bencito',
            'date_implemented':
            'N/A',
            'overview':
            tr('To assess the impacts of classified hazards in raster '
               'format on a population raster layer.'),
            'detailed_description':
            tr('This function will treat the values in the hazard raster '
               'layer as classes representing low, medium and high '
               'impact. You need to ensure that the keywords for the hazard '
               'layer have been set appropriately to define these classes.'
               'The number of people that will be affected will be '
               'calculated for each class. The report will show the total '
               'number of people that will be affected for each '
               'hazard class.'),
            'hazard_input':
            tr('A hazard raster layer where each cell represents the '
               'class of the hazard. There should be three classes: e.g. '
               '1, 2, and 3.'),
            'exposure_input':
            tr('An exposure raster layer where each cell represents the '
               'population count for that cell.'),
            'output':
            tr('Map of population exposed to the highest class and a table '
               'with the number of people in each class'),
            'actions':
            tr('Provide details about how many people would likely be '
               'affected for each hazard class.'),
            'limitations': [tr('The number of classes is three.')],
            'citations': [{
                'text': None,
                'link': None
            }],
            'legend_title':
            tr('Number of People'),
            'legend_units':
            tr('(people per cell)'),
            'legend_notes':
            tr('Thousand separator is represented by %s' %
               get_thousand_separator()),
            'layer_requirements': {
                'hazard': {
                    'layer_mode':
                    layer_mode_classified,
                    'layer_geometries': [layer_geometry_raster],
                    'hazard_categories': [
                        hazard_category_single_event,
                        hazard_category_multiple_event
                    ],
                    'hazard_types':
                    hazard_all,
                    'continuous_hazard_units': [],
                    'vector_hazard_classifications': [],
                    'raster_hazard_classifications':
                    [generic_raster_hazard_classes],
                    'additional_keywords': []
                },
                'exposure': {
                    'layer_mode': layer_mode_continuous,
                    'layer_geometries': [layer_geometry_raster],
                    'exposure_types': [exposure_population],
                    'exposure_units':
                    [count_exposure_unit, density_exposure_unit],
                    'exposure_class_fields': [],
                    'additional_keywords': []
                }
            },
            'parameters':
            OrderedDict([('Categorical hazards', categorical_hazards()),
                         ('postprocessors',
                          OrderedDict([
                              ('Gender', default_gender_postprocessor()),
                              ('Age', age_postprocessor()),
                              ('MinimumNeeds', minimum_needs_selector()),
                          ])), ('minimum needs', default_minimum_needs())])
        }
        return dict_meta
    def run(self, layers):
        """Risk plugin for flood population evacuation

        Input
          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.

        Return
          Map of population exposed to flood levels exceeding the threshold
          Table with number of people evacuated and supplies required
        """

        # 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
        D = my_hazard.get_data(nan=0.0)  # Depth

        # Calculate impact as population exposed to depths > max threshold
        P = my_exposure.get_data(nan=0.0, scaling=True)

        # Calculate impact to intermediate thresholds
        counts = []
        # merely initialize
        my_impact = None
        for i, lo in enumerate(thresholds):
            if i == len(thresholds) - 1:
                # The last threshold
                my_impact = M = numpy.where(D >= lo, P, 0)
            else:
                # Intermediate thresholds
                hi = thresholds[i + 1]
                M = numpy.where((D >= lo) * (D < hi), P, 0)

            # Count
            val = int(numpy.sum(M))

            # Don't show digits less than a 1000
            val = round_thousand(val)
            counts.append(val)

        # Count totals
        evacuated = counts[-1]
        total = int(numpy.sum(P))
        # 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'),
                     header=False),
            TableRow(tr('Map shows population density needing evacuation')),
            TableRow(tr('Table below shows the weekly minium 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 fractionals.')])

        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, header=False))

        # Result
        impact_summary = Table(table_body).toNewlineFreeString()
        impact_table = impact_summary

        # check for zero impact
        if numpy.nanmax(my_impact) == 0 == numpy.nanmin(my_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(my_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
        R = Raster(my_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},
                   style_info=style_info)
        return R
예제 #51
0
    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
예제 #52
0
    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()

        self.provenance.append_step("Calculating Step", "Impact function is calculating the impact.")

        # Value from layer's keywords
        self.hazard_class_attribute = self.hazard.keyword("field")
        self.hazard_class_mapping = self.hazard.keyword("value_map")
        # TODO: Remove check to self.validate (Ismail)
        # Input checks
        message = tr(
            "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(message)

        # Check if hazard_class_attribute exists in hazard_layer
        if self.hazard_class_attribute not in self.hazard.layer.get_attribute_names():
            message = (
                "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(message)

        # Retrieve the classification that is used by the hazard layer.
        vector_hazard_classification = self.hazard.keyword("vector_hazard_classification")
        # Get the dictionary that contains the definition of the classification
        vector_hazard_classification = definition(vector_hazard_classification)
        # Get the list classes in the classification
        vector_hazard_classes = vector_hazard_classification["classes"]
        # Initialize OrderedDict of affected buildings
        self.affected_population = OrderedDict()
        # Iterate over vector hazard classes
        for vector_hazard_class in vector_hazard_classes:
            # Check if the key of class exist in hazard_class_mapping
            if vector_hazard_class["key"] in self.hazard_class_mapping.keys():
                # Replace the key with the name as we need to show the human
                # friendly name in the report.
                self.hazard_class_mapping[vector_hazard_class["name"]] = self.hazard_class_mapping.pop(
                    vector_hazard_class["key"]
                )
                # Adding the class name as a key in affected_building
                self.affected_population[vector_hazard_class["name"]] = 0

        # 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
        )

        # 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_value = get_key_for_value(row[self.hazard_class_attribute], self.hazard_class_mapping)
                if not hazard_value:
                    hazard_value = self._not_affected_value
                self.affected_population[hazard_value] += 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])

            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")

        # 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())

        extra_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,
        }

        self.set_if_provenance()

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # 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_layer_keywords,
            style_info=style_info,
        )

        self._impact = impact_layer
        return impact_layer
예제 #53
0
    def run(self, layers=None):
        """Risk plugin for classified polygon hazard on building/structure.

        Counts number of building exposed to each hazard zones.

        :param layers: List of layers expected to contain.
                * hazard_layer: Hazard layer
                * exposure_layer: Vector layer of structure data on
                the same grid as hazard_layer

        :returns: Map of building exposed to each hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare(layers)

        # Target Field
        target_field = 'zone'

        # Not affected string in the target field
        not_affected_value = 'Not Affected'

        # Parameters
        hazard_zone_attribute = self.parameters['hazard zone attribute']

        # Identify hazard and exposure layers
        hazard_layer = self.hazard
        exposure_layer = self.exposure

        # Input checks
        if not hazard_layer.is_polygon_data:
            message = (
                'Input hazard must be a polygon. I got %s with '
                'layer type %s' %
                (hazard_layer.get_name(), hazard_layer.get_geometry_name()))
            raise Exception(message)

        # Check if hazard_zone_attribute exists in hazard_layer
        if hazard_zone_attribute not in hazard_layer.get_attribute_names():
            message = (
                'Hazard data %s does not contain expected attribute %s ' %
                (hazard_layer.get_name(), hazard_zone_attribute))
            # noinspection PyExceptionInherit
            raise InaSAFEError(message)

        # Find the target field name that has no conflict with default
        # target
        attribute_names = hazard_layer.get_attribute_names()
        target_field = get_non_conflicting_attribute_name(
            target_field, attribute_names)

        # Hazard zone categories from hazard layer
        self.hazard_zones = list(
            set(hazard_layer.get_data(hazard_zone_attribute)))

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for hazard_zone in self.hazard_zones:
            self.affected_buildings[hazard_zone] = {}

        # Run interpolation function for polygon2polygon
        interpolated_layer = assign_hazard_values_to_exposure_data(
            hazard_layer, exposure_layer, attribute_name=None)

        # Extract relevant interpolated data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        for i in range(len(features)):
            hazard_value = features[i][hazard_zone_attribute]
            if not hazard_value:
                hazard_value = not_affected_value
            features[i][target_field] = hazard_value
            usage = get_osm_building_usage(attribute_names, features[i])
            if usage is None:
                usage = tr('Unknown')
            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][usage] = OrderedDict(
                        [(tr('Buildings Affected'), 0)])
            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.generate_html_report()

        # Create style
        categories = self.hazard_zones
        categories.append(not_affected_value)
        colours = color_ramp(len(categories))
        style_classes = []

        i = 0
        for hazard_zone in self.hazard_zones:
            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)
            i += 1

        # Override style info with new classes and name
        style_info = dict(target_field=target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by each 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
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by each hazard zone'),
            keywords={'impact_summary': impact_summary,
                      'impact_table': impact_table,
                      'target_field': 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
예제 #54
0
    def run(self, layers=None):
        """Counts number of building exposed to each volcano hazard zones.

        :param layers: List of layers expected to contain.
                * hazard_layer: Hazard layer of volcano
                * exposure_layer: Vector layer of structure data on
                the same grid as hazard_layer

        :returns: Map of building exposed to volcanic hazard zones.
                  Table with number of buildings affected
        :rtype: dict
        """
        self.validate()
        self.prepare(layers)
        # Target Field
        target_field = 'zone'
        # Hazard Zone Attribute
        hazard_zone_attribute = 'radius'
        # Not Affected Value
        not_affected_value = 'Not Affected'

        # Parameters
        radii = self.parameters['distances [km]']
        volcano_name_attribute = self.parameters['volcano name attribute']

        # Identify hazard and exposure layers
        hazard_layer = self.hazard  # Volcano hazard layer
        exposure_layer = self.exposure  # Building exposure layer

        # Input checks
        if not hazard_layer.is_point_data:
            message = (
                'Input hazard must be a vector point layer. I got %s '
                'with layer type %s' % (
                    hazard_layer.get_name(), hazard_layer.get_geometry_name()))
            raise Exception(message)

        # Make hazard layer by buffering the point
        centers = hazard_layer.get_geometry()
        features = hazard_layer.get_data()
        radii_meter = [x * 1000 for x in radii]  # Convert to meters
        hazard_layer = buffer_points(
            centers,
            radii_meter,
            hazard_zone_attribute,
            data_table=features)
        # Category names for the impact zone
        category_names = radii_meter
        category_names.append(not_affected_value)

        # Get names of volcanoes considered
        if volcano_name_attribute in hazard_layer.get_attribute_names():
            volcano_name_list = set()
            for row in hazard_layer.get_data():
                # Run through all polygons and get unique names
                volcano_name_list.add(row[volcano_name_attribute])
            self.volcano_names = ', '.join(volcano_name_list)

        # Find the target field name that has no conflict with the attribute
        # names in the hazard layer
        hazard_attribute_names = hazard_layer.get_attribute_names()
        target_field = get_non_conflicting_attribute_name(
            target_field, hazard_attribute_names)

        # Run interpolation function for polygon2polygon
        interpolated_layer = assign_hazard_values_to_exposure_data(
            hazard_layer, exposure_layer, attribute_name=None)

        # Extract relevant interpolated layer data
        attribute_names = interpolated_layer.get_attribute_names()
        features = interpolated_layer.get_data()

        self.buildings = {}
        self.affected_buildings = OrderedDict()
        for category in radii_meter:
            self.affected_buildings[category] = {}

        # Iterate the interpolated building layer
        for i in range(len(features)):
            hazard_value = features[i][hazard_zone_attribute]
            if not hazard_value:
                hazard_value = not_affected_value
            features[i][target_field] = hazard_value

            # Count affected buildings by usage type if available
            usage = get_osm_building_usage(attribute_names, features[i])
            if usage is [None, 'NULL', 'null', 'Null', 0]:
                usage = tr('Unknown')

            if usage not in self.buildings:
                self.buildings[usage] = 0
                for category in self.affected_buildings.keys():
                    self.affected_buildings[category][
                        usage] = OrderedDict([
                            (tr('Buildings Affected'), 0)])

            self.buildings[usage] += 1
            if hazard_value in self.affected_buildings.keys():
                self.affected_buildings[hazard_value][usage][
                    tr('Buildings Affected')] += 1

        # Lump small entries and 'unknown' into 'other' category
        self._consolidate_to_other()

        # Generate simple impact report
        impact_summary = impact_table = self.generate_html_report()

        # Create style
        colours = ['#FFFFFF', '#38A800', '#79C900', '#CEED00',
                   '#FFCC00', '#FF6600', '#FF0000', '#7A0000']
        colours = colours[::-1]  # flip
        colours = colours[:len(category_names)]
        style_classes = []

        i = 0
        for category_name in category_names:
            style_class = dict()
            style_class['label'] = tr(category_name)
            style_class['transparency'] = 0
            style_class['value'] = category_name
            style_class['size'] = 1

            if i >= len(category_names):
                i = len(category_names) - 1
            style_class['colour'] = colours[i]
            i += 1

            style_classes.append(style_class)

        # Override style info with new classes and name
        style_info = dict(target_field=target_field,
                          style_classes=style_classes,
                          style_type='categorizedSymbol')

        # For printing map purpose
        map_title = tr('Buildings affected by volcanic buffered point')
        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
        impact_layer = Vector(
            data=features,
            projection=interpolated_layer.get_projection(),
            geometry=interpolated_layer.get_geometry(),
            name=tr('Buildings affected by volcanic buffered point'),
            keywords={'impact_summary': impact_summary,
                      'impact_table': impact_table,
                      'target_field': 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
예제 #55
0
    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