示例#1
0
    def test_update_from_dict(self):
        """Test update_from_dict method."""
        good_data = {
            'start_time': '20140714_060955',
            'finish_time': '20140714_061255',
            'hazard_layer': 'path/to/hazard/layer',
            'exposure_layer': 'path/to/exposure/layer',
            'impact_function_id': 'IF_id',
            'impact_function_version': '2.1',
            'host_name': 'my_computer',
            'user': '******',
            'qgis_version': '2.4',
            'gdal_version': '1.9.1',
            'qt_version': '4.5',
            'pyqt_version': '5.1',
            'os': 'ubuntu 12.04',
            'inasafe_version': '2.1',
            'exposure_pixel_size': '0.1',
            'hazard_pixel_size': '0.2',
            'impact_pixel_size': '0.1',
            'actual_extent': [0, 1, 2, 2],
            'requested_extent': [0, 1, 2, 2],
            'actual_extent_crs': 'EPSG: 4326',
            'requested_extent_crs': 'EPSG: 4326',
            'parameter': {},
        }

        metadata = ImpactLayerMetadata('random_layer_id')
        provenance = Provenance()
        provenance.append_step(
            'Title 1', 'Description of step 1', '2015-06-25T13:14:24.508974')
        provenance.append_step(
            'Title 2', 'Description of step 2', '2015-06-25T13:14:24.508980')
        provenance.append_if_provenance_step(
            'Title 3',
            'Description of step 3',
            '2015-06-25T13:14:24.508984',
            data=good_data
        )
        keywords = {
            'layer_purpose': 'impact_layer',
            'layer_geometry': 'raster',
            'if_provenance': provenance,
        }
        metadata.update_from_dict(keywords)
        self.assertEqual(metadata.layer_purpose, 'impact_layer')
        self.assertEqual(metadata.layer_geometry, 'raster')
        self.assertNotEqual(metadata.layer_mode, 'raster')
        self.assertEqual(len(metadata.provenance.steps), 3)
        self.assertEqual(metadata.provenance.get(2), provenance.get(2))
        self.assertEqual(metadata.provenance.get(2).user, 'my_user')
示例#2
0
    def test_append_step(self):
        provenance = Provenance()
        title0 = 'Calculated first random impact'
        description0 = 'In this step we calculated a first random impact'
        provenance.append_step(title0, description0)
        title1 = 'Calculated second random impact'
        description1 = 'In this step we calculated a second random impact'
        provenance.append_step(title1, description1)
        title2 = 'Calculated third random impact'
        description2 = 'In this step we calculated a third random impact'
        provenance.append_step(title2, description2)
        title3 = 'Calculated fourth random impact'
        description3 = 'In this step we calculated a fourth random impact'
        data = {
            'start_time': '20140714_060955',
            'finish_time': '20140714_061255',
            'hazard_layer': 'path/to/hazard/layer',
            'exposure_layer': 'path/to/exposure/layer',
            'impact_function_id': 'IF_id',
            'impact_function_version': '2.1',
            'host_name': 'my_computer',
            'user': '******',
            'qgis_version': '2.4',
            'gdal_version': '1.9.1',
            'qt_version': '4.5',
            'pyqt_version': '5.1',
            'os': 'ubuntu 12.04',
            'inasafe_version': '2.1',
            'exposure_pixel_size': '0.1',
            'hazard_pixel_size': '0.2',
            'impact_pixel_size': '0.1',
            'analysis_extent': [0, 1, 2, 2],
            'parameter': {}
        }
        provenance.append_step(title3, description3, data=data)

        self.assertEqual(provenance.count, 4)
        self.assertEqual(provenance.get(1).title, title1)
        self.assertEqual(provenance.last.title, title3)
        self.assertEqual(provenance.last.data(), data)
示例#3
0
    def test_append_step(self):
        provenance = Provenance()
        title0 = 'Calculated first random impact'
        description0 = 'In this step we calculated a first random impact'
        provenance.append_step(title0, description0)
        title1 = 'Calculated second random impact'
        description1 = 'In this step we calculated a second random impact'
        provenance.append_step(title1, description1)
        title2 = 'Calculated third random impact'
        description2 = 'In this step we calculated a third random impact'
        provenance.append_step(title2, description2)
        title3 = 'Calculated fourth random impact'
        description3 = 'In this step we calculated a fourth random impact'
        data = {
            'start_time': '20140714_060955',
            'finish_time': '20140714_061255',
            'hazard_layer': 'path/to/hazard/layer',
            'exposure_layer': 'path/to/exposure/layer',
            'impact_function_id': 'IF_id',
            'impact_function_version': '2.1',
            'host_name': 'my_computer',
            'user': '******',
            'qgis_version': '2.4',
            'gdal_version': '1.9.1',
            'qt_version': '4.5',
            'pyqt_version': '5.1',
            'os': 'ubuntu 12.04',
            'inasafe_version': '2.1',
            'exposure_pixel_size': '0.1',
            'hazard_pixel_size': '0.2',
            'impact_pixel_size': '0.1',
            'analysis_extent': [0, 1, 2, 2],
            'parameter': {}
        }
        provenance.append_step(title3, description3, data=data)

        self.assertEqual(provenance.count, 4)
        self.assertEqual(provenance.get(1).title, title1)
        self.assertEqual(provenance.last.title, title3)
        self.assertEqual(provenance.last.data(), data)
