Esempio n. 1
0
    def test_get_impact_function_class(self):
        """TestRegistry: Test we can get an impact function class."""
        # Getting an IF class using its class name
        registry = Registry()
        expected = 'FloodPolygonBuildingFunction'
        impact_function = registry.get_class(expected)

        # Check that it should be a class, not an instance
        message = 'Expecting a class, not an object'
        self.assertTrue(inspect.isclass(impact_function), message)

        # Check the IF name the same
        result = impact_function.__name__
        message = 'Expecting %s. Got %s instead.' % (expected, result)
        self.assertEqual(expected, result, message)
Esempio n. 2
0
    def test_get_impact_function_class(self):
        """TestRegistry: Test we can get an impact function class."""
        # Getting an IF class using its class name
        registry = Registry()
        expected = 'FloodPolygonBuildingFunction'
        impact_function = registry.get_class(expected)

        # Check that it should be a class, not an instance
        message = 'Expecting a class, not an object'
        self.assertTrue(inspect.isclass(impact_function), message)

        # Check the IF name the same
        result = impact_function.__name__
        message = 'Expecting %s. Got %s instead.' % (expected, result)
        self.assertEqual(expected, result, message)
class ImpactFunctionManager(object):
    """Class for managing metadata for all impact function.

    .. versionadded:: 2.1
    """

    def __init__(self):
        """Constructor."""
        # Singleton Registry to track all the registered Impact Functions
        self.registry = Registry()

    @property
    def impact_functions(self):
        """Return all registered impact functions."""
        return self.registry.impact_functions

    def get_instance(self, class_name):
        """Return an instance of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if =  if_manager.get_instance(if_class_name)

        :param class_name: The name of IF class.
        :type class_name: str

        :return: Impact function instance that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_instance(class_name)

    def get_class(self, class_name):
        """Return the class of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if_class =  if_manager.get_class(if_class_name)

        :param class_name: the name of IF class
        :type class_name: str

        :return: impact function class that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_class(class_name)

    def get(self, impact_function_id):
        """Return an instance of an impact function given its ID.

        This is a preferred way to get an instance of IF. IF should have a
        unique human readable ID in their metadata.

        .. example::

            if_manager = ImpactFunctionManager()
            if_id = 'FloodBuildingImpactFunction'
            if =  if_manager.get(if_id)

        :param impact_function_id: The ID of impact function in the metadata.
        :type impact_function_id: str

        :return An Impact function instance that has matched id.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        impact_functions = self.registry.filter_by_metadata("id", impact_function_id)
        if len(impact_functions) == 0:
            raise Exception("Impact function with ID: %s not found" % impact_function_id)
        elif len(impact_functions) > 1:
            raise Exception("There are some Impact Functions that have the same ID: %s" % impact_function_id)
        return impact_functions[0].instance()

    def filter(self, hazard_metadata=None, exposure_metadata=None):
        """Get available impact functions from hazard and exposure metadata.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_metadata = {
                'subcategory': hazard_flood,
                'units': unit_wetdry,
                'layer_constraints': layer_vector_polygon
            }
            exposure_metadata = {
                'subcategory': exposure_structure,
                'units': unit_building_type_type,
                'layer_constraints': layer_vector_polygon
            }
            ifs =  if_manager.filter(hazard_metadata, exposure_metadata)

        :param hazard_metadata: The metadata of the hazard.
        :type hazard_metadata: dict

        :param exposure_metadata: The metadata of the exposure.
        :type exposure_metadata: dict
        """
        return self.registry.filter(hazard_metadata, exposure_metadata)

    def filter_by_keywords(self, hazard_keywords=None, exposure_keywords=None):
        """Get available impact functions from hazard and exposure keywords.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_keywords = {
                'subcategory': 'flood',
                'units': 'wetdry',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            exposure_keywords = {
                'subcategory': 'structure',
                'units': 'building_type',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            ifs =  if_manager.filter_by_keywords(hazard_keywords,
            exposure_keywords)

        :param hazard_keywords: The keywords of the hazard.
        :type hazard_keywords: dict

        :param exposure_keywords: The keywords of the exposure.
        :type exposure_keywords: dict
        """
        return self.registry.filter_by_keyword_string(hazard_keywords, exposure_keywords)

    def filter_by_metadata(self, metadata_key, metadata_value):
        """Return IF classes given its metadata key and value.

        .. example::

            if_manager = ImpactFunctionManager()
            metadata_key = 'author'
            metadata_value = 'Akbar Gumbira'
            ifs =  if_manager.filter_by_metadata(metadata_key,
            metadata_value)

        :param metadata_key: The key of the metadata e.g 'id', 'name'
        :type metadata_key: str

        :param metadata_value: The value of the metadata, e.g for the key
            'id' the value is 'FloodNativePolygonExperimentalFunction'
        :type metadata_value: str, dict

        :return: Impact Function classes match the arguments
        :rtype: list
        """
        return self.registry.filter_by_metadata(metadata_key, metadata_value)

    @staticmethod
    def get_function_id(impact_function):
        """Get the ID of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The ID of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get("id", None)

    @staticmethod
    def get_function_title(impact_function):
        """Get title of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The title of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get("title", None)

    @staticmethod
    def get_function_name(impact_function):
        """Get the human readable name of the impact function.

        :param impact_function: Class of an impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.metadata().as_dict().get("name", None)

    @staticmethod
    def get_function_type(impact_function):
        """Return the impact function type.

        :param impact_function: The impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.function_type()

    def allowed_subcategories(self, category=None):
        """Determine allowed subcategories, optionally filtered by category.

        :param category: Optional category which will be used to subset the
            allowed subcategories. If omitted, all supported subcategories will
            be returned (for both hazard and exposure). Default is None.
        :type category: str

        :returns: A list of strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_subcategories = impact_function.metadata().allowed_subcategories(category)
            result = add_to_list(result, my_allowed_subcategories)
        return result

    def allowed_data_types(self, subcategory):
        """Determine allowed data types for all impact functions.

        It uses subcategory as a filter.

        Passing a subcategory is required otherwise the context of the
        data_type(s) would be ambiguous (i.e. whether they can be used as
        exposure or hazards).

        :param subcategory: Required subcategory which will be used to subset
            the allowed data_types.
        :type subcategory: str

        :returns: A list of one or more strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_data_types = impact_function.metadata().allowed_data_types(subcategory)
            result = add_to_list(result, my_allowed_data_types)
        return result

    def allowed_units(self, subcategory, data_type):
        """Determine allowed units from all impact functions.


        It uses subcategory and data_type as a filter.

        .. note:: One data_type could be  used by more than one subcategory,
            so we need to explicitly pass the subcategory to this function.

        :param subcategory: Required subcategory which will be used to subset
            the allowed data_types.
        :type subcategory: str

        :param data_type: Required data_type which will be used to subset the
            allowed units.
        :type data_type: str

        :returns: A list of one or more strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_units = impact_function.metadata().allowed_units(subcategory, data_type)
            result = add_to_list(result, my_allowed_units)
        return result

    def units_for_layer(self, subcategory, layer_type, data_type):
        """Get the valid units for a layer.

        Example usage::

            foo  = units_for_layer('flood', 'vector', 'polygon')
            print foo

        Would output this::

            {'Wet/Dry': ['wet','dry']}

        While passing a raster layer::

            foo  = units_for_layer('flood', 'raster', None)
            print foo

        Might return this::

            {
                'metres': None,
                'feet': None,
                'wet/dry': ['wet', 'dry'],
            }

        In the returned dictionary the keys are unit types and
        the values are the categories (if any) applicable for that unit type.

        :param subcategory: The subcategory for this layer.
        :type subcategory: str

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :returns: A dictionary as per the example above where each key
            represents a unit and each value that is not None represents a
            list of categories.
        :rtype: dict
        """
        result = []
        for impact_function in self.impact_functions:
            my_units = impact_function.metadata().units_for_layer(subcategory, layer_type, data_type)
            result = add_to_list(result, my_units)
        return result

    def categories_for_layer(self, layer_type, data_type):
        """Return a list of valid categories for a layer.

        This method is used to determine if a given layer can be used as a
        hazard, exposure or aggregation layer.

        Example usage::

            foo  = categories_for_layer('vector', 'polygon')
            print foo

        Would output this::

            ['hazard', 'exposure', 'aggregation']

        While passing a vector point layer::

            foo  = units_for_layer('vector', 'point')
            print foo

        Might return this::

            ['hazard', 'exposure']

        In the returned the values are categories (if any) applicable for that
        layer_type and data_type.

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :returns: A list as per the example above where each value represents
            a valid category.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            categories = impact_function.metadata().categories_for_layer(layer_type, data_type)
            result = add_to_list(result, categories)
        categories_definitions = []
        for category in result:
            if category == "hazard":
                categories_definitions.append(hazard_definition)
            elif category == "exposure":
                categories_definitions.append(exposure_definition)
            else:
                raise Exception("Unsupported categories")
        return categories_definitions

    def subcategories_for_layer(self, category, layer_type, data_type):
        """Return a list of valid subcategories for a layer.

        This method is used to determine which subcategories a given layer
        can be for.

        Example usage::

            foo  = subcategories_for_layer('vector', 'polygon', 'exposure')
            print foo

        Would output this::

            ['flood', 'landuse']

        In the returned the values are categories (if any) applicable for that
        layer_type and data_type.

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :param category: The category for this layer. Valid possibilities
            would be 'hazard', 'exposure' and 'aggregation'.
        :type category: str


        :returns: A list as per the example above where each value represents
            a valid subcategory.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            subcategories = impact_function.metadata().subcategories_for_layer(category, layer_type, data_type)
            result = add_to_list(result, subcategories)
        return result

    def get_available_hazards(self, impact_function=None, ascending=True):
        """Return a list of valid available hazards for an impact function.

        If impact_function is None, return all available hazards

        .. versionadded:: 2.2

        :param impact_function: Impact Function object.
        :type impact_function: FunctionProvider

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of hazard full metadata.
        :rtype: list
        """

        hazards = []
        if impact_function is None:
            for impact_function in self.impact_functions:
                add_to_list(hazards, impact_function.metadata().get_hazards())

        else:
            # noinspection PyUnresolvedReferences
            hazards = impact_function.metadata().get_hazards()

        # make it sorted
        if ascending:
            hazards = sorted(hazards, key=lambda k: k["id"])

        return hazards

    def get_functions_for_hazard(self, hazard):
        """Return all function metadata that has hazard in their metadata.

        .. versionadded:: 2.2

        :param hazard: Dictionary that represent the hazard
        :type hazard: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard(hazard):
                impact_functions_metadata.append(impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_hazard_id(self, hazard_id):
        """Return all function metadata that has hazard_id in their metadata.

        .. versionadded:: 2.2

        :param hazard_id: String that represent the hazard id.
        :type hazard_id: str

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard_id(hazard_id):
                impact_functions_metadata.append(impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_available_exposures(self, impact_function=None, ascending=True):
        """Return a list of valid available exposures for an impact function.

        If impact_function is None, return all available exposures

        .. versionadded:: 2.2

        :param impact_function: Impact Function object.
        :type impact_function: FunctionProvider

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of exposures full metadata.
        :rtype: list
        """

        exposures = []
        if impact_function is None:
            for impact_function in self.impact_functions:
                add_to_list(exposures, impact_function.metadata().get_exposures())

        else:
            # noinspection PyUnresolvedReferences
            exposures = impact_function.metadata().get_exposures()

        # make it sorted
        if ascending:
            exposures = sorted(exposures, key=lambda k: k["id"])

        return exposures

    def get_functions_for_exposure(self, exposure):
        """Return all function metadata that has exposure in their metadata.

        .. versionadded:: 2.2

        :param exposure: Dictionary that represent the exposure
        :type exposure: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_exposure(exposure):
                impact_functions_metadata.append(impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_exposure_id(self, exposure_id):
        """Return all function metadata that has exposure_id in their metadata.

        .. versionadded:: 2.2

        :param exposure_id: String that represent the exposure id.
        :type exposure_id: str

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_exposure_id(exposure_id):
                impact_functions_metadata.append(impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_constraint(self, hazard, exposure, hazard_constraint=None, exposure_constraint=None):
        """Get all impact functions that match the hazard n exposure metadata.

        :param hazard: The hazard metadata.
        :type hazard: dict

        :param exposure: The exposure metadata.
        :type exposure: dict

        :param hazard_constraint:
        :param exposure_constraint:
        :return:
        """
        result = []
        if_hazard = self.get_functions_for_hazard(hazard)
        if_exposure = self.get_functions_for_exposure(exposure)
        if_exposure_id = [function["id"] for function in if_exposure]

        for f in if_hazard:
            if f["id"] in if_exposure_id:
                if not hazard_constraint or hazard_constraint in f["categories"]["hazard"]["layer_constraints"]:
                    if (
                        not exposure_constraint
                        or exposure_constraint in f["categories"]["exposure"]["layer_constraints"]
                    ):
                        result.append(f)

        return result
Esempio n. 4
0
class ImpactFunctionManager(object):
    """Class for managing metadata for all impact function.

    .. versionadded:: 2.1
    """
    def __init__(self):
        """Constructor."""
        # Singleton Registry to track all the registered Impact Functions
        self.registry = Registry()

    @property
    def impact_functions(self):
        """Return all registered impact functions."""
        return self.registry.impact_functions

    def get_instance(self, class_name):
        """Return an instance of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if =  if_manager.get_instance(if_class_name)

        :param class_name: The name of IF class.
        :type class_name: str

        :return: Impact function instance that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_instance(class_name)

    def get_class(self, class_name):
        """Return the class of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if_class =  if_manager.get_class(if_class_name)

        :param class_name: the name of IF class
        :type class_name: str

        :return: impact function class that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_class(class_name)

    def get(self, impact_function_id):
        """Return an instance of an impact function given its ID.

        This is a preferred way to get an instance of IF. IF should have a
        unique human readable ID in their metadata.

        .. example::

            if_manager = ImpactFunctionManager()
            if_id = 'FloodBuildingImpactFunction'
            if =  if_manager.get(if_id)

        :param impact_function_id: The ID of impact function in the metadata.
        :type impact_function_id: str

        :return An Impact function instance that has matched id.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        impact_functions = self.registry.filter_by_metadata(
            'id', impact_function_id)
        if len(impact_functions) == 0:
            raise Exception('Impact function with ID: %s not found' %
                            impact_function_id)
        elif len(impact_functions) > 1:
            raise Exception(
                'There are some Impact Functions that have the same ID: %s' %
                impact_function_id)
        return impact_functions[0].instance()

    def filter(self, hazard_metadata=None, exposure_metadata=None):
        """Get available impact functions from hazard and exposure metadata.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_metadata = {
                'subcategory': hazard_flood,
                'units': unit_wetdry,
                'layer_constraints': layer_vector_polygon
            }
            exposure_metadata = {
                'subcategory': exposure_structure,
                'units': unit_building_type_type,
                'layer_constraints': layer_vector_polygon
            }
            ifs =  if_manager.filter(hazard_metadata, exposure_metadata)

        :param hazard_metadata: The metadata of the hazard.
        :type hazard_metadata: dict

        :param exposure_metadata: The metadata of the exposure.
        :type exposure_metadata: dict
        """
        return self.registry.filter(hazard_metadata, exposure_metadata)

    def filter_by_keywords(self, hazard_keywords=None, exposure_keywords=None):
        """Get available impact functions from hazard and exposure keywords.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_keywords = {
                'subcategory': 'flood',
                'units': 'wetdry',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            exposure_keywords = {
                'subcategory': 'structure',
                'units': 'building_type',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            ifs =  if_manager.filter_by_keywords(hazard_keywords,
            exposure_keywords)

        :param hazard_keywords: The keywords of the hazard.
        :type hazard_keywords: dict

        :param exposure_keywords: The keywords of the exposure.
        :type exposure_keywords: dict
        """
        return self.registry.filter_by_keyword_string(hazard_keywords,
                                                      exposure_keywords)

    def filter_by_metadata(self, metadata_key, metadata_value):
        """Return IF classes given its metadata key and value.

        .. example::

            if_manager = ImpactFunctionManager()
            metadata_key = 'author'
            metadata_value = 'Akbar Gumbira'
            ifs =  if_manager.filter_by_metadata(metadata_key,
            metadata_value)

        :param metadata_key: The key of the metadata e.g 'id', 'name'
        :type metadata_key: str

        :param metadata_value: The value of the metadata, e.g for the key
            'id' the value is 'FloodNativePolygonExperimentalFunction'
        :type metadata_value: str, dict

        :return: Impact Function classes match the arguments
        :rtype: list
        """
        return self.registry.filter_by_metadata(metadata_key, metadata_value)

    @staticmethod
    def get_function_id(impact_function):
        """Get the ID of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The ID of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('id', None)

    @staticmethod
    def get_function_title(impact_function):
        """Get title of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The title of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('title', None)

    @staticmethod
    def get_function_name(impact_function):
        """Get the human readable name of the impact function.

        :param impact_function: Class of an impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.metadata().as_dict().get('name', None)

    @staticmethod
    def get_function_type(impact_function):
        """Return the impact function type.

        :param impact_function: The impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.function_type()

    def get_functions_for_hazard(self, hazard):
        """Return all function metadata that has hazard in their metadata.

        .. versionadded:: 2.2

        :param hazard: Dictionary that represent the hazard
        :type hazard: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard(hazard):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def purposes_for_layer(self, layer_geometry_key):
        """Get purposes of a layer geometry id.

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        """
        layer_purposes = []
        for impact_function in self.impact_functions:
            if_layer_purposes = impact_function.metadata().purposes_for_layer(
                layer_geometry_key)
            if if_layer_purposes:
                add_to_list(layer_purposes, if_layer_purposes)

        return layer_purposes

    def hazard_categories_for_layer(self, layer_geometry_key, hazard_key=None):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :param hazard_key: The hazard key
        :type hazard_key: str

        :returns: List of hazard_categories
        :rtype: list
        """
        hazard_categories = []
        for impact_function in self.impact_functions:
            if_hazard_categories = impact_function.metadata()\
                .hazard_categories_for_layer(layer_geometry_key, hazard_key)
            if if_hazard_categories:
                add_to_list(hazard_categories, if_hazard_categories)

        return hazard_categories

    def hazards_for_layer(self, layer_geometry_key, hazard_category_key=None):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :param hazard_category_key: The hazard category
        :type hazard_category_key: str

        :returns: List of hazard
        :rtype: list
        """
        hazards = []
        for impact_function in self.impact_functions:
            if_hazards = impact_function.metadata().hazards_for_layer(
                layer_geometry_key, hazard_category_key)
            if if_hazards:
                add_to_list(hazards, if_hazards)

        return hazards

    def exposures_for_layer(self, layer_geometry_key):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :returns: List of hazard
        :rtype: list
        """
        exposures = []
        for impact_function in self.impact_functions:
            if_exposures = impact_function.metadata().exposures_for_layer(
                layer_geometry_key)
            if if_exposures:
                add_to_list(exposures, if_exposures)

        return exposures

    def exposure_units_for_layer(self, exposure_key, layer_geometry_key,
                                 layer_mode_key):
        """Get hazard categories form layer_geometry_key

        :param exposure_key: The exposure key
        :type exposure_key: str

        :param layer_geometry_key: The geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :returns: List of exposure unit
        :rtype: list
        """
        exposure_units = []
        for impact_function in self.impact_functions:
            if_exposure_units = impact_function.metadata().\
                exposure_units_for_layer(
                    exposure_key, layer_geometry_key, layer_mode_key)
            if if_exposure_units:
                add_to_list(exposure_units, if_exposure_units)

        return exposure_units

    def continuous_hazards_units_for_layer(self, hazard_key,
                                           layer_geometry_key, layer_mode_key,
                                           hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of continuous hazard unit
        :rtype: list
        """
        continuous_hazards_units = []
        for impact_function in self.impact_functions:
            if_continuous_hazard_units = impact_function.metadata().\
                continuous_hazards_units_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_continuous_hazard_units:
                add_to_list(continuous_hazards_units,
                            if_continuous_hazard_units)

        return continuous_hazards_units

    def vector_hazards_classifications_for_layer(self, hazard_key,
                                                 layer_geometry_key,
                                                 layer_mode_key,
                                                 hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of vector_hazards_classifications
        :rtype: list
        """
        vector_hazards_classifications = []
        for impact_function in self.impact_functions:
            if_vector_hazards_classifications = impact_function.metadata().\
                vector_hazards_classifications_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_vector_hazards_classifications:
                add_to_list(vector_hazards_classifications,
                            if_vector_hazards_classifications)

        return vector_hazards_classifications

    def raster_hazards_classifications_for_layer(self, hazard_key,
                                                 layer_geometry_key,
                                                 layer_mode_key,
                                                 hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of raster_hazards_classifications
        :rtype: list
        """
        raster_hazards_classifications = []
        for impact_function in self.impact_functions:
            if_vector_hazards_classifications = impact_function.metadata().\
                raster_hazards_classifications_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_vector_hazards_classifications:
                add_to_list(raster_hazards_classifications,
                            if_vector_hazards_classifications)

        return raster_hazards_classifications

    def available_hazards(self, hazard_category_key, ascending=True):
        """available_hazards from hazard_category_key

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: List of available hazards
        :rtype: list
        """

        hazards = []
        for impact_function in self.impact_functions:
            if_hazards = impact_function.metadata(). \
                available_hazards(hazard_category_key)
            if if_hazards:
                add_to_list(hazards, if_hazards)

        # make it sorted
        if ascending and hazards:
            hazards = sorted(hazards, key=lambda k: k['key'])

        return hazards

    def available_exposures(self, ascending=True):
        """Return a list of valid available exposures

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of exposures full metadata.
        :rtype: list
        """

        exposures = []
        for impact_function in self.impact_functions:
            if_exposures = impact_function.metadata().available_exposures()
            if if_exposures:
                add_to_list(exposures, if_exposures)

        # make it sorted
        if ascending and exposures:
            exposures = sorted(exposures, key=lambda k: k['key'])

        return exposures

    def functions_for_constraint(self,
                                 hazard_key,
                                 exposure_key,
                                 hazard_geometry_key=None,
                                 exposure_geometry_key=None,
                                 hazard_mode_key=None,
                                 exposure_mode_key=None):
        """Obtain all functions that match with the constraints

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param exposure_key: the exposure key
        :type exposure_key: str

        :param hazard_geometry_key: The hazard geometry key
        :type hazard_geometry_key: str

        :param exposure_geometry_key: The exposure geometry key
        :type exposure_geometry_key: str

        :param hazard_mode_key: The hazard mode key
        :type hazard_mode_key: str

        :param exposure_mode_key: The exposure mode key
        :type exposure_mode_key: str

        :returns: List of matched Impact Function
        :rtype: list
        """
        impact_functions = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().is_function_for_constraint(
                    hazard_key, exposure_key, hazard_geometry_key,
                    exposure_geometry_key, hazard_mode_key, exposure_mode_key):
                impact_functions.append(impact_function.metadata().as_dict())

        return impact_functions

    def available_hazard_constraints(self, hazard_key, hazard_category_key):
        """Get hazard constraints for hazard_key and hazard_category_key

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of tuple of layer_mode and layer_geometry
        :rtype: list
        """
        hazard_constraints = []
        for impact_function in self.impact_functions:
            if_hazard_constraints = impact_function.metadata().\
                available_hazard_constraints(
                    hazard_key, hazard_category_key)
            if if_hazard_constraints:
                add_to_list(hazard_constraints, if_hazard_constraints)

        return hazard_constraints

    def available_exposure_constraints(self, exposure_key):
        """Get exposure constraints for exposure_key.

        :param exposure_key: The exposure key
        :type exposure_key: str

        :returns: List of tuple of layer_mode and layer_geometry
        :rtype: list
        """
        exposure_constraints = []
        for impact_function in self.impact_functions:
            if_exposure_constraints = impact_function.metadata(). \
                available_exposure_constraints(exposure_key)
            if if_exposure_constraints:
                add_to_list(exposure_constraints, if_exposure_constraints)

        return exposure_constraints

    def available_hazard_layer_modes(self, hazard_key, hazard_geometry_key,
                                     hazard_category_key):
        """Return all available layer_mode.

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param hazard_geometry_key: The hazard geometry key
        :type hazard_geometry_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of layer_mode
        :rtype: list
        """
        layer_modes = []
        for impact_function in self.impact_functions:
            if_hazard_layer_mode = impact_function.metadata().\
                available_hazard_layer_mode(
                    hazard_key, hazard_geometry_key, hazard_category_key)
            if if_hazard_layer_mode:
                add_to_list(layer_modes, if_hazard_layer_mode)

        return layer_modes

    def available_exposure_layer_modes(self, exposure_key,
                                       exposure_geometry_key):
        """Get exposure layer mode for exposure_key.

        :param exposure_key: The exposure key
        :type exposure_key: str

        :param exposure_geometry_key: The exposure geometry key
        :type exposure_geometry_key: str

        :returns: List of layer_mode
        :rtype: list
        """
        layer_modes = []
        for impact_function in self.impact_functions:
            if_exposure_layer_mode = impact_function.metadata().\
                available_exposure_layer_mode(
                    exposure_key, exposure_geometry_key)
            if if_exposure_layer_mode:
                add_to_list(layer_modes, if_exposure_layer_mode)

        return layer_modes

    def hazard_additional_keywords(self,
                                   layer_mode_key=None,
                                   layer_geometry_key=None,
                                   hazard_category_key=None,
                                   hazard_key=None):
        """Return additional_keywords for hazard.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :param hazard_key: The hazard key
        :type hazard_key: str

        :returns: List of additional keywords
        :rtype: list
        """
        additional_keywords = []
        for impact_function in self.impact_functions:
            if_additional_keywords = impact_function.metadata().\
                hazard_additional_keywords(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    hazard_category_key=hazard_category_key,
                    hazard_key=hazard_key)
            if if_additional_keywords:
                add_to_list(additional_keywords, if_additional_keywords)

        return additional_keywords

    def exposure_additional_keywords(self,
                                     layer_mode_key=None,
                                     layer_geometry_key=None,
                                     exposure_key=None):
        """Return additional_keywords for exposure.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param exposure_key: The hazard key
        :type exposure_key: str

        :returns: List of additional keywords
        :rtype: list
        """
        additional_keywords = []
        for impact_function in self.impact_functions:
            if_additional_keywords = impact_function.metadata().\
                exposure_additional_keywords(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    exposure_key=exposure_key)
            if if_additional_keywords:
                add_to_list(additional_keywords, if_additional_keywords)

        return additional_keywords

    def exposure_class_fields(self,
                              layer_mode_key=None,
                              layer_geometry_key=None,
                              exposure_key=None):
        """Return list of exposure class field.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param exposure_key: The exposure key
        :type exposure_key: str

        :returns: List of exposure class field.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            if_exposure_class_field = impact_function.metadata(). \
                exposure_class_fields(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    exposure_key=exposure_key)
            if if_exposure_class_field:
                add_to_list(result, if_exposure_class_field)

        return result
class ImpactFunctionManager(object):
    """Class for managing metadata for all impact function.

    .. versionadded:: 2.1
    """

    def __init__(self):
        """Constructor."""
        # Singleton Registry to track all the registered Impact Functions
        self.registry = Registry()

    @property
    def impact_functions(self):
        """Return all registered impact functions."""
        return self.registry.impact_functions

    def get_instance(self, class_name):
        """Return an instance of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if =  if_manager.get_instance(if_class_name)

        :param class_name: The name of IF class.
        :type class_name: str

        :return: Impact function instance that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_instance(class_name)

    def get_class(self, class_name):
        """Return the class of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if_class =  if_manager.get_class(if_class_name)

        :param class_name: the name of IF class
        :type class_name: str

        :return: impact function class that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_class(class_name)

    def get(self, impact_function_id):
        """Return an instance of an impact function given its ID.

        This is a preferred way to get an instance of IF. IF should have a
        unique human readable ID in their metadata.

        .. example::

            if_manager = ImpactFunctionManager()
            if_id = 'FloodBuildingImpactFunction'
            if =  if_manager.get(if_id)

        :param impact_function_id: The ID of impact function in the metadata.
        :type impact_function_id: str

        :return An Impact function instance that has matched id.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        impact_functions = self.registry.filter_by_metadata(
            'id', impact_function_id)
        if len(impact_functions) == 0:
            raise Exception(
                'Impact function with ID: %s not found' % impact_function_id)
        elif len(impact_functions) > 1:
            raise Exception(
                'There are some Impact Functions that have the same ID: %s' %
                impact_function_id)
        return impact_functions[0].instance()

    def filter(self, hazard_metadata=None, exposure_metadata=None):
        """Get available impact functions from hazard and exposure metadata.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_metadata = {
                'subcategory': hazard_flood,
                'units': unit_wetdry,
                'layer_constraints': layer_vector_polygon
            }
            exposure_metadata = {
                'subcategory': exposure_structure,
                'units': unit_building_type_type,
                'layer_constraints': layer_vector_polygon
            }
            ifs =  if_manager.filter(hazard_metadata, exposure_metadata)

        :param hazard_metadata: The metadata of the hazard.
        :type hazard_metadata: dict

        :param exposure_metadata: The metadata of the exposure.
        :type exposure_metadata: dict
        """
        return self.registry.filter(hazard_metadata, exposure_metadata)

    def filter_by_keywords(
            self, hazard_keywords=None, exposure_keywords=None):
        """Get available impact functions from hazard and exposure keywords.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_keywords = {
                'subcategory': 'flood',
                'units': 'wetdry',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            exposure_keywords = {
                'subcategory': 'structure',
                'units': 'building_type',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            ifs =  if_manager.filter_by_keywords(hazard_keywords,
            exposure_keywords)

        :param hazard_keywords: The keywords of the hazard.
        :type hazard_keywords: dict

        :param exposure_keywords: The keywords of the exposure.
        :type exposure_keywords: dict
        """
        return self.registry.filter_by_keyword_string(
            hazard_keywords, exposure_keywords)

    def filter_by_metadata(self, metadata_key, metadata_value):
        """Return IF classes given its metadata key and value.

        .. example::

            if_manager = ImpactFunctionManager()
            metadata_key = 'author'
            metadata_value = 'Akbar Gumbira'
            ifs =  if_manager.filter_by_metadata(metadata_key,
            metadata_value)

        :param metadata_key: The key of the metadata e.g 'id', 'name'
        :type metadata_key: str

        :param metadata_value: The value of the metadata, e.g for the key
            'id' the value is 'FloodNativePolygonExperimentalFunction'
        :type metadata_value: str, dict

        :return: Impact Function classes match the arguments
        :rtype: list
        """
        return self.registry.filter_by_metadata(metadata_key, metadata_value)

    @staticmethod
    def get_function_id(impact_function):
        """Get the ID of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The ID of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('id', None)

    @staticmethod
    def get_function_title(impact_function):
        """Get title of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The title of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('title', None)

    @staticmethod
    def get_function_name(impact_function):
        """Get the human readable name of the impact function.

        :param impact_function: Class of an impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.metadata().as_dict().get('name', None)

    @staticmethod
    def get_function_type(impact_function):
        """Return the impact function type.

        :param impact_function: The impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.function_type()

    def get_functions_for_hazard(self, hazard):
        """Return all function metadata that has hazard in their metadata.

        .. versionadded:: 2.2

        :param hazard: Dictionary that represent the hazard
        :type hazard: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard(hazard):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def purposes_for_layer(self, layer_geometry_key):
        """Get purposes of a layer geometry id.

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        """
        layer_purposes = []
        for impact_function in self.impact_functions:
            if_layer_purposes = impact_function.metadata().purposes_for_layer(
                layer_geometry_key)
            if if_layer_purposes:
                add_to_list(layer_purposes, if_layer_purposes)

        return layer_purposes

    def hazard_categories_for_layer(self, layer_geometry_key, hazard_key=None):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :param hazard_key: The hazard key
        :type hazard_key: str

        :returns: List of hazard_categories
        :rtype: list
        """
        hazard_categories = []
        for impact_function in self.impact_functions:
            if_hazard_categories = impact_function.metadata()\
                .hazard_categories_for_layer(layer_geometry_key, hazard_key)
            if if_hazard_categories:
                add_to_list(hazard_categories, if_hazard_categories)

        return hazard_categories

    def hazards_for_layer(self, layer_geometry_key, hazard_category_key=None):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :param hazard_category_key: The hazard category
        :type hazard_category_key: str

        :returns: List of hazard
        :rtype: list
        """
        hazards = []
        for impact_function in self.impact_functions:
            if_hazards = impact_function.metadata().hazards_for_layer(
                layer_geometry_key, hazard_category_key)
            if if_hazards:
                add_to_list(hazards, if_hazards)

        return hazards

    def exposures_for_layer(self, layer_geometry_key):
        """Get hazard categories form layer_geometry_key

        :param layer_geometry_key: The geometry id
        :type layer_geometry_key: str

        :returns: List of hazard
        :rtype: list
        """
        exposures = []
        for impact_function in self.impact_functions:
            if_exposures = impact_function.metadata().exposures_for_layer(
                layer_geometry_key)
            if if_exposures:
                add_to_list(exposures, if_exposures)

        return exposures

    def exposure_units_for_layer(
            self, exposure_key, layer_geometry_key, layer_mode_key):
        """Get hazard categories form layer_geometry_key

        :param exposure_key: The exposure key
        :type exposure_key: str

        :param layer_geometry_key: The geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :returns: List of exposure unit
        :rtype: list
        """
        exposure_units = []
        for impact_function in self.impact_functions:
            if_exposure_units = impact_function.metadata().\
                exposure_units_for_layer(
                    exposure_key, layer_geometry_key, layer_mode_key)
            if if_exposure_units:
                add_to_list(exposure_units, if_exposure_units)

        return exposure_units

    def continuous_hazards_units_for_layer(
            self, hazard_key, layer_geometry_key, layer_mode_key,
            hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of continuous hazard unit
        :rtype: list
        """
        continuous_hazards_units = []
        for impact_function in self.impact_functions:
            if_continuous_hazard_units = impact_function.metadata().\
                continuous_hazards_units_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_continuous_hazard_units:
                add_to_list(
                    continuous_hazards_units, if_continuous_hazard_units)

        return continuous_hazards_units

    def vector_hazards_classifications_for_layer(
            self, hazard_key, layer_geometry_key, layer_mode_key,
            hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of vector_hazards_classifications
        :rtype: list
        """
        vector_hazards_classifications = []
        for impact_function in self.impact_functions:
            if_vector_hazards_classifications = impact_function.metadata().\
                vector_hazards_classifications_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_vector_hazards_classifications:
                add_to_list(
                    vector_hazards_classifications,
                    if_vector_hazards_classifications)

        return vector_hazards_classifications

    def raster_hazards_classifications_for_layer(
            self, hazard_key, layer_geometry_key, layer_mode_key,
            hazard_category_key):
        """Get continuous hazard units.
        :param hazard_key: The hazard key
        :type hazard_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of raster_hazards_classifications
        :rtype: list
        """
        raster_hazards_classifications = []
        for impact_function in self.impact_functions:
            if_vector_hazards_classifications = impact_function.metadata().\
                raster_hazards_classifications_for_layer(
                    hazard_key, layer_geometry_key, layer_mode_key,
                    hazard_category_key)
            if if_vector_hazards_classifications:
                add_to_list(
                    raster_hazards_classifications,
                    if_vector_hazards_classifications)

        return raster_hazards_classifications

    def available_hazards(self, hazard_category_key, ascending=True):
        """available_hazards from hazard_category_key

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: List of available hazards
        :rtype: list
        """

        hazards = []
        for impact_function in self.impact_functions:
            if_hazards = impact_function.metadata(). \
                available_hazards(hazard_category_key)
            if if_hazards:
                add_to_list(hazards, if_hazards)

        # make it sorted
        if ascending and hazards:
            hazards = sorted(hazards, key=lambda k: k['key'])

        return hazards

    def available_exposures(self, ascending=True):
        """Return a list of valid available exposures

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of exposures full metadata.
        :rtype: list
        """

        exposures = []
        for impact_function in self.impact_functions:
            if_exposures = impact_function.metadata().available_exposures()
            if if_exposures:
                add_to_list(exposures, if_exposures)

        # make it sorted
        if ascending and exposures:
            exposures = sorted(exposures, key=lambda k: k['key'])

        return exposures

    def functions_for_constraint(
            self, hazard_key, exposure_key, hazard_geometry_key=None,
            exposure_geometry_key=None, hazard_mode_key=None,
            exposure_mode_key=None):
        """Obtain all functions that match with the constraints

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param exposure_key: the exposure key
        :type exposure_key: str

        :param hazard_geometry_key: The hazard geometry key
        :type hazard_geometry_key: str

        :param exposure_geometry_key: The exposure geometry key
        :type exposure_geometry_key: str

        :param hazard_mode_key: The hazard mode key
        :type hazard_mode_key: str

        :param exposure_mode_key: The exposure mode key
        :type exposure_mode_key: str

        :returns: List of matched Impact Function
        :rtype: list
        """
        impact_functions = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().is_function_for_constraint(
                    hazard_key, exposure_key, hazard_geometry_key,
                    exposure_geometry_key, hazard_mode_key, exposure_mode_key):
                impact_functions.append(impact_function.metadata().as_dict())

        return impact_functions

    def available_hazard_constraints(
            self, hazard_key, hazard_category_key):
        """Get hazard constraints for hazard_key and hazard_category_key

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of tuple of layer_mode and layer_geometry
        :rtype: list
        """
        hazard_constraints = []
        for impact_function in self.impact_functions:
            if_hazard_constraints = impact_function.metadata().\
                available_hazard_constraints(
                    hazard_key, hazard_category_key)
            if if_hazard_constraints:
                add_to_list(hazard_constraints, if_hazard_constraints)

        return hazard_constraints

    def available_exposure_constraints(self, exposure_key):
        """Get exposure constraints for exposure_key.

        :param exposure_key: The exposure key
        :type exposure_key: str

        :returns: List of tuple of layer_mode and layer_geometry
        :rtype: list
        """
        exposure_constraints = []
        for impact_function in self.impact_functions:
            if_exposure_constraints = impact_function.metadata(). \
                available_exposure_constraints(exposure_key)
            if if_exposure_constraints:
                add_to_list(exposure_constraints, if_exposure_constraints)

        return exposure_constraints

    def available_hazard_layer_modes(
            self, hazard_key, hazard_geometry_key, hazard_category_key):
        """Return all available layer_mode.

        :param hazard_key: The hazard key
        :type hazard_key: str

        :param hazard_geometry_key: The hazard geometry key
        :type hazard_geometry_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :returns: List of layer_mode
        :rtype: list
        """
        layer_modes = []
        for impact_function in self.impact_functions:
            if_hazard_layer_mode = impact_function.metadata().\
                available_hazard_layer_mode(
                    hazard_key, hazard_geometry_key, hazard_category_key)
            if if_hazard_layer_mode:
                add_to_list(layer_modes, if_hazard_layer_mode)

        return layer_modes

    def available_exposure_layer_modes(
            self, exposure_key, exposure_geometry_key):
        """Get exposure layer mode for exposure_key.

        :param exposure_key: The exposure key
        :type exposure_key: str

        :param exposure_geometry_key: The exposure geometry key
        :type exposure_geometry_key: str

        :returns: List of layer_mode
        :rtype: list
        """
        layer_modes = []
        for impact_function in self.impact_functions:
            if_exposure_layer_mode = impact_function.metadata().\
                available_exposure_layer_mode(
                    exposure_key, exposure_geometry_key)
            if if_exposure_layer_mode:
                add_to_list(layer_modes, if_exposure_layer_mode)

        return layer_modes

    def hazard_additional_keywords(
            self, layer_mode_key=None, layer_geometry_key=None,
            hazard_category_key=None, hazard_key=None):
        """Return additional_keywords for hazard.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param hazard_category_key: The hazard category key
        :type hazard_category_key: str

        :param hazard_key: The hazard key
        :type hazard_key: str

        :returns: List of additional keywords
        :rtype: list
        """
        additional_keywords = []
        for impact_function in self.impact_functions:
            if_additional_keywords = impact_function.metadata().\
                hazard_additional_keywords(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    hazard_category_key=hazard_category_key,
                    hazard_key=hazard_key)
            if if_additional_keywords:
                add_to_list(additional_keywords, if_additional_keywords)

        return additional_keywords

    def exposure_additional_keywords(
            self, layer_mode_key=None, layer_geometry_key=None,
            exposure_key=None):
        """Return additional_keywords for exposure.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param exposure_key: The hazard key
        :type exposure_key: str

        :returns: List of additional keywords
        :rtype: list
        """
        additional_keywords = []
        for impact_function in self.impact_functions:
            if_additional_keywords = impact_function.metadata().\
                exposure_additional_keywords(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    exposure_key=exposure_key)
            if if_additional_keywords:
                add_to_list(additional_keywords, if_additional_keywords)

        return additional_keywords

    def exposure_class_fields(
            self, layer_mode_key=None, layer_geometry_key=None,
            exposure_key=None):
        """Return list of exposure class field.

        :param layer_mode_key: The layer mode key
        :type layer_mode_key: str

        :param layer_geometry_key: The layer geometry key
        :type layer_geometry_key: str

        :param exposure_key: The exposure key
        :type exposure_key: str

        :returns: List of exposure class field.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            if_exposure_class_field = impact_function.metadata(). \
                exposure_class_fields(
                    layer_mode_key=layer_mode_key,
                    layer_geometry_key=layer_geometry_key,
                    exposure_key=exposure_key)
            if if_exposure_class_field:
                add_to_list(result, if_exposure_class_field)

        return result
Esempio n. 6
0
class ImpactFunctionManager(object):
    """Class for managing metadata for all impact function.

    .. versionadded:: 2.1
    """
    def __init__(self):
        """Constructor."""
        # Singleton Registry to track all the registered Impact Functions
        self.registry = Registry()

    @property
    def impact_functions(self):
        """Return all registered impact functions."""
        return self.registry.impact_functions

    def get_instance(self, class_name):
        """Return an instance of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if =  if_manager.get_instance(if_class_name)

        :param class_name: The name of IF class.
        :type class_name: str

        :return: Impact function instance that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_instance(class_name)

    def get_class(self, class_name):
        """Return the class of an impact function given its class name.

        .. example::

            if_manager = ImpactFunctionManager()
            if_class_name = 'FloodBuildingImpactFunction'
            if_class =  if_manager.get_class(if_class_name)

        :param class_name: the name of IF class
        :type class_name: str

        :return: impact function class that matches the argument.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        return self.registry.get_class(class_name)

    def get(self, impact_function_id):
        """Return an instance of an impact function given its ID.

        This is a preferred way to get an instance of IF. IF should have a
        unique human readable ID in their metadata.

        .. example::

            if_manager = ImpactFunctionManager()
            if_id = 'FloodBuildingImpactFunction'
            if =  if_manager.get(if_id)

        :param impact_function_id: The ID of impact function in the metadata.
        :type impact_function_id: str

        :return An Impact function instance that has matched id.
        :rtype: safe.impact_functions.base.ImpactFunction
        """
        impact_functions = self.registry.filter_by_metadata(
            'id', impact_function_id)
        if len(impact_functions) == 0:
            raise Exception('Impact function with ID: %s not found' %
                            impact_function_id)
        elif len(impact_functions) > 1:
            raise Exception(
                'There are some Impact Functions that have the same ID: %s' %
                impact_function_id)
        return impact_functions[0].instance()

    def filter(self, hazard_metadata=None, exposure_metadata=None):
        """Get available impact functions from hazard and exposure metadata.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_metadata = {
                'subcategory': hazard_flood,
                'units': unit_wetdry,
                'layer_constraints': layer_vector_polygon
            }
            exposure_metadata = {
                'subcategory': exposure_structure,
                'units': unit_building_type_type,
                'layer_constraints': layer_vector_polygon
            }
            ifs =  if_manager.filter(hazard_metadata, exposure_metadata)

        :param hazard_metadata: The metadata of the hazard.
        :type hazard_metadata: dict

        :param exposure_metadata: The metadata of the exposure.
        :type exposure_metadata: dict
        """
        return self.registry.filter(hazard_metadata, exposure_metadata)

    def filter_by_keywords(self, hazard_keywords=None, exposure_keywords=None):
        """Get available impact functions from hazard and exposure keywords.

        Disabled impact function will not be loaded.

        .. example::

            if_manager = ImpactFunctionManager()
            hazard_keywords = {
                'subcategory': 'flood',
                'units': 'wetdry',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            exposure_keywords = {
                'subcategory': 'structure',
                'units': 'building_type',
                'layer_type': 'vector',
                'data_type': 'polygon'
            }
            ifs =  if_manager.filter_by_keywords(hazard_keywords,
            exposure_keywords)

        :param hazard_keywords: The keywords of the hazard.
        :type hazard_keywords: dict

        :param exposure_keywords: The keywords of the exposure.
        :type exposure_keywords: dict
        """
        return self.registry.filter_by_keyword_string(hazard_keywords,
                                                      exposure_keywords)

    def filter_by_metadata(self, metadata_key, metadata_value):
        """Return IF classes given its metadata key and value.

        .. example::

            if_manager = ImpactFunctionManager()
            metadata_key = 'author'
            metadata_value = 'Akbar Gumbira'
            ifs =  if_manager.filter_by_metadata(metadata_key,
            metadata_value)

        :param metadata_key: The key of the metadata e.g 'id', 'name'
        :type metadata_key: str

        :param metadata_value: The value of the metadata, e.g for the key
            'id' the value is 'FloodNativePolygonExperimentalFunction'
        :type metadata_value: str, dict

        :return: Impact Function classes match the arguments
        :rtype: list
        """
        return self.registry.filter_by_metadata(metadata_key, metadata_value)

    @staticmethod
    def get_function_id(impact_function):
        """Get the ID of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The ID of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('id', None)

    @staticmethod
    def get_function_title(impact_function):
        """Get title of the impact function.

        :param impact_function: Class of an impact function
        :type impact_function: safe.impact_functions.base.ImpactFunction

        :returns: The title of the impact function specified in its metadata.
        :rtype: str
        """
        return impact_function.metadata().as_dict().get('title', None)

    @staticmethod
    def get_function_name(impact_function):
        """Get the human readable name of the impact function.

        :param impact_function: Class of an impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.metadata().as_dict().get('name', None)

    @staticmethod
    def get_function_type(impact_function):
        """Return the impact function type.

        :param impact_function: The impact function.
        :type impact_function: safe.impact_functions.base.ImpactFunction
        """
        return impact_function.function_type()

    def allowed_subcategories(self, category=None):
        """Determine allowed subcategories, optionally filtered by category.

        :param category: Optional category which will be used to subset the
            allowed subcategories. If omitted, all supported subcategories will
            be returned (for both hazard and exposure). Default is None.
        :type category: str

        :returns: A list of strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_subcategories = impact_function.metadata()\
                .allowed_subcategories(category)
            result = add_to_list(result, my_allowed_subcategories)
        return result

    def allowed_data_types(self, subcategory):
        """Determine allowed data types for all impact functions.

        It uses subcategory as a filter.

        Passing a subcategory is required otherwise the context of the
        data_type(s) would be ambiguous (i.e. whether they can be used as
        exposure or hazards).

        :param subcategory: Required subcategory which will be used to subset
            the allowed data_types.
        :type subcategory: str

        :returns: A list of one or more strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_data_types = impact_function.metadata() \
                .allowed_data_types(subcategory)
            result = add_to_list(result, my_allowed_data_types)
        return result

    def allowed_units(self, subcategory, data_type):
        """Determine allowed units from all impact functions.


        It uses subcategory and data_type as a filter.

        .. note:: One data_type could be  used by more than one subcategory,
            so we need to explicitly pass the subcategory to this function.

        :param subcategory: Required subcategory which will be used to subset
            the allowed data_types.
        :type subcategory: str

        :param data_type: Required data_type which will be used to subset the
            allowed units.
        :type data_type: str

        :returns: A list of one or more strings is returned.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            my_allowed_units = impact_function.metadata()\
                .allowed_units(subcategory, data_type)
            result = add_to_list(result, my_allowed_units)
        return result

    def units_for_layer(self, subcategory, layer_type, data_type):
        """Get the valid units for a layer.

        Example usage::

            foo  = units_for_layer('flood', 'vector', 'polygon')
            print foo

        Would output this::

            {'Wet/Dry': ['wet','dry']}

        While passing a raster layer::

            foo  = units_for_layer('flood', 'raster', None)
            print foo

        Might return this::

            {
                'metres': None,
                'feet': None,
                'wet/dry': ['wet', 'dry'],
            }

        In the returned dictionary the keys are unit types and
        the values are the categories (if any) applicable for that unit type.

        :param subcategory: The subcategory for this layer.
        :type subcategory: str

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :returns: A dictionary as per the example above where each key
            represents a unit and each value that is not None represents a
            list of categories.
        :rtype: dict
        """
        result = []
        for impact_function in self.impact_functions:
            my_units = impact_function.metadata()\
                .units_for_layer(subcategory, layer_type, data_type)
            result = add_to_list(result, my_units)
        return result

    def categories_for_layer(self, layer_type, data_type):
        """Return a list of valid categories for a layer.

        This method is used to determine if a given layer can be used as a
        hazard, exposure or aggregation layer.

        Example usage::

            foo  = categories_for_layer('vector', 'polygon')
            print foo

        Would output this::

            ['hazard', 'exposure', 'aggregation']

        While passing a vector point layer::

            foo  = units_for_layer('vector', 'point')
            print foo

        Might return this::

            ['hazard', 'exposure']

        In the returned the values are categories (if any) applicable for that
        layer_type and data_type.

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :returns: A list as per the example above where each value represents
            a valid category.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            categories = impact_function.metadata()\
                .categories_for_layer(layer_type, data_type)
            result = add_to_list(result, categories)
        categories_definitions = []
        for category in result:
            if category == 'hazard':
                categories_definitions.append(hazard_definition)
            elif category == 'exposure':
                categories_definitions.append(exposure_definition)
            else:
                raise Exception('Unsupported categories')
        return categories_definitions

    def subcategories_for_layer(self, category, layer_type, data_type):
        """Return a list of valid subcategories for a layer.

        This method is used to determine which subcategories a given layer
        can be for.

        Example usage::

            foo  = subcategories_for_layer('vector', 'polygon', 'exposure')
            print foo

        Would output this::

            ['flood', 'landuse']

        In the returned the values are categories (if any) applicable for that
        layer_type and data_type.

        :param layer_type: The type for this layer. Valid values would be,
            'raster' or 'vector'.
        :type layer_type: str

        :param data_type: The data_type for this layer. Valid possibilities
            would be 'numeric' (for raster), point, line, polygon
            (for vectors).
        :type data_type: str

        :param category: The category for this layer. Valid possibilities
            would be 'hazard', 'exposure' and 'aggregation'.
        :type category: str


        :returns: A list as per the example above where each value represents
            a valid subcategory.
        :rtype: list
        """
        result = []
        for impact_function in self.impact_functions:
            subcategories = impact_function.metadata()\
                .subcategories_for_layer(category, layer_type, data_type)
            result = add_to_list(result, subcategories)
        return result

    def get_available_hazards(self, impact_function=None, ascending=True):
        """Return a list of valid available hazards for an impact function.

        If impact_function is None, return all available hazards

        .. versionadded:: 2.2

        :param impact_function: Impact Function object.
        :type impact_function: FunctionProvider

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of hazard full metadata.
        :rtype: list
        """

        hazards = []
        if impact_function is None:
            for impact_function in self.impact_functions:
                add_to_list(hazards, impact_function.metadata().get_hazards())

        else:
            # noinspection PyUnresolvedReferences
            hazards = impact_function.metadata().get_hazards()

        # make it sorted
        if ascending:
            hazards = sorted(hazards, key=lambda k: k['id'])

        return hazards

    def get_functions_for_hazard(self, hazard):
        """Return all function metadata that has hazard in their metadata.

        .. versionadded:: 2.2

        :param hazard: Dictionary that represent the hazard
        :type hazard: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard(hazard):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_hazard_id(self, hazard_id):
        """Return all function metadata that has hazard_id in their metadata.

        .. versionadded:: 2.2

        :param hazard_id: String that represent the hazard id.
        :type hazard_id: str

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_hazard_id(hazard_id):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_available_exposures(self, impact_function=None, ascending=True):
        """Return a list of valid available exposures for an impact function.

        If impact_function is None, return all available exposures

        .. versionadded:: 2.2

        :param impact_function: Impact Function object.
        :type impact_function: FunctionProvider

        :param ascending: Sort ascending or not.
        :type ascending: bool

        :returns: A list of exposures full metadata.
        :rtype: list
        """

        exposures = []
        if impact_function is None:
            for impact_function in self.impact_functions:
                add_to_list(exposures,
                            impact_function.metadata().get_exposures())

        else:
            # noinspection PyUnresolvedReferences
            exposures = impact_function.metadata().get_exposures()

        # make it sorted
        if ascending:
            exposures = sorted(exposures, key=lambda k: k['id'])

        return exposures

    def get_functions_for_exposure(self, exposure):
        """Return all function metadata that has exposure in their metadata.

        .. versionadded:: 2.2

        :param exposure: Dictionary that represent the exposure
        :type exposure: dict

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_exposure(exposure):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_exposure_id(self, exposure_id):
        """Return all function metadata that has exposure_id in their metadata.

        .. versionadded:: 2.2

        :param exposure_id: String that represent the exposure id.
        :type exposure_id: str

        :return: List of impact function metadata.
        :rtype: list
        """
        impact_functions_metadata = []
        for impact_function in self.impact_functions:
            if impact_function.metadata().has_exposure_id(exposure_id):
                impact_functions_metadata.append(
                    impact_function.metadata().as_dict())

        return impact_functions_metadata

    def get_functions_for_constraint(self,
                                     hazard,
                                     exposure,
                                     hazard_constraint=None,
                                     exposure_constraint=None):
        """Get all impact functions that match the hazard n exposure metadata.

        :param hazard: The hazard metadata.
        :type hazard: dict

        :param exposure: The exposure metadata.
        :type exposure: dict

        :param hazard_constraint:
        :param exposure_constraint:
        :return:
        """
        result = []
        if_hazard = self.get_functions_for_hazard(hazard)
        if_exposure = self.get_functions_for_exposure(exposure)
        if_exposure_id = [function['id'] for function in if_exposure]

        for f in if_hazard:
            if f['id'] in if_exposure_id:
                if (not hazard_constraint or hazard_constraint
                        in f['categories']['hazard']['layer_constraints']):
                    if (not exposure_constraint or exposure_constraint in
                            f['categories']['exposure']['layer_constraints']):
                        result.append(f)

        return result