示例#4
0
class ImpactLayerMetadata(BaseMetadata):
    """
    Metadata class for impact layers

    if you need to add a standard XML property that only applies to this
    subclass, do it this way. @property and @propname.setter will be
    generated automatically

    _standard_properties = {
        'TESTprop': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'gco:CharacterString')
    }
    from safe.metadata35.utils import merge_dictionaries
    _standard_properties = merge_dictionaries(
        BaseMetadata._standard_properties, _standard_properties)

    .. versionadded:: 3.2
    """

    # remember to add an attribute or a setter property with the same name
    # these are properties that need special getters and setters thus are
    # not put in the standard_properties
    _standard_properties = {
        'elapsed_time': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'elapsed_time/'
                         'gco:Integer'),
        'hazard_title': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'hazard_title/'
                         'gco:CharacterString'),
        'postprocessing_report': ('gmd:identificationInfo/'
                                  'gmd:MD_DataIdentification/'
                                  'gmd:supplementalInformation/'
                                  'inasafe/'
                                  'postprocessing_report/'
                                  'gco:CharacterString'),
        'exposure_title': ('gmd:identificationInfo/'
                           'gmd:MD_DataIdentification/'
                           'gmd:supplementalInformation/'
                           'inasafe/'
                           'exposure_title/'
                           'gco:CharacterString'),
        'legend_title': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'legend_title/'
                         'gco:CharacterString'),
        'legend_notes': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'legend_notes/'
                         'gco:CharacterString'),
        'exposure_source': ('gmd:identificationInfo/'
                            'gmd:MD_DataIdentification/'
                            'gmd:supplementalInformation/'
                            'inasafe/'
                            'exposure_source/'
                            'gco:CharacterString'),
        'map_title': ('gmd:identificationInfo/'
                      'gmd:MD_DataIdentification/'
                      'gmd:supplementalInformation/'
                      'inasafe/'
                      'map_title/'
                      'gco:CharacterString'),
        'legend_units': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'legend_units/'
                         'gco:CharacterString'),
        'impact_summary': ('gmd:identificationInfo/'
                           'gmd:MD_DataIdentification/'
                           'gmd:supplementalInformation/'
                           'inasafe/'
                           'impact_summary/'
                           'gco:CharacterString'),
        'user': ('gmd:identificationInfo/'
                 'gmd:MD_DataIdentification/'
                 'gmd:supplementalInformation/'
                 'inasafe/'
                 'user/'
                 'gco:CharacterString'),
        'host_name': ('gmd:identificationInfo/'
                      'gmd:MD_DataIdentification/'
                      'gmd:supplementalInformation/'
                      'inasafe/'
                      'host_name/'
                      'gco:CharacterString'),
        'time_stamp': ('gmd:identificationInfo/'
                       'gmd:MD_DataIdentification/'
                       'gmd:supplementalInformation/'
                       'inasafe/'
                       'time_stamp/'
                       'gco:CharacterString'),
        'hazard_source': ('gmd:identificationInfo/'
                          'gmd:MD_DataIdentification/'
                          'gmd:supplementalInformation/'
                          'inasafe/'
                          'hazard_source/'
                          'gco:CharacterString'),
        'target_field': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'target_field/'
                         'gco:CharacterString'),
        'impact_table': ('gmd:identificationInfo/'
                         'gmd:MD_DataIdentification/'
                         'gmd:supplementalInformation/'
                         'inasafe/'
                         'impact_table/'
                         'gco:CharacterString'),
    }
    _standard_properties = merge_dictionaries(
        BaseMetadata._standard_properties, _standard_properties)

    _special_properties = {
        'provenance': ('gmd:identificationInfo/'
                       'gmd:MD_DataIdentification/'
                       'gmd:supplementalInformation/'
                       'inasafe_provenance')
    }

    def __init__(self, layer_uri, xml_uri=None, json_uri=None):
        """
        Constructor

        :param layer_uri: uri of the layer for which the metadata ae
        :type layer_uri: str
        :param xml_uri: uri of an xml file to use
        :type xml_uri: str
        :param json_uri: uri of a json file to use
        :type json_uri: str
        """

        # Initialise members
        # private members
        self._provenance = Provenance()

        # public members
        self.summary_data = None

        # initialize base class
        super(ImpactLayerMetadata, self).__init__(layer_uri, xml_uri, json_uri)

    @property
    def dict(self):
        """
        calls the overridden method and adds provenance and summary data

        :return: dictionary representation of the metadata
        :rtype: dict
        """
        metadata = super(ImpactLayerMetadata, self).dict

        metadata['provenance'] = self.provenance
        metadata['summary_data'] = self.summary_data

        return metadata

    @property
    def json(self):
        """
        json representation of the metadata

        :return: json representation of the metadata
        :rtype: str
        """
        metadata = self.dict

        metadata['provenance'] = self.provenance.dict
        json_dumps = json.dumps(metadata,
                                indent=2,
                                sort_keys=True,
                                separators=(',', ': '),
                                cls=MetadataEncoder)
        if not json_dumps.endswith('\n'):
            json_dumps += '\n'
        return json_dumps

    def read_json(self):
        """
        read metadata from json and set all the found properties.

        :return: the read metadata
        :rtype: dict
        """
        with reading_ancillary_files(self):
            metadata = super(ImpactLayerMetadata, self).read_json()
            if 'provenance' in metadata:
                for provenance_step in metadata['provenance']:
                    try:
                        title = provenance_step['title']
                        if 'IF Provenance' in title:
                            self.append_if_provenance_step(
                                provenance_step['title'],
                                provenance_step['description'],
                                provenance_step['time'],
                                provenance_step['data'])
                        else:
                            self.append_provenance_step(
                                provenance_step['title'],
                                provenance_step['description'],
                                provenance_step['time'],
                            )
                    except KeyError:
                        # we want to get as much as we can without raising
                        # errors
                        pass
            if 'summary_data' in metadata:
                self.summary_data = metadata['summary_data']

        return metadata

    @property
    def xml(self):
        """
        xml representation of the metadata.

        :return: xml representation of the metadata
        :rtype: ElementTree.Element
        """

        root = super(ImpactLayerMetadata, self).xml
        provenance_path = self._special_properties['provenance']
        provenance_element = root.find(provenance_path, XML_NS)

        # find the provenance parent tag
        if provenance_element is not None:
            # there is already a provenance tag so we remove it
            provenance_parent = provenance_element.getparent()
            provenance_parent.remove(provenance_element)
        else:
            # find the parent using the provenance path minus one level
            provenance_parent = '/'.join(provenance_path.split('/')[:-1])
            provenance_parent = root.find(provenance_parent, XML_NS)

        # generate the provenance xml element
        provenance_element = ElementTree.fromstring(self.provenance.xml)
        provenance_parent.append(provenance_element)
        return prettify_xml(ElementTree.tostring(root))

    def read_xml(self):
        """
        read metadata from xml and set all the found properties.

        :return: the root element of the xml
        :rtype: ElementTree.Element
        """

        with reading_ancillary_files(self):
            root = super(ImpactLayerMetadata, self).read_xml()
            if root is not None:
                self._read_provenance_from_xml(root)
        return root

    def _read_provenance_from_xml(self, root):
        """
        read metadata provenance from xml.

        :param root: container in which we search
        :type root: ElementTree.Element
        """
        path = self._special_properties['provenance']
        provenance = root.find(path, XML_NS)
        for step in provenance.iter('provenance_step'):
            title = step.find('title').text
            description = step.find('description').text
            timestamp = step.get('timestamp')

            if 'IF Provenance' in title:
                data = {}
                from safe.metadata35.provenance import IFProvenanceStep
                keys = IFProvenanceStep.impact_functions_fields
                for key in keys:
                    value = step.find(key)
                    if value is not None:
                        data[key] = value.text
                    else:
                        data[key] = ''
                self.append_if_provenance_step(title, description, timestamp,
                                               data)
            else:
                self.append_provenance_step(title, description, timestamp)

    @property
    def provenance(self):
        """
        Get the provenance elements of the metadata

        there is no setter as provenance can only grow. use
        append_provenance_step to add steps

        :return: The provenance element
        :rtype: Provenance
        """
        return self._provenance

    def append_provenance_step(self, title, description, timestamp=None):
        """
        Add a step to the provenance of the metadata

        :param title: The title of the step.
        :type title: str

        :param description: The content of the step
        :type description: str

        :param timestamp: the time of the step
        :type timestamp: datetime, str
        """
        step_time = self._provenance.append_step(title, description, timestamp)
        if step_time > self.last_update:
            self.last_update = step_time

    def append_if_provenance_step(self,
                                  title,
                                  description,
                                  timestamp=None,
                                  data=None):
        """Add a if provenance step to the provenance of the metadata

        :param title: The title of the step.
        :type title: str

        :param description: The content of the step
        :type description: str

        :param timestamp: the time of the step
        :type timestamp: datetime, str

        :param data: The data of the step.
        :type data: dict
        """
        step_time = self._provenance.append_if_provenance_step(
            title, description, timestamp, data)
        if step_time > self.last_update:
            self.last_update = step_time

    def update_from_dict(self, keywords):
        """Update metadata value from a keywords dictionary.

        :param keywords:
        :return:
        """
        super(ImpactLayerMetadata, self).update_from_dict(keywords)

        if 'if_provenance' in keywords.keys():
            if_provenance = keywords['if_provenance']
            for provenance_step in if_provenance:
                self.provenance.append_provenance_step(provenance_step)
示例#5
0
class ImpactLayerMetadata(BaseMetadata):
    """
    Metadata class for impact layers

    if you need to add a standard XML property that only applies to this
    subclass, do it this way. @property and @propname.setter will be
    generated automatically

    _standard_properties = {
        'TESTprop': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'gco:CharacterString')
    }
    from safe.metadata35.utils import merge_dictionaries
    _standard_properties = merge_dictionaries(
        BaseMetadata._standard_properties, _standard_properties)

    .. versionadded:: 3.2
    """

    # remember to add an attribute or a setter property with the same name
    # these are properties that need special getters and setters thus are
    # not put in the standard_properties
    _standard_properties = {
        'elapsed_time': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'elapsed_time/'
            'gco:Integer'),
        'hazard_title': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'hazard_title/'
            'gco:CharacterString'),
        'postprocessing_report': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'postprocessing_report/'
            'gco:CharacterString'),
        'exposure_title': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'exposure_title/'
            'gco:CharacterString'),
        'legend_title': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'legend_title/'
            'gco:CharacterString'),
        'legend_notes': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'legend_notes/'
            'gco:CharacterString'),
        'exposure_source': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'exposure_source/'
            'gco:CharacterString'),
        'map_title': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'map_title/'
            'gco:CharacterString'),
        'legend_units': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'legend_units/'
            'gco:CharacterString'),
        'impact_summary': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'impact_summary/'
            'gco:CharacterString'),
        'user': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'user/'
            'gco:CharacterString'),
        'host_name': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'host_name/'
            'gco:CharacterString'),
        'time_stamp': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'time_stamp/'
            'gco:CharacterString'),
        'hazard_source': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'hazard_source/'
            'gco:CharacterString'),
        'target_field': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'target_field/'
            'gco:CharacterString'),
        'impact_table': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe/'
            'impact_table/'
            'gco:CharacterString'),
    }
    _standard_properties = merge_dictionaries(
        BaseMetadata._standard_properties, _standard_properties)

    _special_properties = {
        'provenance': (
            'gmd:identificationInfo/'
            'gmd:MD_DataIdentification/'
            'gmd:supplementalInformation/'
            'inasafe_provenance')
    }

    def __init__(self, layer_uri, xml_uri=None, json_uri=None):
        """
        Constructor

        :param layer_uri: uri of the layer for which the metadata ae
        :type layer_uri: str
        :param xml_uri: uri of an xml file to use
        :type xml_uri: str
        :param json_uri: uri of a json file to use
        :type json_uri: str
        """

        # Initialise members
        # private members
        self._provenance = Provenance()

        # public members
        self.summary_data = None

        # initialize base class
        super(ImpactLayerMetadata, self).__init__(layer_uri, xml_uri, json_uri)

    @property
    def dict(self):
        """
        calls the overridden method and adds provenance and summary data

        :return: dictionary representation of the metadata
        :rtype: dict
        """
        metadata = super(ImpactLayerMetadata, self).dict

        metadata['provenance'] = self.provenance
        metadata['summary_data'] = self.summary_data

        return metadata

    @property
    def json(self):
        """
        json representation of the metadata

        :return: json representation of the metadata
        :rtype: str
        """
        metadata = self.dict

        metadata['provenance'] = self.provenance.dict
        json_dumps = json.dumps(
            metadata, indent=2, sort_keys=True, separators=(',', ': '),
            cls=MetadataEncoder)
        if not json_dumps.endswith('\n'):
            json_dumps += '\n'
        return json_dumps

    def read_json(self):
        """
        read metadata from json and set all the found properties.

        :return: the read metadata
        :rtype: dict
        """
        with reading_ancillary_files(self):
            metadata = super(ImpactLayerMetadata, self).read_json()
            if 'provenance' in metadata:
                for provenance_step in metadata['provenance']:
                    try:
                        title = provenance_step['title']
                        if 'IF Provenance' in title:
                            self.append_if_provenance_step(
                                provenance_step['title'],
                                provenance_step['description'],
                                provenance_step['time'],
                                provenance_step['data']
                            )
                        else:
                            self.append_provenance_step(
                                provenance_step['title'],
                                provenance_step['description'],
                                provenance_step['time'],
                            )
                    except KeyError:
                        # we want to get as much as we can without raising
                        # errors
                        pass
            if 'summary_data' in metadata:
                self.summary_data = metadata['summary_data']

        return metadata

    @property
    def xml(self):
        """
        xml representation of the metadata.

        :return: xml representation of the metadata
        :rtype: str
        """

        root = super(ImpactLayerMetadata, self).xml
        provenance_path = self._special_properties['provenance']
        provenance_element = root.find(provenance_path, XML_NS)

        # find the provenance parent tag
        if provenance_element is not None:
            # there is already a provenance tag so we remove it
            provenance_parent = provenance_element.getparent()
            provenance_parent.remove(provenance_element)
        else:
            # find the parent using the provenance path minus one level
            provenance_parent = '/'.join(provenance_path.split('/')[:-1])
            provenance_parent = root.find(provenance_parent, XML_NS)

        # generate the provenance xml element
        provenance_element = ElementTree.fromstring(self.provenance.xml)
        provenance_parent.append(provenance_element)
        return prettify_xml(ElementTree.tostring(root, 'unicode'))

    def read_xml(self):
        """
        read metadata from xml and set all the found properties.

        :return: the root element of the xml
        :rtype: ElementTree.Element
        """

        with reading_ancillary_files(self):
            root = super(ImpactLayerMetadata, self).read_xml()
            if root is not None:
                self._read_provenance_from_xml(root)
        return root

    def _read_provenance_from_xml(self, root):
        """
        read metadata provenance from xml.

        :param root: container in which we search
        :type root: ElementTree.Element
        """
        path = self._special_properties['provenance']
        provenance = root.find(path, XML_NS)
        for step in provenance.iter('provenance_step'):
            title = step.find('title').text
            description = step.find('description').text
            timestamp = step.get('timestamp')

            if 'IF Provenance' in title:
                data = {}
                from safe.metadata35.provenance import IFProvenanceStep
                keys = IFProvenanceStep.impact_functions_fields
                for key in keys:
                    value = step.find(key)
                    if value is not None:
                        data[key] = value.text
                    else:
                        data[key] = ''
                self.append_if_provenance_step(
                    title, description, timestamp, data)
            else:
                self.append_provenance_step(title, description, timestamp)

    @property
    def provenance(self):
        """
        Get the provenance elements of the metadata

        there is no setter as provenance can only grow. use
        append_provenance_step to add steps

        :return: The provenance element
        :rtype: Provenance
        """
        return self._provenance

    def append_provenance_step(self, title, description, timestamp=None):
        """
        Add a step to the provenance of the metadata

        :param title: The title of the step.
        :type title: str

        :param description: The content of the step
        :type description: str

        :param timestamp: the time of the step
        :type timestamp: datetime, str
        """
        step_time = self._provenance.append_step(title, description, timestamp)
        if step_time > self.last_update:
            self.last_update = step_time

    def append_if_provenance_step(
            self, title, description, timestamp=None, data=None):
        """Add a if provenance step to the provenance of the metadata

        :param title: The title of the step.
        :type title: str

        :param description: The content of the step
        :type description: str

        :param timestamp: the time of the step
        :type timestamp: datetime, str

        :param data: The data of the step.
        :type data: dict
        """
        step_time = self._provenance.append_if_provenance_step(
            title, description, timestamp, data)
        if step_time > self.last_update:
            self.last_update = step_time

    def update_from_dict(self, keywords):
        """Update metadata value from a keywords dictionary.

        :param keywords:
        :return:
        """
        super(ImpactLayerMetadata, self).update_from_dict(keywords)

        if 'if_provenance' in list(keywords.keys()):
            if_provenance = keywords['if_provenance']
            for provenance_step in if_provenance:
                self.provenance.append_provenance_step(provenance_step)