def _generate_tables(self, aoi_mode=True):
        """Parses the postprocessing output as one table per postprocessor.

        TODO: This should rather return json and then have a helper method to
        make html from the JSON.

        :param aoi_mode: adds a Total in aggregation areas
        row to the calculated table
        :type aoi_mode: bool

        :returns: The html.
        :rtype: str
        """
        message = m.Message()

        for processor, results_list in self.output.iteritems():

            self.current_output_postprocessor = processor
            # results_list is for example:
            # [
            # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([
            #        (u'Total', {'value': 977536, 'metadata': {}}),
            #        (u'Female population', {'value': 508319, 'metadata': {}}),
            #        (u'Weekly hygiene packs', {'value': 403453, 'metadata': {
            #         'description': 'Females hygiene packs for weekly use'}})
            #    ]))
            # ]

            # sorting using the first indicator of a postprocessor
            sorted_results = sorted(results_list,
                                    key=self._sort_no_data,
                                    reverse=True)

            # init table
            has_no_data = False
            table = m.Table(style_class='table table-condensed table-striped')
            table.caption = self.tr('Detailed %s report') % (tr(
                get_postprocessor_human_name(processor)).lower())

            # Dirty hack to make "evacuated" comes out in the report.
            # Currently only MinimumNeeds that calculate from evacuation
            # percentage.
            if processor == 'MinimumNeeds':
                if 'evacuation_percentage' in self.function_parameters.keys():
                    table.caption = self.tr(
                        'Detailed %s report (for people needing '
                        'evacuation)') % (tr(
                            get_postprocessor_human_name(processor)).lower())
                else:
                    table.caption = self.tr(
                        'Detailed %s report (affected people)') % (tr(
                            get_postprocessor_human_name(processor)).lower())

            if processor in ['Gender', 'Age']:
                table.caption = self.tr(
                    'Detailed %s report (affected people)') % (tr(
                        get_postprocessor_human_name(processor)).lower())

            header = m.Row()
            header.add(str(self.attribute_title).capitalize())
            for calculation_name in sorted_results[0][1]:
                header.add(self.tr(calculation_name))
            table.add(header)

            # used to calculate the totals row as per issue #690
            postprocessor_totals = OrderedDict()

            for zone_name, calc in sorted_results:
                row = m.Row(zone_name)

                for indicator, calculation_data in calc.iteritems():
                    value = calculation_data['value']
                    value = str(unhumanize_number(value))
                    if value == self.aggregator.get_default_keyword('NO_DATA'):
                        has_no_data = True
                        value += ' *'
                        try:
                            postprocessor_totals[indicator] += 0
                        except KeyError:
                            postprocessor_totals[indicator] = 0
                    else:
                        value = int(value)
                        try:
                            postprocessor_totals[indicator] += value
                        except KeyError:
                            postprocessor_totals[indicator] = value
                    row.add(format_int(value))
                table.add(row)

            if not aoi_mode:
                # add the totals row
                row = m.Row(self.tr('Total in aggregation areas'))
                for _, total in postprocessor_totals.iteritems():
                    row.add(format_int(total))
                table.add(row)

            # add table to message
            message.add(table)
            if has_no_data:
                message.add(
                    m.EmphasizedText(
                        self.
                        tr('* "%s" values mean that there where some problems while '
                           'calculating them. This did not affect the other '
                           'values.') %
                        (self.aggregator.get_default_keyword('NO_DATA'))))

        return message
Exemple #2
0
 def test_static_and_error(self):
     """Test error message works when there is a static message in place."""
     self.message_viewer.static_message_event(None, m.Message('Hi'))
     text = self.fake_error()
     self.assertIn('****Problem', text)
Exemple #3
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    message.add(
        m.Paragraph(
            tr('The InaSAFE options dialog is used to control various aspects of '
               'the InaSAFE analysis and reporting environment. Here are brief '
               'descriptions of all the options available, grouped by the tab '
               'page on which they occur.')))

    header = m.Heading(tr('Organisation Profile tab'), **INFO_STYLE)
    message.add(header)

    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'inasafe-options-organisation-screenshot.png' % resources_path()),
                            style_class='text-center')

    message.add(paragraph)
    message.add(
        m.Paragraph(
            tr('The Organisation Profile tab provides several general settings:'
               )))
    bullets = m.BulletedList()
    bullets.add(
        m.Text(
            m.ImportantText(tr('Organisation')),
            tr(' - Use this option to specify the name of your organisation.'))
    )
    bullets.add(
        m.Text(
            m.ImportantText(tr('Contact email')),
            tr(' - Use this option to specify the contact person\'s email '
               'address to use in the generated metadata document.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Website')),
            tr(' - Use this option to set the website address to be used in '
               'the generated metadata document.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Use custom organisation logo')),
            tr(' - By default, InaSAFE will add the supporters logo to each '
               'map template. The supporters logo is also used at tbe bottom '
               'of the dock panel if the \'show organisation logo in dock\' '
               'option is enabled. You can use this option to replace the '
               'organisation logo with that of your own organisation. The logo '
               'will be rescaled automatically to fill the space provided.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Currency')),
            tr(' - InaSAFE will use the selected currency for the analysis.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Analysis license')),
            tr(' - Use this to set the usage and redistribution license for the '
               'generated impact layer.')))
    message.add(bullets)

    header = m.Heading(tr('Population Parameters tab'), **INFO_STYLE)
    message.add(header)

    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'inasafe-options-population-screenshot.png' % resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    message.add(
        m.Paragraph(
            tr('In this tab you can define some parameters that will be used by '
               'InaSAFE in the analysis of exposed populations. You have the option '
               'to change the parameters for whether the exposed population is '
               'considered to be affected by each hazard type and class, and the '
               'displacement rate that will be used for affected people.')))

    bullets = m.BulletedList()
    bullets.add(
        m.Text(
            m.ImportantText(tr('Affected')),
            tr(' - When this option is checked, people exposed to the hazard '
               'class will be included in the count of affected people.')))

    bullets.add(
        m.Text(
            m.ImportantText(tr('Displacement Rate')),
            tr(' - The displacement rate is used to estimate the number of '
               'people displaced for each hazard class. People must be affected '
               'before they can be displaced. ')))
    message.add(bullets)
    message.add(
        m.Paragraph(
            tr('Please refer to the InaSAFE manual for concept definitions and '
               'more information on the source of the hazard classifications and '
               'default settings. We really encourage you to consider these '
               'parameters carefully and to choose appropriate values for your '
               'local situation based on past events and expert knowledge.')))

    header = m.Heading(tr('GIS Environment tab'), **INFO_STYLE)
    message.add(header)
    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'inasafe-options-environment-screenshot.png' % resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    message.add(
        m.Paragraph(
            tr('The GIS Environment tab provides several general settings:')))

    bullets = m.BulletedList()

    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Always show welcome message when opening QGIS with InaSAFE'
                   )),
            tr(' - When this option is enabled, the welcome message will be '
               'enabled when opening QGIS with InaSAFE. By default the Welcome '
               'message will be displayed.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Show organisation logo in InaSAFE dock')),
            tr(' - When this option is enabled, a logo will be displayed at the '
               'bottom of the InaSAFE dock widget. By default the logo used '
               'is the InaSAFE supporters logo, but you can alter this by '
               'setting the \'Use custom organisation logo\' option in '
               'the template options tab (see below).')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Show only visible layers in the InaSAFE dock')),
            tr(' - When this option is enabled layers that are not visible '
               'in the QGIS layers panel will not be shown in the hazard, '
               'exposure and aggregation combo boxes in the dock area.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Set QGIS layer name from title in keywords')),
            tr(' - If this option is enabled, the InaSAFE keywords title '
               'attribute will be used for the layer name in the QGIS Layers list '
               'when adding a layer.')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Zoom to impact layer on scenario estimate completion')),
            tr(' - When this option is enabled, the map view extents will '
               'be updated to match the extents of the generated impact layer '
               'once the analysis completes.')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Hide exposure on scenario estimate completion')),
            tr(' - Use this option if you prefer to not show the exposure '
               'layer as an underlay behind the generated impact layer.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Show only impact layer on report map')),
            tr('When this option is enabled, the map report created after an '
               'analysis completes will not show any other layers in your '
               'current project except for the impact layer. ')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Print atlas report on atlas driven template with the '
                   'aggregation layer')),
            tr('When this option is enabled, InaSAFE will generate an atlas '
               'report based on aggregation area if the template has atlas '
               'generation flag enabled.')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Use selected features only with the aggregation layer')),
            tr('If enabled, running an analysis with some features of the '
               'aggregation layer selected will constrain the analysis to only '
               'those selected aggregation areas, all others will be ignored.')
        ))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Location for results')),
            tr(' - By default, InaSAFE will write impact layer and intermediate '
               'outputs to the system temporary directory. On some operating '
               'systems, these temporary files will be deleted on each reboot. '
               'If you wish to, you can specify an alternative directory '
               'to use for storing these temporary files.')))
    message.add(bullets)

    header = m.Heading(tr('Earthquake tab'), **INFO_STYLE)
    message.add(header)
    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'inasafe-options-earthquake-screenshot.png' % resources_path()),
                            style_class='text-center')
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('In this tab you can select which earthquake fatality model to use '
           'when estimating earthquake impact on population. This option is '
           'global - it will affect all subsequent earthquake analyses carried '
           'out in InaSAFE.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('When selecting an earthquake analysis model, its details will be '
           'shown below in the text box area.'))
    message.add(paragraph)

    header = m.Heading(tr('Template Options tab'), **INFO_STYLE)
    message.add(header)
    paragraph = m.Paragraph(m.Image('file:///%s/img/screenshots/'
                                    'inasafe-options-template-screenshot.png' %
                                    resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    message.add(
        m.Paragraph(
            tr('This tab has options relating to the generation of map composer '
               'templates and how reports will be printed:'
               '.')))

    bullets = m.BulletedList()

    bullets.add(
        m.Text(
            m.ImportantText(tr('Use custom north arrow image')),
            tr(' - InaSAFE provides a basic north arrow which is placed on '
               'generated map compositions and rendered PDF reports. You can '
               'replace this north arrow with one of your own choosing using '
               'this option.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Use custom disclaimer text')),
            tr(' - By default, InaSAFE will display a disclaimer on reports '
               'advising readers of the report to exercise caution when '
               'interpreting the outputs presented. You can override this '
               'text using this option, though we do advise that you include '
               'a similar statement of caution in your overridden text.')))
    message.add(bullets)

    header = m.Heading(tr('Demographic Defaults tab'), **INFO_STYLE)
    message.add(header)
    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'inasafe-options-demographic-screenshot.png' % resources_path()),
                            style_class='text-center')
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('In this tab you will find options for setting the default ratios '
           'for demographic groups. There is more detailed help on demographic '
           'groups within the main help page for InaSAFE in the Field Mapping '
           'Tool section. Essentially default ratios for demographic groups '
           'determine what proportion of the population are within each '
           'population group (e.g. infants versus children etc.). The options '
           'defined in this tab are used in cases where you choose to use the '
           'global default ratios while configuring the keywords for an '
           'aggregation layer as shown below.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('Note that the contents of this tab may changed depending on what '
           'groups have been defined for demographic breakdowns.'))
    message.add(paragraph)
    paragraph = m.Paragraph(m.Image(
        'file:///%s/img/screenshots/'
        'field-mapping-tool-default-ratios-screenshot.png' % resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    header = m.Heading(tr('Advanced tab'), **INFO_STYLE)
    message.add(header)
    paragraph = m.Paragraph(m.Image('file:///%s/img/screenshots/'
                                    'inasafe-options-advanced-screenshot.png' %
                                    resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    message.add(
        m.Paragraph(
            tr('This tab contains options intended for advanced users only: '))
    )

    bullets = m.BulletedList()
    bullets.add(
        m.Text(
            m.ImportantText(tr('Keyword cache for remote databases')),
            tr(' - When InaSAFE is used with remote layers (for example a '
               'database layer or a WFS layer), it is not possible to store the '
               'keywords for the layer with the layer itself. To accommodate for '
               'these types of layers, InaSAFE writes the keywords to a small '
               'file based database (using sqlite) and then retrieves them based '
               'on unique connection details used for that layer. You can '
               'specify a custom path to be used for storing the keywords '
               'database using this option.')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('Help to improve InaSAFE by submitting errors to a '
                   'remote server')),
            tr(' - With this option enabled, InaSAFE will post any errors that '
               'occur to an online server for analysis by our development team. '
               'This option is disabled by default as some may consider some of '
               'the data submitted (IP Address, logged in user name) to be '
               'sensitive.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Enable developer mode')),
            tr(' - When this option is enabled, right clicking on the webview '
               'widget in the dock will allow you to debug the generated HTML. '
               'In addition, if the metadata.txt for the running InaSAFE is '
               'set to \'alpha\', an additional icon will be added to the '
               'toolbar to add test layers to the QGIS project.')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Generate reports')),
            tr(' - When this option is enabled, InaSAFE will generate reports. '
               )))
    bullets.add(
        m.Text(
            m.ImportantText(tr('Show memory profile')),
            tr(' - When this option is enabled, InaSAFE will display the memory '
               'profile when it runs. ')))

    message.add(bullets)

    return message
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 4.0.0

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    # We will store a contents section at the top for easy navigation
    table_of_contents = m.Message()
    # and this will be the main message that we create
    message = m.Message()

    _create_section_header(
        message,
        table_of_contents,
        'overview',
        tr('Overview'),
        heading_level=1)
    ##
    # Credits and disclaimers ...
    ##
    _create_section_header(
        message,
        table_of_contents,
        'disclaimer',
        tr('Disclaimer'),
        heading_level=2)
    message.add(m.Paragraph(definitions.messages.disclaimer()))

    _create_section_header(
        message,
        table_of_contents,
        'limitations',
        tr('Limitations and License'),
        heading_level=2)
    bullets = m.BulletedList()
    for item in definitions.limitations():
        bullets.add(item)
    message.add(bullets)

    ##
    # Basic concepts ...
    ##
    ##
    # Help dialog contents ...
    ##
    _create_section_header(
        message,
        table_of_contents,
        'glossary',
        tr('Glossary of terms'),
        heading_level=1)

    last_group = None
    table = None
    for key, value in list(definitions.concepts.items()):
        current_group = value['group']
        if current_group != last_group:
            if last_group is not None:
                message.add(table)
            _create_section_header(
                message,
                table_of_contents,
                current_group.replace(' ', '-'),
                current_group,
                heading_level=2)
            table = _start_glossary_table(current_group)
            last_group = current_group
        row = m.Row()
        term = value['key'].replace('_', ' ').title()
        description = m.Message(value['description'])
        for citation in value['citations']:
            if citation['text'] in [None, '']:
                continue
            if citation['link'] in [None, '']:
                description.add(m.Paragraph(citation['text']))
            else:
                description.add(m.Paragraph(
                    m.Link(citation['link'], citation['text'])))
        row.add(m.Cell(term))
        row.add(m.Cell(description))
        url = _definition_icon_url(value)
        if url:
            row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE)))
        else:
            row.add(m.Cell(''))
        table.add(row)
    # ensure the last group's table is added
    message.add(table)

    ##
    # Help dialog contents ...
    ##
    _create_section_header(
        message,
        table_of_contents,
        'core-functionality',
        tr('Core functionality and tools'),
        heading_level=1)

    _create_section_header(
        message,
        table_of_contents,
        'dock',
        tr('The InaSAFE Dock'),
        heading_level=2)
    message.add(dock_help())

    _create_section_header(
        message,
        table_of_contents,
        'reports',
        tr('InaSAFE Reports'),
        heading_level=2)
    message.add(report_help())

    _create_section_header(
        message,
        table_of_contents,
        'extents',
        tr('Managing analysis extents with the extents selector'),
        heading_level=2)
    message.add(extent_help())

    _create_section_header(
        message,
        table_of_contents,
        'options',
        tr('InaSAFE Options'),
        heading_level=2)
    message.add(options_help())

    _create_section_header(
        message,
        table_of_contents,
        'batch-runner',
        tr('The Batch Runner'),
        heading_level=2)
    message.add(batch_help())

    _create_section_header(
        message,
        table_of_contents,
        'osm-downloader',
        tr('The OpenStreetmap Downloader'),
        heading_level=2)
    message.add(osm_help())

    _create_section_header(
        message,
        table_of_contents,
        'petabencana-downloader',
        tr('The PetaBencana Downloader'),
        heading_level=2)
    message.add(petabencana_help())

    _create_section_header(
        message,
        table_of_contents,
        'shakemap-converter',
        tr('The Shakemap Converter'),
        heading_level=2)
    message.add(shakemap_help())

    _create_section_header(
        message,
        table_of_contents,
        'multi-buffer-tool',
        tr('The Multi Buffer Tool'),
        heading_level=2)
    message.add(multi_buffer_help())

    # Field mapping tool has a few added bits to enumerate the groups
    _create_section_header(
        message,
        table_of_contents,
        'field-mapping-tool',
        tr('The Field Mapping Tool'),
        heading_level=2)
    message.add(field_mapping_tool_help())

    _create_section_header(
        message,
        table_of_contents,
        'exposure-groups',
        tr('Exposure Groups'),
        heading_level=3)
    message.add(m.Paragraph(
        'The following demographic groups apply only to vector population '
        'exposure layers:'
    ))
    for group in population_field_groups:
        definition_to_message(
            group, message, table_of_contents, heading_level=4)

    _create_section_header(
        message,
        table_of_contents,
        'aggregation-groups',
        tr('Aggregation Groups'),
        heading_level=3)
    message.add(m.Paragraph(
        'The following demographic groups apply only to aggregation layers:'
    ))
    for group in aggregation_field_groups:
        definition_to_message(
            group, message, table_of_contents, heading_level=4)

    # End of field mapping tool help

    # Keep this last in the tool section please as it has subsections
    # and so uses the top level section style
    _create_section_header(
        message,
        table_of_contents,
        'minimum-needs',
        tr('Minimum Needs'),
        heading_level=2)
    _create_section_header(
        message,
        table_of_contents,
        'minimum-needs-tool',
        tr('The minimum needs tool'),
        heading_level=3)
    message.add(needs_help())
    _create_section_header(
        message,
        table_of_contents,
        'minimum-manager',
        tr('The minimum needs manager'),
        heading_level=3)
    message.add(needs_manager_help())

    ##
    #  Analysis workflow
    ##

    _create_section_header(
        message,
        table_of_contents,
        'analysis-steps',
        tr('Analysis steps'),
        heading_level=1)
    _create_section_header(
        message,
        table_of_contents,
        'analysis-internal-process',
        tr('Analysis internal process'),
        heading_level=2)
    analysis = definitions.concepts['analysis']
    message.add(analysis['description'])
    url = _definition_screenshot_url(analysis)
    if url:
        message.add(m.Paragraph(m.Image(url), style_class='text-center'))

    _create_section_header(
        message,
        table_of_contents,
        'analysis-progress-reporting',
        tr('Progress reporting steps'),
        heading_level=2)
    steps = list(definitions.analysis_steps.values())
    for step in steps:
        definition_to_message(
            step, message, table_of_contents, heading_level=3)

    ##
    #  Hazard definitions
    ##

    _create_section_header(
        message,
        table_of_contents,
        'hazards',
        tr('Hazard Concepts'),
        heading_level=1)

    hazard_category = definitions.hazard_category
    definition_to_message(
        hazard_category,
        message,
        table_of_contents,
        heading_level=2)

    hazards = definitions.hazards
    definition_to_message(
        hazards,
        message,
        table_of_contents,
        heading_level=2)

    ##
    #  Exposure definitions
    ##

    _create_section_header(
        message,
        table_of_contents,
        'exposures',
        tr('Exposure Concepts'),
        heading_level=1)
    exposures = definitions.exposures

    definition_to_message(
        exposures,
        message,
        table_of_contents,
        heading_level=2)

    ##
    #  Defaults
    ##

    _create_section_header(
        message,
        table_of_contents,
        'defaults',
        tr('InaSAFE Defaults'),
        heading_level=1)
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Default value'), header=True))
    row.add(m.Cell(tr('Default min'), header=True))
    row.add(m.Cell(tr('Default max'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    table.add(row)
    defaults = [
        definitions.youth_ratio_default_value,
        definitions.adult_ratio_default_value,
        definitions.elderly_ratio_default_value,
        definitions.female_ratio_default_value,
        definitions.feature_rate_default_value
    ]
    for default in defaults:
        row = m.Row()
        row.add(m.Cell(default['name']))
        row.add(m.Cell(default['default_value']))
        row.add(m.Cell(default['min_value']))
        row.add(m.Cell(default['max_value']))
        row.add(m.Cell(default['description']))
        table.add(row)
    message.add(table)

    ##
    #  All Fields
    ##

    _create_section_header(
        message,
        table_of_contents,
        'all-fields',
        tr('Fields'),
        heading_level=1)
    _create_section_header(
        message,
        table_of_contents,
        'input-fields',
        tr('Input dataset fields'),
        heading_level=2)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Exposure fields'),
        definitions.exposure_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Hazard fields'),
        definitions.hazard_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Aggregation fields'),
        definitions.aggregation_fields)
    _create_section_header(
        message,
        table_of_contents,
        'output-fields',
        tr('Output dataset fields'),
        heading_level=2)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Impact fields'),
        definitions.impact_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Aggregate hazard fields'),
        definitions.aggregate_hazard_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Aggregation summary fields'),
        definitions.aggregation_summary_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Exposure summary table fields'),
        definitions.exposure_summary_table_fields)
    _create_fields_section(
        message,
        table_of_contents,
        tr('Analysis fields'),
        definitions.analysis_fields)

    ##
    #  Geometries
    ##

    _create_section_header(
        message,
        table_of_contents,
        'geometries',
        tr('Layer Geometry Types'),
        heading_level=1)
    _create_section_header(
        message,
        table_of_contents,
        'vector-geometries',
        tr('Vector'),
        heading_level=2)
    definition_to_message(
        definitions.layer_geometry_point,
        message,
        table_of_contents,
        heading_level=3)
    definition_to_message(
        definitions.layer_geometry_line,
        message,
        table_of_contents,
        heading_level=3)
    definition_to_message(
        definitions.layer_geometry_polygon,
        message,
        table_of_contents,
        heading_level=3)
    _create_section_header(
        message,
        table_of_contents,
        'raster-geometries',
        tr('Raster'),
        heading_level=2)
    definition_to_message(
        definitions.layer_geometry_raster,
        message,
        table_of_contents,
        heading_level=3)

    ##
    #  Layer Modes
    ##

    _create_section_header(
        message,
        table_of_contents,
        'layer-modes',
        tr('Layer Modes'),
        heading_level=1)
    definition_to_message(
        definitions.layer_mode,
        message,
        table_of_contents,
        heading_level=2)

    ##
    #  Layer Purposes
    ##

    _create_section_header(
        message,
        table_of_contents,
        'layer-purposes',
        tr('Layer Purposes'),
        heading_level=1)
    definition_to_message(
        definitions.layer_purpose_hazard,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_exposure,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_aggregation,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_exposure_summary,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_aggregate_hazard_impacted,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_aggregation_summary,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_exposure_summary_table,
        message,
        table_of_contents,
        heading_level=2)
    definition_to_message(
        definitions.layer_purpose_profiling,
        message,
        table_of_contents,
        heading_level=2)

    ##
    # All units
    ##

    _create_section_header(
        message,
        table_of_contents,
        'all-units',
        tr('All Units'),
        heading_level=1)
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Plural'), header=True))
    row.add(m.Cell(tr('Abbreviation'), header=True))
    row.add(m.Cell(tr('Details'), header=True))
    table.add(row)
    for unit in definitions.units_all:
        row = m.Row()
        row.add(m.Cell(unit['name']))
        row.add(m.Cell(unit['plural_name']))
        row.add(m.Cell(unit['abbreviation']))
        row.add(m.Cell(unit['description']))
        table.add(row)
    message.add(table)

    ##
    #  Post processors
    ##

    _create_section_header(
        message,
        table_of_contents,
        'post-processors',
        tr('Post Processors'),
        heading_level=1)
    _create_section_header(
        message,
        table_of_contents,
        'post-processor-input-types',
        tr('Post Processor Input Types'),
        heading_level=2)
    table = _create_post_processor_subtable(
        post_processor_input_types
    )
    message.add(table)

    _create_section_header(
        message,
        table_of_contents,
        'post-processor-input-values',
        tr('Post Processor Input Values'),
        heading_level=2)
    table = _create_post_processor_subtable(
        post_processor_input_values
    )
    message.add(table)

    _create_section_header(
        message,
        table_of_contents,
        'post-processor-process-values',
        tr('Post Processor Process Types'),
        heading_level=2)
    table = _create_post_processor_subtable(
        safe.processors.post_processor_process_types
    )
    message.add(table)

    _create_section_header(
        message,
        table_of_contents,
        'post-processors',
        tr('Post Processors'),
        heading_level=2)
    post_processors = safe.processors.post_processors
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Input Fields'), header=True))
    row.add(m.Cell(tr('Output Fields'), header=True))
    table.add(row)
    for post_processor in post_processors:
        row = m.Row()
        row.add(m.Cell(post_processor['name']))
        # Input fields
        bullets = m.BulletedList()
        for key, value in sorted(post_processor['input'].items()):
            bullets.add(key)
        row.add(m.Cell(bullets))
        # Output fields
        bullets = m.BulletedList()
        for key, value in sorted(post_processor['output'].items()):
            name = value['value']['name']
            formula_type = value['type']['key']
            if formula_type == 'formula':
                formula = value['formula']
            else:
                # We use python introspection because the processor
                # uses a python function for calculations
                formula = value['function'].__name__
                formula += ' ('
                formula += value['function'].__doc__
                formula += ')'
            bullets.add('%s  %s. : %s' % (
                name, formula_type, formula))

        row.add(m.Cell(bullets))
        table.add(row)
        # Add the descriptions
        row = m.Row()
        row.add(m.Cell(''))
        row.add(m.Cell(post_processor['description'], span=2))
        table.add(row)
    message.add(table)

    ##
    # Reporting
    ##
    _create_section_header(
        message,
        table_of_contents,
        'reporting',
        tr('Reporting'),
        heading_level=1)

    paragraph = m.Paragraph(
        m.ImportantText(tr('Note: ')),
        m.Text(tr(
            'This section of the help documentation is intended for advanced '
            'users who want to modify reports which are produced by InaSAFE.'
        )))
    message.add(paragraph)
    _create_section_header(
        message,
        table_of_contents,
        'reporting-overview',
        tr('Overview'),
        heading_level=2)
    message.add(m.Paragraph(tr(
        'Whenever InaSAFE completes an analysis, it will automatically '
        'generate a number of reports. Some of these reports are based on '
        'templates that are shipped with InaSAFE, and can be customised or '
        'over-ridden by creating your own templates. The following '
        'reports are produced in InaSAFE:'
    )))
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Customisable?'), header=True))
    row.add(m.Cell(tr('Example'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    table.add(row)

    for report in all_reports:
        row = m.Row()
        row.add(m.Cell(report['name']))
        if report['customisable']:
            row.add(m.Cell(tr('Yes')))
        else:
            row.add(m.Cell(tr('No')))
        png_image_path = resources_path(
            'img', 'screenshots', report['thumbnail'])
        row.add(m.Image(png_image_path, style_class='text-center'))
        row.add(m.Cell(report['description']))
        table.add(row)

    message.add(table)

    message.add(m.Paragraph(tr(
        'In the sections that follow, we provide more technical information '
        'about the custom QGIS Expressions and special template elements '
        'that can be used to customise your templates.'
    )))

    _create_section_header(
        message,
        table_of_contents,
        'reporting-expressions',
        tr('QGIS Expressions'),
        heading_level=2)
    message.add(m.Paragraph(tr(
        'InaSAFE adds a number of expressions that can be used to '
        'conveniently obtain provenance data to the active analysis results. '
        'The expressions can also be used elsewhere in QGIS as needed.'
        '.'
    )))

    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    table.add(row)
    for expression_name, expression in sorted(qgis_expressions().items()):
        row = m.Row()
        row.add(m.Cell(expression_name))
        help = expression.helptext()
        # This pattern comes from python/qgis/core/__init__.py ≈ L79
        pattern = r'<h3>(.*) function</h3><br>'
        help = re.sub(pattern, '', help)
        help = re.sub(r'\n', '<br>', help)
        row.add(m.Cell(help))
        table.add(row)
    message.add(table)

    _create_section_header(
        message,
        table_of_contents,
        'reporting-composer-elements',
        tr('Composer Elements'),
        heading_level=2)
    message.add(m.Paragraph(tr(
        'InaSAFE looks for elements with specific id\'s on the composer '
        'page and replaces them with InaSAFE specific content.'
    )))
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('ID'), header=True))
    row.add(m.Cell(tr('Description'), header=True))
    table.add(row)
    for item in html_frame_elements:
        row = m.Row()
        row.add(m.Cell(item['id']))
        row.add(m.Cell(item['description']))
        table.add(row)
    message.add(table)

    ##
    # Developer documentation
    ##
    _create_section_header(
        message,
        table_of_contents,
        'developer-guide',
        tr('Developer Guide'),
        heading_level=1)
    message.add(developer_help())

    # Finally we add the table of contents at the top
    full_message = m.Message()
    # Contents is not a link so reset style
    style = SECTION_STYLE
    style['element_id'] = ''
    header = m.Heading(tr('Contents'), **style)
    full_message.add(header)
    full_message.add(table_of_contents)
    full_message.add(message)
    return full_message
Exemple #5
0
    def to_message(self, keywords=None, show_header=True):
        """Format keywords as a message object.

        .. versionadded:: 3.2

        .. versionchanged:: 3.3 - default keywords to None

        The message object can then be rendered to html, plain text etc.

        :param keywords: Keywords to be converted to a message. Optional. If
            not passed then we will attempt to get keywords from self.layer
            if it is not None.
        :type keywords: dict

        :param show_header: Flag indicating if InaSAFE logo etc. should be
            added above the keywords table. Default is True.
        :type show_header: bool

        :returns: A safe message object containing a table.
        :rtype: safe.messaging.message
        """
        if keywords is None and self.layer is not None:
            keywords = self.read_keywords(self.layer)
        # This order was determined in issue #2313
        preferred_order = [
            'title',
            'layer_purpose',
            'exposure',
            'hazard',
            'hazard_category',
            'layer_geometry',
            'layer_mode',
            'vector_hazard_classification',
            'exposure_unit',
            'continuous_hazard_unit',
            'volcano_name_field',
            'road_class_field',
            'population_field',
            'name_field',
            'structure_class_field',
            'field',
            'value_map',  # attribute values
            'value_mapping',  # attribute values
            'resample',
            'source',
            'url',
            'scale',
            'license',
            'date',
            'keyword_version'
        ]  # everything else in arbitrary order
        report = m.Message()
        if show_header:
            logo_element = m.Brand()
            report.add(logo_element)
            report.add(m.Heading(self.tr(
                'Layer keywords:'), **styles.INFO_STYLE))
            report.add(m.Text(self.tr(
                'The following keywords are defined for the active layer:')))

        table = m.Table(style_class='table table-condensed table-striped')
        # First render out the preferred order keywords
        for keyword in preferred_order:
            if keyword in keywords:
                value = keywords[keyword]
                row = self._keyword_to_row(keyword, value)
                keywords.pop(keyword)
                table.add(row)

        # now render out any remaining keywords in arbitrary order
        for keyword in keywords:
            value = keywords[keyword]
            row = self._keyword_to_row(keyword, value)
            table.add(row)

        # If the keywords class was instantiated with a layer object
        # we can add some context info not stored in the keywords themselves
        # but that is still useful to see...
        if self.layer:
            # First the CRS
            keyword = self.tr('Reference system')
            value = self.layer.crs().authid()
            row = self._keyword_to_row(keyword, value)
            table.add(row)
            # Next the data source
            keyword = self.tr('Layer source')
            value = self.layer.source()
            row = self._keyword_to_row(keyword, value, wrap_slash=True)
            table.add(row)

        # Finalise the report
        report.add(table)
        return report
Exemple #6
0
def check_memory_usage(buffered_geo_extent, cell_size):
    """Helper to check if analysis is feasible when extents change.

    For simplicity, we will do all our calculations in geocrs.

    :param buffered_geo_extent: An extent in the for [xmin, ymin, xmax, ymax]
    :type buffered_geo_extent: list

    :param cell_size: The size of a cell (assumes in the X direction).
    :type cell_size: float

    :returns: True if it appears we have enough memory (or we can't compute
        it), False if it appears we do not have enough.
    :rtype: bool

    :raises: A Message containing notes about how much memory is needed
        for a single raster and if this is likely to result in an error.

    :returns: True if it is supposed that there is sufficient memory,
        False if it is supposed that too little memory exists.
    :rtype: bool
    """
    message = m.Message()
    check_heading = m.Heading(tr('Checking available memory'),
                              **PROGRESS_UPDATE_STYLE)
    message.add(check_heading)

    width = buffered_geo_extent[2] - buffered_geo_extent[0]
    height = buffered_geo_extent[3] - buffered_geo_extent[1]
    try:
        # noinspection PyAugmentAssignment
        width = width / cell_size
        # noinspection PyAugmentAssignment
        height = height / cell_size
    except TypeError:
        # Could have been a vector layer for example
        reason = tr(
            'Computed cellsize was None. Memory check currently only works '
            'for raster input layers.')
        message.add(reason)
        send_dynamic_message(dispatcher.Anonymous, message)
        return True  # assume enough mem since we have no vector check logic

    bullet_list = m.BulletedList()
    bullet = m.Paragraph(m.ImportantText(tr('Width: ')), str(width))
    bullet_list.add(bullet)
    bullet = m.Paragraph(m.ImportantText(tr('Height: ')), str(height))
    bullet_list.add(bullet)
    bullet = m.Paragraph(m.ImportantText(tr('Cell Size: ')), str(cell_size))
    bullet_list.add(bullet)
    message.add(bullet_list)

    # Compute mem requirement in MB (assuming numpy uses 8 bytes by per
    # cell) see this link:
    # http://stackoverflow.com/questions/11784329/
    #      python-memory-usage-of-numpy-arrays
    # Also note that the on-disk requirement of the clipped tifs is about
    # half this since the tifs as in single precision,
    # whereas numpy arrays are in double precision.
    requirement = ((width * height * 8) / 1024 / 1024)
    try:
        free_memory = get_free_memory()
    except ValueError:
        error_heading = m.Heading(tr('Memory check error'), **WARNING_STYLE)
        error_message = tr('Could not determine free memory')
        message.add(error_heading)
        message.add(error_message)
        send_dynamic_message(dispatcher.Anonymous, message)
        LOGGER.exception(message)
        return True  # still let the user try to run their analysis

    # We work on the assumption that if more than 10% of the available
    # memory is occupied by a single layer we could run out of memory
    # (depending on the impact function). This is because multiple
    # in memory copies of the layer are often made during processing.
    warning_limit = 10
    usage_indicator = (float(requirement) / float(free_memory)) * 100
    counts_message = tr('Memory requirement: about %d mb per raster layer ('
                        '%d mb available)') % (requirement, free_memory)
    usage_message = tr('Memory used / available: %d/%d') % (usage_indicator,
                                                            warning_limit)
    message.add(counts_message)
    message.add(usage_message)

    if warning_limit <= usage_indicator:
        warning_heading = m.Heading(tr('Potential memory issue'),
                                    **WARNING_STYLE)
        warning_message = tr(
            'There may not be enough free memory to run this analysis. You can'
            ' attempt to run the analysis anyway, but note that your computer '
            'may become unresponsive during execution, and / or the analysis '
            'may fail due to insufficient memory. Proceed at your own risk.')
        suggestion_heading = m.Heading(tr('Suggestion'), **SUGGESTION_STYLE)
        suggestion = tr(
            'Try zooming in to a smaller area or using a raster layer with a '
            'coarser resolution to speed up execution and reduce memory '
            'requirements. You could also try adding more RAM to your '
            'computer.')

        message.add(warning_heading)
        message.add(warning_message)
        message.add(suggestion_heading)
        message.add(suggestion)
        send_dynamic_message(dispatcher.Anonymous, message)
        # LOGGER.info(message.to_text())
        return False

    send_dynamic_message(dispatcher.Anonymous, message)
    # LOGGER.info(message.to_text())
    return True
Exemple #7
0
    def _generate_tables(self, aoi_mode=True):
        """Parses the postprocessing output as one table per postprocessor.

        TODO: This should rather return json and then have a helper method to
        make html from the JSON.

        :param aoi_mode: adds a Total in aggregation areas
        row to the calculated table
        :type aoi_mode: bool

        :returns: The html.
        :rtype: str
        """
        message = m.Message()

        for processor, results_list in self.output.iteritems():

            self.current_output_postprocessor = processor
            # results_list is for example:
            # [
            # (PyQt4.QtCore.QString(u'Entire area'), OrderedDict([
            #        (u'Total', {'value': 977536, 'metadata': {}}),
            #        (u'Female population', {'value': 508319, 'metadata': {}}),
            #        (u'Weekly hygiene packs', {'value': 403453, 'metadata': {
            #         'description': 'Females hygiene packs for weekly use'}})
            #    ]))
            # ]

            # sorting using the first indicator of a postprocessor
            sorted_results = sorted(results_list,
                                    key=self._sort_no_data,
                                    reverse=True)

            # init table
            has_no_data = False
            table = m.Table(style_class='table table-condensed table-striped')
            name = get_postprocessor_human_name(processor).lower()

            if name == 'building type':
                table.caption = self.tr('Closed buildings')
            elif name == 'road type':
                table.caption = self.tr('Closed roads')
            elif name == 'people':
                table.caption = self.tr('Affected people')

            # Dirty hack to make "evacuated" come out in the report.
            # Currently only MinimumNeeds that calculate from evacuation
            # percentage.
            if processor == 'MinimumNeeds':
                if 'evacuation_percentage' in self.function_parameters.keys():
                    table.caption = self.tr(
                        'Detailed %s report (for people needing '
                        'evacuation)') % (tr(
                            get_postprocessor_human_name(processor)).lower())
                else:
                    table.caption = self.tr(
                        'Detailed %s report (affected people)') % (tr(
                            get_postprocessor_human_name(processor)).lower())

            if processor in ['Gender', 'Age']:
                table.caption = self.tr(
                    'Detailed %s report (affected people)') % (tr(
                        get_postprocessor_human_name(processor)).lower())

            empty_table = not sorted_results[0][1]
            if empty_table:
                # Due to an error? The table is empty.
                message.add(table)
                message.add(
                    m.EmphasizedText(
                        self.tr('Could not compute the %s report.' % tr(
                            get_postprocessor_human_name(processor)).lower())))
                continue

            header = m.Row()
            header.add(str(self.attribute_title).capitalize())
            for calculation_name in sorted_results[0][1]:
                header.add(self.tr(calculation_name))
            table.add(header)

            # used to calculate the totals row as per issue #690
            postprocessor_totals = OrderedDict()

            null_index = 0  # counting how many null value in the data
            for zone_name, calc in sorted_results:
                if isinstance(zone_name, QPyNullVariant):
                    # I have made sure that the zone_name won't be Null in
                    # run method. But just in case there is something wrong.
                    zone_name = 'Unnamed Area %s' % null_index
                    null_index += 1
                if name == 'road type':
                    # We add the unit 'meter' as we are counting roads.
                    zone_name = '%s (m)' % zone_name
                row = m.Row(zone_name)

                for indicator, calculation_data in calc.iteritems():
                    value = calculation_data['value']
                    value = str(unhumanize_number(value))
                    if value == self.aggregator.get_default_keyword('NO_DATA'):
                        has_no_data = True
                        value += ' *'
                        try:
                            postprocessor_totals[indicator] += 0
                        except KeyError:
                            postprocessor_totals[indicator] = 0
                    else:
                        value = int(value)
                        try:
                            postprocessor_totals[indicator] += value
                        except KeyError:
                            postprocessor_totals[indicator] = value
                    row.add(format_int(value))
                table.add(row)

            if not aoi_mode:
                # add the totals row
                row = m.Row(self.tr('Total in aggregation areas'))
                for _, total in postprocessor_totals.iteritems():
                    row.add(format_int(total))
                table.add(row)

            # add table to message
            message.add(table)
            if has_no_data:
                message.add(
                    m.EmphasizedText(
                        self.
                        tr('* "%s" values mean that there where some problems while '
                           'calculating them. This did not affect the other '
                           'values.') %
                        (self.aggregator.get_default_keyword('NO_DATA'))))
            caption = m.EmphasizedText(
                self.tr('Columns containing exclusively 0 and "%s" '
                        'have not been shown in the table.' %
                        self.aggregator.get_default_keyword('NO_DATA')))
            message.add(m.Paragraph(caption, style_class='caption'))

        return message
Exemple #8
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    other contexts.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """

    message = m.Message()

    message.add(
        m.Paragraph(
            tr('With this tool you can set up numerous scenarios and run them all in '
               'one go. A typical use case may be where you define a number of e.g. '
               'flood impact scenarios all using a standard data set e.g. '
               'flood.shp. As new flood data becomes available you replace flood.shp '
               'and rerun the scenarios using the batch runner. Using this approach '
               'you can quickly produce regional contingency plans as your '
               'understanding of hazards changes. When you run the batch of '
               'scenarios, pdf reports are generated automatically and all placed in '
               'a single common directory making it easy for you to browse and '
               'disseminate the reports produced.')))

    message.add(
        m.Paragraph(
            tr('When the batch process completes, it will also produce a summary '
               'report like this:')))

    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row(m.Cell(tr('InaSAFE Batch Report File')), header=True)
    table.add(row)
    table.add(m.Row(m.Cell('P: gempa bumi Sumatran fault (Mw7.8)')))
    table.add(m.Row(m.Cell('P: gempa di Yogya tahun 2006')))
    table.add(m.Row(m.Cell('P: banjir jakarta 2007')))
    table.add(m.Row(m.Cell('P: Tsunami di Maumere (Mw 8.1)')))
    table.add(m.Row(m.Cell('P: gempa Mw6.5 Palu-Koro Fault')))
    table.add(m.Row(m.Cell('P: gunung merapi meletus')))
    table.add(m.Row(m.Cell('-----------------------------')))
    table.add(m.Row(m.Cell(tr('Total passed: 6'))))
    table.add(m.Row(m.Cell(tr('Total failed: 0'))))
    table.add(m.Row(m.Cell(tr('Total tasks: 6'))))

    message.add(table)

    # message.add(m.Paragraph(tr(
    #     'For advanced users there is also the ability to batch run python '
    #     'scripts using this tool, but this should be considered an '
    #     'experimental</strong> feature still at this stage.')))

    message.add(
        m.Paragraph(
            tr('Before running the Batch Runner you might want to use the \'save '
               'scenario\' tool to first save some scenarios on which you '
               'can let the batch runner do its work. This tool lets you run saved '
               'scenarios in one go. It lets you select scenarios or let run all '
               'scenarios in one go.')))
    return message
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    message.add(
        m.Paragraph(
            tr('During and after a disaster, providing for the basic human minimum '
               'needs of food, water, hygiene and shelter is an important element of '
               'your contingency plan. InaSAFE has a customisable minimum needs '
               'system that allows you to define country or region specific '
               'requirements for compiling a needs report where the exposure '
               'layer represents population.')))
    message.add(
        m.Paragraph(
            tr('By default InaSAFE uses minimum needs defined for Indonesia - '
               'and ships with additional profiles for the Philippines and Tanzania. '
               'You can customise these or add your own region-specific profiles too.'
               )))
    message.add(
        m.Paragraph(
            tr('Minimum needs are grouped into regional or linguistic \'profiles\'. '
               'The default profile is \'BNPB_en\' - the english profile for the '
               'national disaster agency in Indonesia. '
               'You will see that this profile defines requirements for displaced '
               'persons in terms of Rice, Drinking Water, Clean Water (for bathing '
               'etc.), Family Kits (with personal hygiene items) and provision of '
               'toilets.')))
    message.add(
        m.Paragraph(
            tr('Each item in the profile can be customised or removed. For example '
               'selecting the first item in the list and then clicking on the '
               '\'pencil\' icon will show the details of how it was defined. '
               'If you scroll up and down in the panel you will see that for each '
               'item, you can set a name, description, units (in singular, '
               'plural and abbreviated forms), specify maxima and minima for the '
               'quantity of item allowed, a default and a frequency. You would use '
               'the maxima and minima to ensure that disaster managers never '
               'allocate amounts that will not be sufficient for human livelihood, '
               'and also that will not overtax the logistics operation for those '
               'providing humanitarian relief.')))
    message.add(
        m.Paragraph(
            tr('The final item in the item configuration is the \'readable '
               'sentence\' which bears special discussion. Using a simple system of '
               'tokens you can construct a sentence that will be used in the '
               'generated needs report.')))
    message.add(m.Heading(tr('Minimum needs profiles'), **INFO_STYLE))
    message.add(
        m.Paragraph(
            tr('A profile is a collection of resources that define the minimum needs '
               'for a particular country or region. Typically a profile should be '
               'based on a regional, national or international standard. The '
               'actual definition of which resources are needed in a given '
               'profile is dependent on the local conditions and customs for the '
               'area where the contingency plan is being devised.')))
    message.add(
        m.Paragraph(
            tr('For example in the middle east, rice is a staple food whereas in '
               'South Africa, maize meal is a staple food and thus the contingency '
               'planning should take these localised needs into account.')))

    message.add(m.Heading(tr('Minimum needs resources'), **INFO_STYLE))
    message.add(
        m.Paragraph(
            tr('Each item in a minimum needs profile is a resource. Each resource '
               'is described as a simple natural language sentence e.g.:')))

    message.add(
        m.EmphasizedText(
            tr('Each person should be provided with 2.8 kilograms of Rice weekly.'
               )))
    message.add(
        m.Paragraph(
            tr('By clicking on a resource entry in the profile window, and then '
               'clicking the black pencil icon you will be able to edit the '
               'resource using the resource editor. Alternatively you can create a '
               'new resource for a profile by clicking on the black + icon in '
               'the profile manager. You can also remove any resource from a '
               'profile using the - icon in the profile manager.')))

    message.add(m.Heading(tr('Resource Editor'), **INFO_STYLE))
    message.add(
        m.Paragraph(
            tr('When switching to edit or add resource mode, the minimum needs '
               'manager will be updated to show the resource editor. Each '
               'resource is described in terms of:')))
    bullets = m.BulletedList()
    bullets.add(
        m.Text(m.ImportantText(tr('resource name')), tr(' - e.g. Rice')))
    bullets.add(
        m.Text(m.ImportantText(tr('a description of the resource')),
               tr(' - e.g. Basic food')))
    bullets.add(
        m.Text(m.ImportantText(tr('unit in which the resource is provided')),
               tr(' - e.g. kilogram')))
    bullets.add(
        m.Text(m.ImportantText(tr('pluralised form of the units')),
               tr(' - e.g. kilograms')))
    bullets.add(
        m.Text(m.ImportantText(tr('abbreviation for the unit')),
               tr(' - e.g. kg')))
    bullets.add(
        m.Text(
            m.ImportantText(tr('the default allocation for the resource')),
            tr(' - e.g. 2.8. This number can be overridden on a '
               'per-analysis basis')))
    bullets.add(
        m.Text(
            m.ImportantText(
                tr('minimum allowed which is used to prevent allocating')),
            tr(' - e.g. no drinking water to displaced persons')))
    bullets.add(
        m.ImportantText(
            tr('maximum allowed which is used to set a sensible upper '
               'limit for the resource')))
    bullets.add(
        m.ImportantText(
            tr('a readable sentence which is used to compile the '
               'sentence describing the resource in reports.')))
    message.add(bullets)

    message.add(
        m.Paragraph(
            tr('These parameters are probably all fairly self explanatory, but '
               'the readable sentence probably needs further detail. The '
               'sentence is compiled using a simple keyword token replacement '
               'system. The following tokens can be used:')))

    bullets = m.BulletedList()
    bullets.add(m.Text('{{ Default }}'))
    bullets.add(m.Text('{{ Unit }}'))
    bullets.add(m.Text('{{ Units }}'))
    bullets.add(m.Text('{{ Unit abbreviation }}'))
    bullets.add(m.Text('{{ Resource name }}'))
    bullets.add(m.Text('{{ Frequency }}'))
    bullets.add(m.Text('{{ Minimum allowed }}'))
    bullets.add(m.Text('{{ Maximum allowed }}'))
    message.add(bullets)

    message.add(
        m.Paragraph(
            tr('When the token is placed in the sentence it will be replaced with '
               'the actual value at report generation time. This contrived example '
               'shows a tokenised sentence that includes all possible keywords:'
               )))

    message.add(
        m.EmphasizedText(
            tr('A displaced person should be provided with {{ %s }} '
               '{{ %s }}/{{ %s }}/{{ %s }} of {{ %s }}. Though no less than {{ %s }} '
               'and no more than {{ %s }}. This should be provided {{ %s }}.' %
               ('Default', 'Unit', 'Units', 'Unit abbreviation',
                'Resource name', 'Minimum allowed', 'Maximum allowed',
                'Frequency'))))
    message.add(
        m.Paragraph(tr('Would generate a human readable sentence like this:')))

    message.add(
        m.ImportantText(
            tr('A displaced person should be provided with 2.8 kilogram/kilograms/kg '
               'of rice. Though no less than 0 and no more than 100. This should '
               'be provided daily.')))
    message.add(
        m.Paragraph(
            tr('Once you have populated the resource elements, click the Save '
               'resource button to return to the profile view. You will see the '
               'new resource added in the profile\'s resource list.')))

    message.add(m.Heading(tr('Managing profiles'), **INFO_STYLE))
    message.add(
        m.Paragraph(
            tr('In addition to the profiles that come as standard with InaSAFE, you '
               'can create new ones, either from scratch, or based on an existing '
               'one (which you can then modify).')))
    message.add(
        m.Paragraph(
            tr('Use the New button to create new profile. When prompted, give your '
               'profile a name e.g. \'JakartaProfile\'.')))
    message.add(
        m.Paragraph(
            tr('Note: The profile must be saved in your home directory under '
               '.qgis2/minimum_needs in order for InaSAFE to successfully detect it.'
               )))
    message.add(
        m.Paragraph(
            tr('An alternative way to create a new profile is to use the Save as to '
               'clone an existing profile. The clone profile can then be edited '
               'according to your specific needs.')))
    message.add(m.Heading(tr('Active profile'), **INFO_STYLE))
    message.add(
        m.Paragraph(
            tr('It is important to note, that which ever profile you select in the '
               'Profile pick list, will be considered active and will be used as '
               'the basis for all minimum needs analysis. You need to restart '
               'QGIS before the changed profile become active.')))
    return message
    def show_info(self):
        """Show usage info to the user."""
        # Read the header and footer html snippets
        header = html_header()
        footer = html_footer()

        string = header

        heading = m.Heading(self.tr('OSM Downloader'), **INFO_STYLE)
        body = self.tr(
            'This tool will fetch building (\'structure\') or road ('
            '\'highway\') data from the OpenStreetMap project for you. '
            'The downloaded data will have InaSAFE keywords defined and a '
            'default QGIS style applied. To use this tool effectively:')
        tips = m.BulletedList()
        tips.add(
            self.
            tr('Your current extent, when opening this window, will be used to '
               'determine the area for which you want data to be retrieved.'
               'You can interactively select the area by using the '
               '\'select on map\' button - which will temporarily hide this '
               'window and allow you to drag a rectangle on the map. After you '
               'have finished dragging the rectangle, this window will '
               'reappear.'))
        tips.add(
            self.
            tr('Check the output directory is correct. Note that the saved '
               'dataset will be called either roads.shp or buildings.shp (and '
               'associated files).'))
        tips.add(
            self.
            tr('By default simple file names will be used (e.g. roads.shp, '
               'buildings.shp). If you wish you can specify a prefix to '
               'add in front of this default name. For example using a prefix '
               'of \'padang-\' will cause the downloaded files to be saved as '
               '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that '
               'the only allowed prefix characters are A-Z, a-z, 0-9 and the '
               'characters \'-\' and \'_\'. You can leave this blank if you '
               'prefer.'))
        tips.add(
            self.
            tr('If a dataset already exists in the output directory it will be '
               'overwritten.'))
        tips.add(
            self.
            tr('This tool requires a working internet connection and fetching '
               'buildings or roads will consume your bandwidth.'))
        tips.add(
            m.Link(
                'http://www.openstreetmap.org/copyright',
                text=self.tr(
                    'Downloaded data is copyright OpenStreetMap contributors'
                    ' (click for more info).')))
        message = m.Message()
        message.add(heading)
        message.add(body)
        message.add(tips)
        string += message.to_html()
        string += footer

        self.web_view.setHtml(string)
Exemple #11
0
    def format_impact_summary(self):
        """The impact summary as per category

        :returns: The impact summary.
        :rtype: safe.message.Message
        """
        flat_table = FlatTable().from_dict(
            groups=self.impact_table['groups'],
            data=self.impact_table['data'],
        )

        LOGGER.debug(self.impact_table['groups'])
        LOGGER.debug(self.impact_table['data'])

        LOGGER.debug(flat_table.groups)
        LOGGER.debug(flat_table.data)

        pivot_table = PivotTable(flat_table,
                                 row_field='landcover',
                                 column_field='hazard',
                                 columns=self.ordered_columns,
                                 affected_columns=self.affected_columns)

        report = {'impacted': pivot_table}

        # breakdown by zones
        if self.zone_field is not None:
            report['impacted_zones'] = {}
            for zone in flat_table.group_values('zone'):
                table = PivotTable(flat_table,
                                   row_field="landcover",
                                   column_field='hazard',
                                   columns=self.ordered_columns,
                                   affected_columns=self.affected_columns,
                                   filter_field="zone",
                                   filter_value=zone)
                report['impacted_zones'][zone] = table

        message = m.Message(style_class='container')
        affected_text = tr('Affected Area (ha)')

        show_affected = True if len(self.affected_columns) else False
        if show_affected:
            msg = tr(
                '* Percentage of affected area compared to the total area for '
                'the land cover type.')
            self.notes['fields'].append(msg)

        table = format_pivot_table(report['impacted'],
                                   header_text=affected_text,
                                   total_columns=True,
                                   total_affected=show_affected,
                                   total_percent_affected=show_affected,
                                   bar_chart=False)
        message.add(table)

        if 'impacted_zones' in report:
            message.add(
                m.Heading(tr('Analysis Results by Aggregation Area'),
                          **INFO_STYLE))
            for zone, table in report['impacted_zones'].items():
                message.add(m.Heading(zone.lower().title(), **SUB_INFO_STYLE))
                m_table = format_pivot_table(
                    table,
                    header_text=affected_text,
                    total_columns=True,
                    total_affected=show_affected,
                    total_percent_affected=show_affected,
                    bar_chart=False)
                message.add(m_table)

        return message.to_html(suppress_newlines=True)
Exemple #12
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 4.0.0

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    # We will store a contents section at the top for easy navigation
    table_of_contents = m.Message()
    # and this will be the main message that we create
    message = m.Message()

    _create_section_header(message, table_of_contents, 'overview',
                           tr('Overview'))
    ##
    # Credits and disclaimers ...
    ##
    header = m.Heading(tr('Disclaimer'), **BLUE_CHAPTER_STYLE)
    message.add(header)
    message.add(m.Paragraph(definitions.messages.disclaimer()))

    header = m.Heading(tr('Limitations and License'), **BLUE_CHAPTER_STYLE)
    message.add(header)
    bullets = m.BulletedList()
    for item in definitions.limitations():
        bullets.add(item)
    message.add(bullets)

    ##
    # Basic concepts ...
    ##
    ##
    # Help dialog contents ...
    ##
    _create_section_header(message, table_of_contents, 'glossary',
                           tr('Glossary of terms'))

    last_group = None
    table = None
    for key, value in definitions.concepts.iteritems():
        current_group = value['group']
        if current_group != last_group:
            if last_group is not None:
                message.add(table)
            header = m.Heading(current_group, **SUBSECTION_STYLE)
            message.add(header)
            table = _start_glossary_table(current_group)
            last_group = current_group
        row = m.Row()
        term = value['key'].replace('_', ' ').title()
        description = m.Message(value['description'])
        for citation in value['citations']:
            if citation['text'] in [None, '']:
                continue
            if citation['link'] in [None, '']:
                description.add(m.Paragraph(citation['text']))
            else:
                description.add(
                    m.Paragraph(m.Link(citation['link'], citation['text'])))
        row.add(m.Cell(term))
        row.add(m.Cell(description))
        table.add(row)
    # ensure the last group's table is added
    message.add(table)

    ##
    # Help dialog contents ...
    ##
    _create_section_header(message, table_of_contents, 'core-functionality',
                           tr('Core functionality and tools'))

    header = m.Heading(tr('The InaSAFE Dock'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(dock_help())

    header = m.Heading(tr('InaSAFE Reports'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(report_help())

    header = m.Heading(
        tr('Managing analysis extents with the extents selector'),
        **SUBSECTION_STYLE)
    message.add(header)
    message.add(extent_help())

    header = m.Heading(tr('InaSAFE Options'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(options_help())

    header = m.Heading(tr('The Batch Runner'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(batch_help())

    header = m.Heading(tr('The OpenStreetMap Downloader'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(osm_help())

    header = m.Heading(tr('The PetaBencana Downloader'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(petabencana_help())

    header = m.Heading(tr('The Shakemap Converter'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(shakemap_help())

    header = m.Heading(tr('The Multi Buffer Tool'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(multi_buffer_help())

    # Keep this last in the tool section please as it has subsections
    # and so uses the top level section style
    _create_section_header(message, table_of_contents, 'minimum-needs',
                           tr('Minimum Needs'))
    header = m.Heading(tr('The minimum needs tool'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(needs_help())
    header = m.Heading(tr('The minimum needs manager'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(needs_manager_help())

    ##
    #  Analysis workflow
    ##

    _create_section_header(message, table_of_contents, 'analysis-steps',
                           tr('Analysis steps'))
    header = m.Heading(tr('Analysis internal process'), **SUBSECTION_STYLE)
    message.add(header)
    analysis = definitions.concepts['analysis']
    message.add(analysis['description'])
    url = _definition_screenshot_url(analysis)
    if url:
        message.add(m.Image(url))
    header = m.Heading(tr('Progress reporting steps'), **SUBSECTION_STYLE)
    message.add(header)
    steps = definitions.analysis_steps.values()
    for step in steps:
        message.add(definition_to_message(step, BLUE_CHAPTER_STYLE))

    ##
    #  Hazard definitions
    ##

    _create_section_header(message, table_of_contents, 'hazards',
                           tr('Hazard Concepts'))

    hazard_category = definitions.hazard_category
    message.add(
        definition_to_message(hazard_category, heading_style=SUBSECTION_STYLE))

    hazards = definitions.hazards
    message.add(definition_to_message(hazards, heading_style=SUBSECTION_STYLE))

    ##
    #  Exposure definitions
    ##

    _create_section_header(message, table_of_contents, 'exposures',
                           tr('Exposure Concepts'))
    exposures = definitions.exposures
    message.add(
        definition_to_message(exposures, heading_style=SUBSECTION_STYLE))

    ##
    #  Defaults
    ##

    _create_section_header(message, table_of_contents, 'defaults',
                           tr('InaSAFE Defaults'))
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name')), header_flag=True)
    row.add(m.Cell(tr('Default value')), header_flag=True)
    row.add(m.Cell(tr('Default min')), header_flag=True)
    row.add(m.Cell(tr('Default max')), header_flag=True)
    row.add(m.Cell(tr('Description')), header_flag=True)
    table.add(row)
    defaults = [
        definitions.youth_ratio_default_value,
        definitions.adult_ratio_default_value,
        definitions.elderly_ratio_default_value,
        definitions.female_ratio_default_value,
        definitions.feature_rate_default_value
    ]
    for default in defaults:
        row = m.Row()
        row.add(m.Cell(default['name']))
        row.add(m.Cell(default['default_value']))
        row.add(m.Cell(default['min_value']))
        row.add(m.Cell(default['max_value']))
        row.add(m.Cell(default['description']))
        table.add(row)
    message.add(table)

    ##
    #  All Fields
    ##

    _create_section_header(message, table_of_contents, 'all-fields',
                           tr('Fields'))
    header = m.Heading(tr('Input dataset fields'), **SUBSECTION_STYLE)
    message.add(header)
    _create_fields_section(message, tr('Exposure fields'),
                           definitions.exposure_fields)
    _create_fields_section(message, tr('Hazard fields'),
                           definitions.hazard_fields)
    _create_fields_section(message, tr('Aggregation fields'),
                           definitions.aggregation_fields)
    header = m.Heading(tr('Output dataset fields'), **SUBSECTION_STYLE)
    message.add(header)
    _create_fields_section(message, tr('Impact fields'),
                           definitions.impact_fields)
    _create_fields_section(message, tr('Aggregate hazard fields'),
                           definitions.aggregate_hazard_fields)
    _create_fields_section(message, tr('Aggregation summary fields'),
                           definitions.aggregation_summary_fields)
    _create_fields_section(message, tr('Exposure summary table fields'),
                           definitions.exposure_summary_table_fields)
    _create_fields_section(message, tr('Analysis fields'),
                           definitions.analysis_fields)

    ##
    #  Geometries
    ##

    _create_section_header(message, table_of_contents, 'geometries',
                           tr('Layer Geometry Types'))
    header = m.Heading(tr('Vector'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(
        definition_to_message(definitions.layer_geometry_point,
                              BLUE_CHAPTER_STYLE))
    message.add(
        definition_to_message(definitions.layer_geometry_line,
                              BLUE_CHAPTER_STYLE))
    message.add(
        definition_to_message(definitions.layer_geometry_polygon,
                              BLUE_CHAPTER_STYLE))
    header = m.Heading(tr('Raster'), **SUBSECTION_STYLE)
    message.add(header)
    message.add(
        definition_to_message(definitions.layer_geometry_raster,
                              BLUE_CHAPTER_STYLE))

    ##
    #  Layer Modes
    ##

    _create_section_header(message, table_of_contents, 'layer-modes',
                           tr('Layer Modes'))
    message.add(definition_to_message(definitions.layer_mode,
                                      SUBSECTION_STYLE))

    ##
    #  Layer Purposes
    ##

    _create_section_header(message, table_of_contents, 'layer-purposes',
                           tr('Layer Purposes'))
    message.add(
        definition_to_message(definitions.layer_purpose_hazard,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_exposure,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_aggregation,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_exposure_summary,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(
            definitions.layer_purpose_aggregate_hazard_impacted,
            SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_aggregation_summary,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_exposure_summary_table,
                              SUBSECTION_STYLE))
    message.add(
        definition_to_message(definitions.layer_purpose_profiling,
                              SUBSECTION_STYLE))

    ##
    # All units
    ##

    _create_section_header(message, table_of_contents, 'all-units',
                           tr('All Units'))
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name')), header_flag=True)
    row.add(m.Cell(tr('Plural')), header_flag=True)
    row.add(m.Cell(tr('Abbreviation')), header_flag=True)
    row.add(m.Cell(tr('Details')), header_flag=True)
    table.add(row)
    for unit in definitions.units_all:
        row = m.Row()
        row.add(m.Cell(unit['name']))
        row.add(m.Cell(unit['plural_name']))
        row.add(m.Cell(unit['abbreviation']))
        row.add(m.Cell(unit['description']))
        table.add(row)
    message.add(table)

    ##
    #  Post processors
    ##

    _create_section_header(message, table_of_contents, 'post-processors',
                           tr('Post Processors'))
    header = m.Heading(tr('Post Processor Input Types'), **SUBSECTION_STYLE)
    message.add(header)
    table = _create_post_processor_subtable(
        definitions.post_processor_input_types)
    message.add(table)

    header = m.Heading(tr('Post Processor Input Values'), **SUBSECTION_STYLE)
    message.add(header)
    table = _create_post_processor_subtable(
        definitions.post_processor_input_values)
    message.add(table)

    header = m.Heading(tr('Post Processor Process Types'), **SUBSECTION_STYLE)
    message.add(header)
    table = _create_post_processor_subtable(
        definitions.post_processor_process_types)
    message.add(table)

    header = m.Heading(tr('Post Processors'), **SUBSECTION_STYLE)
    message.add(header)
    post_processors = definitions.post_processors
    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Name')), header_flag=True)
    row.add(m.Cell(tr('Input Fields')), header_flag=True)
    row.add(m.Cell(tr('Output Fields')), header_flag=True)
    table.add(row)
    for post_processor in post_processors:
        row = m.Row()
        row.add(m.Cell(post_processor['name']))
        # Input fields
        bullets = m.BulletedList()
        for key, value in post_processor['input'].iteritems():
            bullets.add(key)
        row.add(m.Cell(bullets))
        # Output fields
        bullets = m.BulletedList()
        for key, value in post_processor['output'].iteritems():
            name = value['value']['name']
            formula_type = value['type']['key']
            if formula_type == 'formula':
                formula = value['formula']
            else:
                # We use python introspection because the processor
                # uses a python function for calculations
                formula = value['function'].__name__
                formula += ' ('
                formula += value['function'].__doc__
                formula += ')'
            bullets.add('%s  %s. : %s' % (name, formula_type, formula))

        row.add(m.Cell(bullets))
        table.add(row)
        # Add the descriptions
        row = m.Row()
        row.add(m.Cell(''))
        row.add(m.Cell(post_processor['description'], span=2))
        table.add(row)
    message.add(table)

    # Finally we add the table of contents at the top
    full_message = m.Message()
    # Contents is not a link so reset style
    style = SECTION_STYLE
    style['element_id'] = ''
    header = m.Heading(tr('Contents'), **style)
    full_message.add(header)
    full_message.add(table_of_contents)
    full_message.add(message)
    return full_message
Exemple #13
0
def definition_to_message(definition, heading_style=None):
    """Helper function to render a definition to a message.

    :param definition: A definition dictionary (see definitions package).
    :type definition: dict

    :param heading_style: Optional style to apply to the definition
        heading. See safe.messaging.styles
    :type heading_style: dict


    :returns: Message
    :rtype: str
    """

    if heading_style:
        header = m.Heading(definition['name'], **heading_style)
    else:
        header = m.Paragraph(m.ImportantText(definition['name']))
    message = m.Message()
    message.add(header)
    # If the definition has an icon, we put the icon and description side by
    # side in a table otherwise just show the description as a paragraph
    url = _definition_icon_url(definition)
    if url is None:
        LOGGER.info('No URL for definition icon')
        message.add(m.Paragraph(definition['description']))
        for citation in definition['citations']:
            if citation['text'] in [None, '']:
                continue
            if citation['link'] in [None, '']:
                message.add(m.Paragraph(citation['text']))
            else:
                message.add(
                    m.Paragraph(m.Link(citation['link'], citation['text'])))
    else:
        LOGGER.info('Creating mini table for definition description: ' + url)
        table = m.Table(style_class='table table-condensed')
        row = m.Row()
        row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE)))
        row.add(m.Cell(definition['description']))
        table.add(row)
        for citation in definition['citations']:
            if citation['text'] in [None, '']:
                continue
            row = m.Row()
            row.add(m.Cell(''))
            if citation['link'] in [None, '']:
                row.add(m.Cell(citation['text']))
            else:
                row.add(m.Cell(m.Link(citation['link'], citation['text'])))
            table.add(row)
        message.add(table)

    url = _definition_screenshot_url(definition)
    if url:
        message.add(m.Image(url))

    # types contains e.g. hazard_all
    if 'types' in definition:
        for sub_definition in definition['types']:
            message.add(
                definition_to_message(sub_definition, RED_CHAPTER_STYLE))

    #
    # Notes section if available
    #

    if 'notes' in definition:
        # Start a notes details group too since we have an exposure
        message.add(m.Heading(tr('Notes:'), **DETAILS_STYLE))
        message.add(m.Heading(tr('General notes:'), **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['notes']:
            bullets.add(m.Text(note))
        message.add(bullets)

    # This only for EQ
    if 'earthquake_fatality_models' in definition:
        for model in definition['earthquake_fatality_models']:
            message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE))
            bullets = m.BulletedList()
            for note in model['notes']:
                bullets.add(m.Text(note))
                message.add(bullets)

    for exposure in exposure_all:
        extra_exposure_notes = specific_notes(definition, exposure)
        if extra_exposure_notes:
            title = tr(u'Notes for exposure : {exposure_name}').format(
                exposure_name=exposure['name'])
            message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE))
            bullets = m.BulletedList()
            for note in extra_exposure_notes:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'continuous_notes' in definition:
        message.add(
            m.Heading(tr('Notes for continuous datasets:'),
                      **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['continuous_notes']:
            bullets.add(m.Text(note))
        message.add(bullets)

    if 'classified_notes' in definition:
        message.add(
            m.Heading(tr('Notes for classified datasets:'),
                      **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['classified_notes']:
            bullets.add(m.Text(note))
        message.add(bullets)

    if 'single_event_notes' in definition:
        message.add(m.Heading(tr('Notes for single events'), **DETAILS_STYLE))
        if len(definition['single_event_notes']) < 1:
            message.add(m.Paragraph(tr('No single event notes defined.')))
        else:
            bullets = m.BulletedList()
            for note in definition['single_event_notes']:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'multi_event_notes' in definition:
        message.add(
            m.Heading(tr('Notes for multi events / scenarios:'),
                      **DETAILS_STYLE))
        if len(definition['multi_event_notes']) < 1:
            message.add(m.Paragraph(tr('No multi-event notes defined.')))
        else:
            bullets = m.BulletedList()
            for note in definition['multi_event_notes']:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'actions' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Actions:'))))
        bullets = m.BulletedList()
        for note in definition['actions']:
            bullets.add(m.Text(note))
        message.add(bullets)

    for exposure in exposure_all:
        extra_exposure_actions = specific_actions(definition, exposure)
        if extra_exposure_actions:
            title = tr(u'Actions for exposure : {exposure_name}').format(
                exposure_name=exposure['name'])
            message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE))
            bullets = m.BulletedList()
            for note in extra_exposure_actions:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'continuous_hazard_units' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Units:'))))
        table = m.Table(style_class='table table-condensed table-striped')
        row = m.Row()
        row.add(m.Cell(tr('Name')), header_flag=True)
        row.add(m.Cell(tr('Plural')), header_flag=True)
        row.add(m.Cell(tr('Abbreviation')), header_flag=True)
        row.add(m.Cell(tr('Details')), header_flag=True)
        table.add(row)
        for unit in definition['continuous_hazard_units']:
            row = m.Row()
            row.add(m.Cell(unit['name']))
            row.add(m.Cell(unit['plural_name']))
            row.add(m.Cell(unit['abbreviation']))
            row.add(m.Cell(unit['description']))
            table.add(row)
        message.add(table)

    if 'extra_fields' and 'fields' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Fields:'))))
        table = _create_fields_table()
        all_fields = definition['fields'] + definition['extra_fields']
        for field in all_fields:
            _add_field_to_table(field, table)
        message.add(table)

    if 'classifications' in definition:
        message.add(m.Heading(tr('Hazard classifications'), **DETAILS_STYLE))
        message.add(
            m.Paragraph(definitions.hazard_classification['description']))
        for inasafe_class in definition['classifications']:
            message.add(
                definition_to_message(inasafe_class, DETAILS_SUBGROUP_STYLE))

    if 'classes' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Classes:'))))
        table = _make_defaults_table()
        for inasafe_class in definition['classes']:
            row = m.Row()
            # name() on QColor returns its hex code
            if 'color' in inasafe_class:
                colour = inasafe_class['color'].name()
                row.add(
                    m.Cell(u'', attributes='style="background: %s;"' % colour))
            else:
                row.add(m.Cell(u' '))

            row.add(m.Cell(inasafe_class['name']))
            if 'affected' in inasafe_class:
                row.add(m.Cell(tr(inasafe_class['affected'])))
            else:
                row.add(m.Cell(tr('unspecified')))

            if 'displacement_rate' in inasafe_class:
                rate = inasafe_class['displacement_rate'] * 100
                rate = u'%s%%' % rate
                row.add(m.Cell(rate))
            else:
                row.add(m.Cell(tr('unspecified')))

            if 'string_defaults' in inasafe_class:
                defaults = None
                for default in inasafe_class['string_defaults']:
                    if defaults:
                        defaults += ',%s' % default
                    else:
                        defaults = default
                row.add(m.Cell(defaults))
            else:
                row.add(m.Cell(tr('unspecified')))
            # Min may be a single value or a dict of values so we need
            # to check type and deal with it accordingly
            if 'numeric_default_min' in inasafe_class:
                if isinstance(inasafe_class['numeric_default_min'], dict):
                    bullets = m.BulletedList()
                    minima = inasafe_class['numeric_default_min']
                    for key, value in minima.iteritems():
                        bullets.add(u'%s : %s' % (key, value))
                    row.add(m.Cell(bullets))
                else:
                    row.add(m.Cell(inasafe_class['numeric_default_min']))
            else:
                row.add(m.Cell(tr('unspecified')))

            # Max may be a single value or a dict of values so we need
            # to check type and deal with it accordingly
            if 'numeric_default_max' in inasafe_class:
                if isinstance(inasafe_class['numeric_default_max'], dict):
                    bullets = m.BulletedList()
                    maxima = inasafe_class['numeric_default_max']
                    for key, value in maxima.iteritems():
                        bullets.add(u'%s : %s' % (key, value))
                    row.add(m.Cell(bullets))
                else:
                    row.add(m.Cell(inasafe_class['numeric_default_max']))
            else:
                row.add(m.Cell(tr('unspecified')))

            table.add(row)
            # Description goes in its own row with spanning
            row = m.Row()
            row.add(m.Cell(''))
            row.add(m.Cell(inasafe_class['description'], span=6))
            table.add(row)
        # For hazard classes we also add the 'not affected' class manually:
        if definition['type'] == definitions.hazard_classification_type:
            row = m.Row()
            colour = definitions.not_exposed_class['color'].name()
            row.add(m.Cell(u'', attributes='style="background: %s;"' % colour))
            description = definitions.not_exposed_class['description']
            row.add(m.Cell(description, span=5))
            table.add(row)

        message.add(table)

    if 'affected' in definition:
        if definition['affected']:
            message.add(
                m.Paragraph(
                    tr('Exposure entities in this class ARE considered affected'
                       )))
        else:
            message.add(
                m.Paragraph(
                    tr('Exposure entities in this class are NOT considered '
                       'affected')))

    if 'optional' in definition:
        if definition['optional']:
            message.add(
                m.Paragraph(
                    tr('This class is NOT required in the hazard keywords.')))
        else:
            message.add(
                m.Paragraph(
                    tr('This class IS required in the hazard keywords.')))

    return message
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    body = tr('This tool will fetch building (\'structure\') or road ('
              '\'highway\') data from the OpenStreetMap project for you. '
              'The downloaded data will have InaSAFE keywords defined and a '
              'default QGIS style applied. To use this tool effectively:')
    tips = m.BulletedList()
    tips.add(
        tr('Your current extent, when opening this window, will be used to '
           'determine the area for which you want data to be retrieved. '
           'You can interactively select the area by using the '
           '\'select on map\' button - which will temporarily hide this '
           'window and allow you to drag a rectangle on the map. After you '
           'have finished dragging the rectangle, this window will '
           'reappear.'))
    tips.add(
        tr('Check the output directory is correct. Note that the saved '
           'dataset will be named after the type of data being downloaded '
           'e.g. roads.shp or buildings.shp (and associated files).'))
    tips.add(
        tr('By default simple file names will be used (e.g. roads.shp, '
           'buildings.shp). If you wish you can specify a prefix to '
           'add in front of this default name. For example using a prefix '
           'of \'padang-\' will cause the downloaded files to be saved as '
           '\'padang-roads.shp\' and \'padang-buildings.shp\'. Note that '
           'the only allowed prefix characters are A-Z, a-z, 0-9 and the '
           'characters \'-\' and \'_\'. You can leave this blank if you '
           'prefer.'))
    tips.add(
        tr('If a dataset already exists in the output directory it will be '
           'overwritten.'))
    tips.add(
        tr('This tool requires a working internet connection and fetching '
           'buildings or roads will consume your bandwidth.'))
    tips.add(
        m.Link('http://www.openstreetmap.org/copyright',
               text=tr(
                   'Downloaded data is copyright OpenStreetMap contributors '
                   '(click for more info).')))
    message.add(m.Paragraph(body))
    message.add(tips)

    message.add(
        m.Paragraph(
            # format 'When the __Political boundaries__' for proper i18n
            tr('When the %s '
               'box in the Feature types menu is ticked, the Political boundary '
               'options panel will be enabled. The panel lets you select which '
               'admin level you wish to download. The admin levels are country '
               'specific. When you select an admin level, the local name for '
               'that admin level will be shown. You can change which country '
               'is used for the admin level description using the country drop '
               'down menu. The country will be automatically set to coincide '
               'with the view extent if a matching country can be found.') %
            (m.ImportantText(tr('Political boundaries')).to_html(), )))
    message.add(
        m.Paragraph(
            m.ImportantText(tr('Note: ')),
            tr('We have only provide presets for a subset of the available '
               'countries. If you want to know what the levels are for your '
               'country, please check on the following web page: '),
            m.Link(
                'http://wiki.openstreetmap.org/wiki/Tag:boundary%3Dadministrative',
                text=tr('List of OSM Admin Boundary definitions '))))

    return message
Exemple #15
0
    def print_map_to_pdf(self, impact_report):
        """Print map to PDF given MapReport instance.

        :param impact_report: Impact Report instance that is ready to print
        :type impact_report: ImpactReport
        """
        impact_report.setup_composition()

        # Get Filename
        map_title = impact_report.map_title
        if map_title is not None:
            default_file_name = map_title + '.pdf'
            default_file_name = default_file_name.replace(' ', '_')
        else:
            self.show_error_message(self.tr('Keyword "map_title" not found.'))
            return

        # Get output path
        # noinspection PyCallByClass,PyTypeChecker
        output_path = QtGui.QFileDialog.getSaveFileName(
            self.parent, self.tr('Write to PDF'),
            os.path.join(temp_dir(), default_file_name),
            self.tr('Pdf File (*.pdf)'))
        output_path = str(output_path)

        if output_path is None or output_path == '':
            # noinspection PyTypeChecker
            self.show_dynamic_message(
                self,
                m.Message(m.Heading(self.tr('Map Creator'), **WARNING_STYLE),
                          m.Text(self.tr('Printing cancelled!'))))
            return

        try:
            map_pdf_path, table_pdf_path = impact_report.print_to_pdf(
                output_path)

            # Make sure the file paths can wrap nicely:
            wrapped_map_path = map_pdf_path.replace(os.sep, '<wbr>' + os.sep)
            wrapped_table_path = table_pdf_path.replace(
                os.sep, '<wbr>' + os.sep)
            status = m.Message(
                m.Heading(self.tr('Map Creator'), **INFO_STYLE),
                m.Paragraph(self.tr('Your PDF was created....')),
                m.Paragraph(
                    self.tr(
                        'Opening using the default PDF viewer on your system. '
                        'The generated pdfs were saved as:')),
                m.Paragraph(wrapped_map_path), m.Paragraph(self.tr('and')),
                m.Paragraph(wrapped_table_path))

            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QtGui.QDesktopServices.openUrl(
                QtCore.QUrl.fromLocalFile(table_pdf_path))
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QtGui.QDesktopServices.openUrl(
                QtCore.QUrl.fromLocalFile(map_pdf_path))

            # noinspection PyTypeChecker
            self.show_dynamic_message(self, status)
        except TemplateLoadingError, e:
            self.show_error_message(get_error_message(e))
    def buildings_breakdown(self):
        """Breakdown by building type.

        :returns: The buildings breakdown report.
        :rtype: safe.messaging.Message
        """
        message = m.Message(style_class='container')
        table = m.Table(style_class='table table-condensed table-striped')
        table.caption = None
        impact_names = self.affected_buildings.keys()  # e.g. flooded, wet, dry

        row = m.Row()
        row.add(m.Cell('Building type', header=True))
        for name in impact_names:
            row.add(m.Cell(tr(name), header=True, align='right'))
        row.add(m.Cell(tr('Total'), header=True, align='right'))
        table.add(row)

        # Let's sort alphabetically first
        building_types = [building_type for building_type in self.buildings]
        building_types.sort()
        impact_totals = []  # Used to store the total for each impact name
        # Initialise totals with zeros
        for _ in impact_names:
            impact_totals.append(0)
        # And one extra total for the cumuluative total column
        impact_totals.append(0)
        # Now build the main table
        for building_type in building_types:
            row = m.Row()
            building_type_name = building_type.replace('_', ' ')
            impact_subtotals = []
            for name in impact_names:
                if building_type in self.affected_buildings[name]:
                    impact_subtotals.append(self.affected_buildings[name]
                                            [building_type].values()[0])
                else:
                    impact_subtotals.append(0)
            row.add(m.Cell(building_type_name.capitalize(), header=True))
            # list out the subtotals for this category per impact type
            for value in impact_subtotals:
                row.add(m.Cell(format_int(value), align='right'))
            # totals column
            line_total = format_int(self.buildings[building_type])
            impact_subtotals.append(self.buildings[building_type])
            row.add(m.Cell(line_total, header=True, align='right'))
            table.add(row)
            # add the subtotal to the cumulative total
            # see http://stackoverflow.com/questions/18713321/element
            #     -wise-addition-of-2-lists-in-python
            # pylint: disable=bad-builtin
            impact_totals = map(add, impact_totals, impact_subtotals)

        # list out the TOTALS for this category per impact type
        row = m.Row()
        row.add(m.Cell(tr('Total'), header=True))
        for value in impact_totals:
            row.add(m.Cell(format_int(value), align='right', header=True))
        table.add(row)

        message.add(table)

        return message
def field_mapping_help_content():
    """Helper method that returns just the content in extent mode.

    This method was added so that the text could be reused in the
    wizard.

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    paragraph = m.Paragraph(
        tr('Field mapping describes the process of matching one or more fields '
           'in an attribute table to a concept in InaSAFE. The field mappings '
           'tool InaSAFE allows you to match concepts such as "elderly", '
           '"disabled people", "pregnant" and so on to their counterpart fields '
           'in either an aggregation layer or an exposure population vector '
           'layer.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(
            'Note: It is not possible to use this tool with raster population '
            'exposure data, but ratios defined in aggregation layers will be '
            'used when raster exposure population data is used.'))
    message.add(paragraph)

    paragraph = m.Paragraph(m.Image('file:///%s/img/screenshots/'
                                    'demographic-concepts-screenshot.png' %
                                    resources_path()),
                            style_class='text-center')
    message.add(paragraph)

    paragraph = m.Paragraph(
        tr('The illustration above shows the principle behind InaSAFE\'s '
           'demographic breakdown reporting system. The idea here is to support '
           'the production of a detailed demographic breakdown when carrying out '
           'an analysis with a population exposure vector dataset. So for '
           'example instead of simply reporting on the total number of people '
           'exposed to a hazard, we want to break down the affected population '
           'into distinct demographic groups. In InaSAFE by default we consider '
           'three groups:'))
    message.add(paragraph)

    bullets = m.BulletedList()
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Gender: ')),
            tr('The gender group reports on gender specific demographics '
               'including things like the number of women of child bearing age, '
               'number of pregnant women, number of lactating women and so on.'
               )))
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Age: ')),
            tr('The age group reports on age specific demographics including '
               'things like the number of infants, children, young adults, '
               'adults elderly people and so on.')))
    bullets.add(
        m.Paragraph(
            m.ImportantText(tr('Vulnerable people: ')),
            tr('The vulnerable people group reports on specific demographics '
               'relating to vulnerability including things like the number of '
               'infants, elderly people, disabled people and so on.')))
    message.add(bullets)
    paragraph = m.Paragraph(
        tr('In the diagram above, you can see that we have an "age" group '
           '(column on the right) which, for purposes of illustration, has two '
           'age classes: "infant" and "child" (center column). These age classes '
           'are defined in InaSAFE metadata and there are actually five classes '
           'in a default installation. In the left hand column you can see a '
           'number of columns listed from the attribute table. In this example '
           'our population data contains columns for different age ranges ('
           '0-1, 1-2, 2-4, 4-6). The field mapping tool can be used in order '
           'to combine the data in the "0 - 1" and "1 - 2" columns into a '
           'new column called "infant". In the next section of this document we '
           'enumerate the different groups and concepts that InaSAFE supports '
           'when generating demographic breakdowns.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('When the tool is used, it will write additional data to the '
           'exposure or aggregation layer keywords so that your preferred '
           'concept mappings will be used when reports are generated after the '
           'analysis is carried out. You should note the following special '
           'characteristics of the field mapping tool when used for aggregation '
           'datasets versus when used for vector population exposure datasets:'
           ))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(tr('Aggregation datasets: ')),
        tr('For aggregation datasets, the field mapping tool uses global '
           'defaults (see the InaSAFE Options Dialog documentation for more '
           'details) or dataset level defaults to determine which ratios '
           'should be used to calculate concept values. For example, in the '
           'age group the aggregation dataset may specify that infants '
           'should by calculated as a ratio of 0.1% of the total population. '
           'Note that for aggregation datasets you can only use ratios, '
           'not counts.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        m.ImportantText(tr('Vector population exposure datasets: ')),
        tr('For exposure datasets, ratios are not supported, only counts. '
           'The field mappings carried out here will be used to generate '
           'new columns during a pre-processing step before the actual '
           'analysis is carried out.'))
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('The interplay between default ratios, aggregation layer '
           'provided ratios and population exposure layers is illustrated '
           'in the table below.'))
    message.add(paragraph)

    table = m.Table(style_class='table table-condensed table-striped')
    row = m.Row()
    row.add(m.Cell(tr('Aggregation'), header=True))
    row.add(m.Cell(tr('Raster'), header=True))
    row.add(m.Cell(tr('Vector, no counts'), header=True))
    row.add(m.Cell(tr('Vector with counts'), header=True))
    row.add(m.Cell(tr('Notes'), header=True))
    table.add(row)
    row = m.Row([
        tr('No aggregation'),
        tr('Use global default ratio'),
        tr('Use global default ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio not set'),
        tr('Use global default ratio'),
        tr('Do nothing'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio value set'),
        tr('Use aggregation layer ratio'),
        tr('Use aggregation layer ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    row = m.Row([
        tr('Aggregation, ratio field mapping set'),
        tr('Use aggregation layer ratio'),
        tr('Use aggregation layer ratio'),
        tr('Use count to determine ratio'),
        tr(''),
    ])
    table.add(row)
    message.add(table)

    return message
Exemple #18
0
def impact_attribution(keywords, inasafe_flag=False):
    """Make a little table for attribution of data sources used in impact.

    :param keywords: A keywords dict for an impact layer.
    :type keywords: dict

    :param inasafe_flag: bool - whether to show a little InaSAFE promotional
        text in the attribution output. Defaults to False.

    :returns: An html snippet containing attribution information for the impact
        layer. If no keywords are present or no appropriate keywords are
        present, None is returned.
    :rtype: safe.messaging.Message
    """
    if keywords is None:
        return None

    join_words = ' - %s ' % tr('sourced from')
    analysis_details = tr('Analysis details')
    hazard_details = tr('Hazard details')
    hazard_title_keywords = 'hazard_title'
    hazard_source_keywords = 'hazard_source'
    exposure_details = tr('Exposure details')
    exposure_title_keywords = 'exposure_title'
    exposure_source_keyword = 'exposure_source'

    if hazard_title_keywords in keywords:
        hazard_title = tr(keywords[hazard_title_keywords])
    else:
        hazard_title = tr('Hazard layer')

    if hazard_source_keywords in keywords:
        hazard_source = tr(keywords[hazard_source_keywords])
    else:
        hazard_source = tr('an unknown source')

    if exposure_title_keywords in keywords:
        exposure_title = keywords[exposure_title_keywords]
    else:
        exposure_title = tr('Exposure layer')

    if exposure_source_keyword in keywords:
        exposure_source = keywords[exposure_source_keyword]
    else:
        exposure_source = tr('an unknown source')

    report = m.Message()
    report.add(m.Heading(analysis_details, **INFO_STYLE))
    report.add(hazard_details)
    report.add(m.Paragraph(hazard_title, join_words, hazard_source))

    report.add(exposure_details)
    report.add(m.Paragraph(exposure_title, join_words, exposure_source))

    if inasafe_flag:
        report.add(m.Heading(tr('Software notes'), **INFO_STYLE))
        # noinspection PyUnresolvedReferences
        inasafe_phrase = tr(
            'This report was created using InaSAFE version %s. Visit '
            'http://inasafe.org to get your free copy of this software! %s'
        ) % (get_version(), disclaimer())

        report.add(m.Paragraph(m.Text(inasafe_phrase)))
    return report
Exemple #19
0
    def run(self):
        """Run any post processors requested by the impact function.
        """
        try:
            requested_postprocessors = self.function_parameters[
                'postprocessors']
            postprocessors = get_postprocessors(requested_postprocessors)
        except (TypeError, KeyError):
            # TypeError is for when function_parameters is none
            # KeyError is for when ['postprocessors'] is unavailable
            postprocessors = {}

        feature_names_attribute = self.aggregator.attributes[
            self.aggregator.get_default_keyword('AGGR_ATTR_KEY')]
        if feature_names_attribute is None:
            self.attribute_title = self.tr('Aggregation unit')
        else:
            self.attribute_title = feature_names_attribute

        name_filed_index = self.aggregator.layer.fieldNameIndex(
            self.attribute_title)
        sum_field_index = self.aggregator.layer.fieldNameIndex(
            self._sum_field_name())

        user_defined_female_ratio = False
        female_ratio_field_index = None
        female_ratio = None
        user_defined_age_ratios = False
        youth_ratio_field_index = None
        youth_ratio = None
        adult_ratio_field_index = None
        adult_ratio = None
        elderly_ratio_field_index = None
        elderly_ratio = None

        if 'Gender' in postprocessors:
            # look if we need to look for a variable female ratio in a layer
            try:
                female_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'FEMALE_RATIO_ATTR_KEY')]
                female_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(female_ratio_field)

                # something went wrong finding the female ratio field,
                # use defaults from below except block
                if female_ratio_field_index == -1:
                    raise KeyError

                user_defined_female_ratio = True

            except KeyError:
                try:
                    female_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'FEMALE_RATIO_KEY'))
                except KeywordNotFoundError:
                    female_ratio = \
                        self.aggregator.get_default_keyword('FEMALE_RATIO')

        if 'Age' in postprocessors:
            # look if we need to look for a variable age ratio in a layer
            try:
                youth_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'YOUTH_RATIO_ATTR_KEY')]
                youth_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(youth_ratio_field)
                adult_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ADULT_RATIO_ATTR_KEY')]
                adult_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(adult_ratio_field)
                elderly_ratio_field = self.aggregator.attributes[
                    self.aggregator.get_default_keyword(
                        'ELDERLY_RATIO_ATTR_KEY')]
                elderly_ratio_field_index = \
                    self.aggregator.layer.fieldNameIndex(elderly_ratio_field)
                # something went wrong finding the youth ratio field,
                # use defaults from below except block
                if (youth_ratio_field_index == -1
                        or adult_ratio_field_index == -1
                        or elderly_ratio_field_index == -1):
                    raise KeyError

                user_defined_age_ratios = True

            except KeyError:
                try:
                    youth_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword('YOUTH_RATIO_KEY'))
                    adult_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword('ADULT_RATIO_KEY'))
                    elderly_ratio = self.keyword_io.read_keywords(
                        self.aggregator.layer,
                        self.aggregator.get_default_keyword(
                            'ELDERLY_RATIO_KEY'))

                except KeywordNotFoundError:
                    youth_ratio = \
                        self.aggregator.get_default_keyword('YOUTH_RATIO')
                    adult_ratio = \
                        self.aggregator.get_default_keyword('ADULT_RATIO')
                    elderly_ratio = \
                        self.aggregator.get_default_keyword('ELDERLY_RATIO')

        if 'BuildingType' or 'RoadType' in postprocessors:
            try:
                key_attribute = self.keyword_io.read_keywords(
                    self.aggregator.exposure_layer, 'key_attribute')
            except KeywordNotFoundError:
                # use 'type' as default
                key_attribute = 'type'

        # iterate zone features
        request = QgsFeatureRequest()
        request.setFlags(QgsFeatureRequest.NoGeometry)
        provider = self.aggregator.layer.dataProvider()
        # start data retrieval: fetch no geometry and all attributes for each
        # feature
        polygon_index = 0
        for feature in provider.getFeatures(request):
            # if a feature has no field called
            if name_filed_index == -1:
                zone_name = str(feature.id())
            else:
                zone_name = feature[name_filed_index]
            if isinstance(zone_name, QPyNullVariant):
                zone_name = 'Unnamed Area %s' % str(feature.id())

            # create dictionary of attributes to pass to postprocessor
            general_params = {
                'target_field': self.aggregator.target_field,
                'function_params': self.function_parameters
            }

            if self.aggregator.statistics_type == 'class_count':
                general_params['impact_classes'] = (
                    self.aggregator.statistics_classes)
            elif self.aggregator.statistics_type == 'sum':
                impact_total = feature[sum_field_index]
                general_params['impact_total'] = impact_total

            try:
                general_params['impact_attrs'] = (
                    self.aggregator.impact_layer_attributes[polygon_index])
            except IndexError:
                # rasters and attributeless vectors have no attributes
                general_params['impact_attrs'] = None
            for key, value in postprocessors.iteritems():
                parameters = general_params
                user_parameters = self.function_parameters['postprocessors'][
                    key]
                user_parameters = dict([(user_parameter.name,
                                         user_parameter.value)
                                        for user_parameter in user_parameters])
                try:
                    # user parameters override default parameters
                    parameters.update(user_parameters)
                except KeyError:
                    pass

                if key == 'Gender':
                    if user_defined_female_ratio:
                        female_ratio = feature[female_ratio_field_index]
                        if female_ratio is None:
                            female_ratio = self.aggregator.defaults[
                                'FEMALE_RATIO']
                            LOGGER.warning('Data Driven Female ratio '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['female_ratio'] = female_ratio

                if key == 'Age':
                    if user_defined_age_ratios:
                        youth_ratio = feature[youth_ratio_field_index]
                        adult_ratio = feature[adult_ratio_field_index]
                        elderly_ratio = feature[elderly_ratio_field_index]
                        if (youth_ratio is None or adult_ratio is None
                                or elderly_ratio is None):
                            LOGGER.debug(
                                '--- only default age ratios used ---')
                            youth_ratio = self.aggregator.defaults[
                                'YOUTH_RATIO']
                            adult_ratio = self.aggregator.defaults[
                                'ADULT_RATIO']
                            elderly_ratio = self.aggregator.defaults[
                                'ELDERLY_RATIO']
                            LOGGER.warning('Data Driven Age ratios '
                                           'incomplete, using defaults for'
                                           ' aggregation unit'
                                           ' %s' % feature.id)

                    parameters['youth_ratio'] = youth_ratio
                    parameters['adult_ratio'] = adult_ratio
                    parameters['elderly_ratio'] = elderly_ratio

                if key == 'BuildingType' or key == 'RoadType':
                    # TODO: Fix this might be referenced before assignment
                    parameters['key_attribute'] = key_attribute
                try:
                    value.setup(parameters)
                    value.process()
                    results = value.results()
                    value.clear()
                    if key not in self.output:
                        self.output[key] = []
                    self.output[key].append((zone_name, results))

                except PostProcessorError as e:
                    message = m.Message(
                        m.Heading(self.tr('%s postprocessor problem' % key),
                                  **styles.DETAILS_STYLE),
                        m.Paragraph(self.tr(str(e))))
                    self.error_message = message
            # increment the index
            polygon_index += 1
        self.remove_empty_columns()
Exemple #20
0
    def run(self):
        """Risk plugin for tsunami population evacuation.

        Counts number of people exposed to tsunami levels exceeding
        specified threshold.

        :returns: Map of population exposed to tsunami levels exceeding the
            threshold. Table with number of people evacuated and supplies
            required.
        :rtype: tuple
        """

        # Determine depths above which people are regarded affected [m]
        # Use thresholds from inundation layer if specified
        thresholds = self.parameters['thresholds'].value

        verify(isinstance(thresholds, list),
               'Expected thresholds to be a list. Got %s' % str(thresholds))

        # Extract data as numeric arrays
        data = self.hazard.layer.get_data(nan=True)  # Depth
        if has_no_data(data):
            self.no_data_warning = True

        # Calculate impact as population exposed to depths > max threshold
        population = self.exposure.layer.get_data(nan=True, scaling=True)
        if has_no_data(population):
            self.no_data_warning = True

        # merely initialize
        impact = None
        for i, lo in enumerate(thresholds):
            if i == len(thresholds) - 1:
                # The last threshold
                thresholds_name = tr('People in >= %.1f m of water') % lo
                impact = medium = numpy.where(data >= lo, population, 0)
                self.impact_category_ordering.append(thresholds_name)
                self._evacuation_category = thresholds_name
            else:
                # Intermediate thresholds
                hi = thresholds[i + 1]
                thresholds_name = tr('People in %.1f m to %.1f m of water' %
                                     (lo, hi))
                medium = numpy.where((data >= lo) * (data < hi), population, 0)

            # Count
            val = int(numpy.nansum(medium))
            self.affected_population[thresholds_name] = val

        # Put the deepest area in top #2385
        self.impact_category_ordering.reverse()

        # Carry the no data values forward to the impact layer.
        impact = numpy.where(numpy.isnan(population), numpy.nan, impact)
        impact = numpy.where(numpy.isnan(data), numpy.nan, impact)

        # Count totals
        self.total_population = int(numpy.nansum(population))
        self.unaffected_population = (self.total_population -
                                      self.total_affected_population)

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

        # check for zero impact
        if numpy.nanmax(impact) == 0 == numpy.nanmin(impact):
            message = m.Message()
            message.add(self.question)
            message.add(tr('No people in %.1f m of water') % thresholds[-1])
            message = message.to_html(suppress_newlines=True)
            raise ZeroImpactException(message)

        # Create style
        colours = [
            '#FFFFFF', '#38A800', '#79C900', '#CEED00', '#FFCC00', '#FF6600',
            '#FF0000', '#7A0000'
        ]
        classes = create_classes(impact.flat[:], len(colours))
        interval_classes = humanize_class(classes)
        style_classes = []

        for i in xrange(len(colours)):
            style_class = dict()
            if i == 1:
                label = create_label(interval_classes[i], 'Low')
            elif i == 4:
                label = create_label(interval_classes[i], 'Medium')
            elif i == 7:
                label = create_label(interval_classes[i], 'High')
            else:
                label = create_label(interval_classes[i])
            style_class['label'] = label
            style_class['quantity'] = classes[i]
            style_class['transparency'] = 0
            style_class['colour'] = colours[i]
            style_classes.append(style_class)

        style_info = dict(target_field=None,
                          style_classes=style_classes,
                          style_type='rasterStyle')

        impact_data = self.generate_data()

        extra_keywords = {
            'map_title': self.map_title(),
            'legend_notes': self.metadata().key('legend_notes'),
            'legend_units': self.metadata().key('legend_units'),
            'legend_title': self.metadata().key('legend_title'),
            'evacuated': self.total_evacuated,
            'total_needs': self.total_needs
        }

        impact_layer_keywords = self.generate_impact_keywords(extra_keywords)

        # Create raster object and return
        impact_layer = Raster(
            impact,
            projection=self.hazard.layer.get_projection(),
            geotransform=self.hazard.layer.get_geotransform(),
            name=self.map_title(),
            keywords=impact_layer_keywords,
            style_info=style_info)

        impact_layer.impact_data = impact_data
        self._impact = impact_layer
        return impact_layer
Exemple #21
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 4.1.0

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    message.add(m.Paragraph(tr(
        'This section of the help documentation is intended for advanced '
        'users who want to modify the internals of InaSAFE. It assumes that '
        'you have basic coding skills. All examples are in python unless '
        'otherwise stated.'
    )))
    message.add(
        m.Heading(tr('Defining a new hazard type'), **SUBSECTION_STYLE))
    message.add(
        m.Heading(tr('Background'), **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(tr(
        'In the previous versions of InaSAFE, we spent a lot of effort '
        'building one impact function per hazard/exposure combination (and '
        'sometimes multiple impact functions per combination). In our new '
        'architecture, we try to deal with everything in the same way - by '
        'following a standardized process of converting the hazard dataset '
        'into a classified polygon layer and then calculating the impacted '
        'and affected areas using a standard work-flow. A simplified version '
        'of this work-flow is described in illustration 1.'))
    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'Because of this change, you will no longer see an impact function '
        'selector in the dock widget and there are no longer any \'impact '
        'function options\' as we had in previous versions of InaSAFE. In '
        'the new system, almost all configuration is managed through '
        'metadata (created using the keywords wizard).'))

    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'Also, in all versions prior to Version 4.0, we made heavy use of '
        'interpolation in order to determine whether buildings or other '
        'exposure layers are impacted. While this is a commonly used '
        'technique in GIS, it often leads to non - intuitive looking '
        'reports. Under our new architecture, we always use geometric '
        'overlay operations to make a determination whether an exposure '
        'feature is affected or not. The implication of this is that we '
        'produce intuitive and easily verifiable impact layers. You can '
        'see an example in Illustration 2.'
    ))
    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'Stepping away from the two previously mentioned paradigms allows '
        'us to simply add new hazard types to the metadata driven impact '
        'function by adding new metadata types to the InaSAFE sources. '
        'In the next chapter we show you how this was achieved and how '
        'it can be repeated for further hazards using the example of '
        'tropical cyclones.'
    ))
    message.add(paragraph)
    message.add(
        m.Heading(tr('Adding a new hazard'), **BLUE_CHAPTER_STYLE))
    link = m.Link(
        'https://github.com/inasafe/inasafe/pull/3539/files',
        tr('Pull Request #3539'))
    paragraph = m.Paragraph(
        tr('The whole work needed can be looked at in '),
        link,
        tr(
            '. Please bear in mind that the paths of the files are now '
            'safe/definitions/xxx.py and not safe/definitionsv4/xxx.py since '
            'v4 is the default codebase. In the next sections we will show '
            'each file that needs to be extended in order to add a new hazard '
            'type.'
    ))
    message.add(paragraph)

    # Setting up units

    message.add(
        m.Heading(tr('safe/definitions/units.py'), **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(tr(
        'If you are adding an hazard that uses units that are not yet known '
        'to InaSAFE, you need to define them in units.py')
    )
    message.add(paragraph)
    paragraph = m.PreformattedText(
        _get_definition_from_module(units, 'unit_kilometres_per_hour')
    )
    message.add(paragraph)

    # Setting up style

    message.add(
        m.Heading('safe/definitions/colors.py', **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(
        'If you are adding an hazard that has more classes than any other '
        'hazards you’ll need to add additional colors for the additional '
        'classes in colors.py. You might also define other colors if you '
        'don\'t want to use the standard colors. For the sake of homogeneous '
        'map reports, this addition should not be taken lightly.'
    )
    message.add(paragraph)
    # Don't translate this
    paragraph = m.PreformattedText('very_dark_red = Qcolor(\'#710017\')')
    message.add(paragraph)

    # Setting up hazard classification

    message.add(
        m.Heading(
            'safe/definitions/hazard_classifications.py',
            **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(tr(
        'Add the classifications you want to make available for your new '
        'hazard type. You can add as many classes as you want in the '
        'classes list.'))
    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'Also, a classification can support multiple units so you don\'t '
        'have to define different classifications just to have the same '
        'classification in two or more different units. These are defined '
        'in the multiple_units attribute of the classification.'
    ))
    message.add(paragraph)

    paragraph = m.PreformattedText(
        _get_definition_from_module(
            hazard_classifications,
            'cyclone_au_bom_hazard_classes')
    )
    message.add(paragraph)

    # Setting up wizard questions

    message.add(
        m.Heading(
            'safe/gui/tools/wizard/wizard_strings.py',
            **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(
        tr('Define the questions for the wizard:')
    )
    message.add(paragraph)
    # don not translate
    message.add(m.PreformattedText(
        'cyclone_kilometres_per_hour_question = tr(\'wind speed in km/h\')'))
    message.add(m.PreformattedText(
        'cyclone_miles_per_hour_question = tr(\'wind speed in mph\')'))
    message.add(m.PreformattedText(
        'cyclone_knots_question = tr(\'wind speed in kn\')'))

    # Setting up

    message.add(
        m.Heading('safe/definitions/hazard.py', **BLUE_CHAPTER_STYLE))
    paragraph = m.Paragraph(
        tr('Finally define new hazard and add it to the hazard_all list:')
    )
    message.add(paragraph)
    paragraph = m.PreformattedText(
        _get_definition_from_module(hazard, 'hazard_cyclone')
    )
    message.add(paragraph)
    paragraph = m.Paragraph(
        tr('Finally define new hazard and add it to the hazard_all list:')
    )
    message.add(paragraph)
    paragraph = m.PreformattedText(
        _get_definition_from_module(hazard, 'hazard_all')
    )
    message.add(paragraph)
    return message
Exemple #22
0
    def show_current_state(self):
        """Setup the UI for QTextEdit to show the current state."""
        right_panel_heading = QLabel(tr('Status'))
        right_panel_heading.setFont(big_font)
        right_panel_heading.setSizePolicy(QSizePolicy.Maximum,
                                          QSizePolicy.Maximum)
        self.right_layout.addWidget(right_panel_heading)

        message = m.Message()
        if self.layer_mode == layer_mode_continuous:
            title = tr('Thresholds')
        else:
            title = tr('Value maps')

        message.add(m.Heading(title, **INFO_STYLE))

        for i in range(len(self.exposures)):
            message.add(m.Text(self.exposure_labels[i]))

            classification = self.get_classification(
                self.exposure_combo_boxes[i])
            if self.layer_mode == layer_mode_continuous:
                thresholds = self.thresholds.get(self.exposures[i]['key'])
                if not thresholds or not classification:
                    message.add(m.Paragraph(tr('No classifications set.')))
                    continue
                table = m.Table(
                    style_class='table table-condensed table-striped')
                header = m.Row()
                header.add(m.Cell(tr('Class name')))
                header.add(m.Cell(tr('Minimum')))
                header.add(m.Cell(tr('Maximum')))
                table.add(header)
                classes = classification.get('classes')
                # Sort by value, put the lowest first
                classes = sorted(classes, key=lambda k: k['value'])
                for the_class in classes:
                    threshold = thresholds[classification['key']]['classes'][
                        the_class['key']]
                    row = m.Row()
                    row.add(m.Cell(the_class['name']))
                    row.add(m.Cell(threshold[0]))
                    row.add(m.Cell(threshold[1]))
                    table.add(row)
            else:
                value_maps = self.value_maps.get(self.exposures[i]['key'])
                if not value_maps or not classification:
                    message.add(m.Paragraph(tr('No classifications set.')))
                    continue
                table = m.Table(
                    style_class='table table-condensed table-striped')
                header = m.Row()
                header.add(m.Cell(tr('Class name')))
                header.add(m.Cell(tr('Value')))
                table.add(header)
                classes = classification.get('classes')
                # Sort by value, put the lowest first
                classes = sorted(classes, key=lambda k: k['value'])
                for the_class in classes:
                    value_map = value_maps[
                        classification['key']]['classes'].get(
                            the_class['key'], [])
                    row = m.Row()
                    row.add(m.Cell(the_class['name']))
                    row.add(m.Cell(', '.join([str(v) for v in value_map])))
                    table.add(row)
            message.add(table)

        # status_text_edit = QTextBrowser(None)
        status_text_edit = QWebView(None)
        status_text_edit.setSizePolicy(QSizePolicy.Ignored,
                                       QSizePolicy.Ignored)

        status_text_edit.page().mainFrame().setScrollBarPolicy(
            Qt.Horizontal, Qt.ScrollBarAlwaysOff)
        html_string = html_header() + message.to_html() + html_footer()
        status_text_edit.setHtml(html_string)
        self.right_layout.addWidget(status_text_edit)
def definition_to_message(
        definition, message=None, table_of_contents=None, heading_level=None):
    """Helper function to render a definition to a message.

    :param definition: A definition dictionary (see definitions package).
    :type definition: dict

    :param message: The message that the definition should be appended to.
    :type message: parameters.message.Message

    :param table_of_contents: Table of contents that the headings should be
        included in.
    :type message: parameters.message.Message

    :param heading_level: Optional style to apply to the definition
        heading. See HEADING_LOOKUPS
    :type heading_level: int

    :returns: Message
    :rtype: str
    """

    if message is None:
        message = m.Message()

    if table_of_contents is None:
        table_of_contents = m.Message()

    if heading_level:
        _create_section_header(
            message,
            table_of_contents,
            definition['name'].replace(' ', '-'),
            definition['name'],
            heading_level=heading_level)
    else:
        header = m.Paragraph(m.ImportantText(definition['name']))
        message.add(header)

    # If the definition has an icon, we put the icon and description side by
    # side in a table otherwise just show the description as a paragraph
    url = _definition_icon_url(definition)
    if url is None:
        message.add(m.Paragraph(definition['description']))
        if 'citations' in definition:
            _citations_to_message(message, definition)
    else:
        LOGGER.info('Creating mini table for definition description: ' + url)
        table = m.Table(style_class='table table-condensed')
        row = m.Row()
        row.add(m.Cell(m.Image(url, **MEDIUM_ICON_STYLE)))
        row.add(m.Cell(definition['description']))
        table.add(row)
        for citation in definition['citations']:
            if citation['text'] in [None, '']:
                continue
            row = m.Row()
            row.add(m.Cell(''))
            if citation['link'] in [None, '']:
                row.add(m.Cell(citation['text']))
            else:
                row.add(m.Cell(m.Link(citation['link'], citation['text'])))
            table.add(row)
        message.add(table)

    url = _definition_screenshot_url(definition)
    if url:
        message.add(m.Paragraph(m.Image(url), style_class='text-center'))

    # types contains e.g. hazard_all
    if 'types' in definition:
        for sub_definition in definition['types']:
            definition_to_message(
                sub_definition,
                message,
                table_of_contents,
                heading_level=3)

    #
    # Notes section if available
    #

    if 'notes' in definition:
        # Start a notes details group too since we have an exposure
        message.add(m.Heading(
            tr('Notes:'), **DETAILS_STYLE))
        message.add(m.Heading(
            tr('General notes:'), **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['notes']:
            if isinstance(note, dict):
                bullets = _add_dict_to_bullets(bullets, note)
            elif note:
                bullets.add(m.Text(note))
        message.add(bullets)
        if 'citations' in definition:
            _citations_to_message(message, definition)

    # This only for EQ
    if 'earthquake_fatality_models' in definition:
        current_function = current_earthquake_model_name()
        paragraph = m.Paragraph(tr(
            'The following earthquake fatality models are available in '
            'InaSAFE. Note that you need to set one of these as the '
            'active model in InaSAFE Options. The currently active model '
            'is: '),
            m.ImportantText(current_function)
        )
        message.add(paragraph)

        models_definition = definition['earthquake_fatality_models']
        for model in models_definition:
            message.add(m.Heading(model['name'], **DETAILS_SUBGROUP_STYLE))
            if 'description' in model:
                paragraph = m.Paragraph(model['description'])
                message.add(paragraph)
            for note in model['notes']:
                paragraph = m.Paragraph(note)
                message.add(paragraph)
            _citations_to_message(message, model)

    for exposure in exposure_all:
        extra_exposure_notes = specific_notes(definition, exposure)
        if extra_exposure_notes:
            title = tr('Notes for exposure : {exposure_name}').format(
                exposure_name=exposure['name'])
            message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE))
            bullets = m.BulletedList()
            for note in extra_exposure_notes:
                if isinstance(note, dict):
                    bullets = _add_dict_to_bullets(bullets, note)
                elif note:
                    bullets.add(m.Text(note))
            message.add(bullets)

    if 'continuous_notes' in definition:
        message.add(m.Heading(
            tr('Notes for continuous datasets:'),
            **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['continuous_notes']:
            bullets.add(m.Text(note))
        message.add(bullets)

    if 'classified_notes' in definition:
        message.add(m.Heading(
            tr('Notes for classified datasets:'),
            **DETAILS_SUBGROUP_STYLE))
        bullets = m.BulletedList()
        for note in definition['classified_notes']:
            bullets.add(m.Text(note))
        message.add(bullets)

    if 'single_event_notes' in definition:
        message.add(
            m.Heading(tr('Notes for single events'), **DETAILS_STYLE))
        if len(definition['single_event_notes']) < 1:
            message.add(m.Paragraph(tr('No single event notes defined.')))
        else:
            bullets = m.BulletedList()
            for note in definition['single_event_notes']:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'multi_event_notes' in definition:
        message.add(
            m.Heading(
                tr('Notes for multi events / scenarios:'),
                **DETAILS_STYLE))
        if len(definition['multi_event_notes']) < 1:
            message.add(m.Paragraph(tr('No multi-event notes defined.')))
        else:
            bullets = m.BulletedList()
            for note in definition['multi_event_notes']:
                bullets.add(m.Text(note))
            message.add(bullets)

    if 'actions' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Actions:'))))
        bullets = m.BulletedList()
        for note in definition['actions']:
            if isinstance(note, dict):
                bullets = _add_dict_to_bullets(bullets, note)
            elif note:
                bullets.add(m.Text(note))
        message.add(bullets)

    for exposure in exposure_all:
        extra_exposure_actions = specific_actions(definition, exposure)
        if extra_exposure_actions:
            title = tr('Actions for exposure : {exposure_name}').format(
                exposure_name=exposure['name'])
            message.add(m.Heading(title, **DETAILS_SUBGROUP_STYLE))
            bullets = m.BulletedList()
            for note in extra_exposure_actions:
                if isinstance(note, dict):
                    bullets = _add_dict_to_bullets(bullets, note)
                elif note:
                    bullets.add(m.Text(note))
            message.add(bullets)

    if 'continuous_hazard_units' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Units:'))))
        table = m.Table(style_class='table table-condensed table-striped')
        row = m.Row()
        row.add(m.Cell(tr('Name'), header=True))
        row.add(m.Cell(tr('Plural'), header=True))
        row.add(m.Cell(tr('Abbreviation'), header=True))
        row.add(m.Cell(tr('Details'), header=True))
        table.add(row)
        for unit in definition['continuous_hazard_units']:
            row = m.Row()
            row.add(m.Cell(unit['name']))
            row.add(m.Cell(unit['plural_name']))
            row.add(m.Cell(unit['abbreviation']))
            row.add(m.Cell(unit['description']))
            table.add(row)
        message.add(table)

    if 'fields' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Fields:'))))
        table = _create_fields_table()

        if 'extra_fields' in definition:
            all_fields = definition['fields'] + definition['extra_fields']
        else:
            all_fields = definition['fields']

        for field in all_fields:
            _add_field_to_table(field, table)
        message.add(table)

    if 'classifications' in definition:
        message.add(m.Heading(
            tr('Hazard classifications'),
            **DETAILS_STYLE))
        message.add(m.Paragraph(
            definitions.hazard_classification['description']))
        for inasafe_class in definition['classifications']:
            definition_to_message(
                inasafe_class,
                message,
                table_of_contents,
                heading_level=3)

    if 'classes' in definition:
        message.add(m.Paragraph(m.ImportantText(tr('Classes:'))))
        is_hazard = definition['type'] == hazard_classification_type
        if is_hazard:
            table = _make_defaults_hazard_table()
        else:
            table = _make_defaults_exposure_table()

        for inasafe_class in definition['classes']:
            row = m.Row()
            if is_hazard:
                # name() on QColor returns its hex code
                if 'color' in inasafe_class:
                    colour = inasafe_class['color'].name()
                    row.add(m.Cell(
                        '', attributes='style="background: %s;"' % colour))
                else:
                    row.add(m.Cell(' '))

            row.add(m.Cell(inasafe_class['name']))

            if is_hazard:
                if 'affected' in inasafe_class:
                    row.add(m.Cell(tr(inasafe_class['affected'])))
                else:
                    row.add(m.Cell(tr('unspecified')))

            if is_hazard:
                if inasafe_class.get('fatality_rate') is None or \
                        inasafe_class.get('fatality_rate') < 0:
                    row.add(m.Cell(tr('unspecified')))
                elif inasafe_class.get('fatality_rate') > 0:
                    # we want to show the rate as a scientific notation
                    rate = html_scientific_notation_rate(
                        inasafe_class['fatality_rate'])
                    rate = '%s%%' % rate
                    row.add(m.Cell(rate))
                else:  # == 0
                    row.add(m.Cell('0%'))

            if is_hazard:
                if 'displacement_rate' in inasafe_class:
                    rate = inasafe_class['displacement_rate'] * 100
                    rate = '%.0f%%' % rate
                    row.add(m.Cell(rate))
                else:
                    row.add(m.Cell(tr('unspecified')))

            if 'string_defaults' in inasafe_class:
                defaults = None
                for default in inasafe_class['string_defaults']:
                    if defaults:
                        defaults += ',%s' % default
                    else:
                        defaults = default
                row.add(m.Cell(defaults))
            else:
                row.add(m.Cell(tr('unspecified')))

            if is_hazard:
                # Min may be a single value or a dict of values so we need
                # to check type and deal with it accordingly
                if 'numeric_default_min' in inasafe_class:
                    if isinstance(inasafe_class['numeric_default_min'], dict):
                        bullets = m.BulletedList()
                        minima = inasafe_class['numeric_default_min']
                        for key, value in sorted(minima.items()):
                            bullets.add('%s : %s' % (key, value))
                        row.add(m.Cell(bullets))
                    else:
                        row.add(m.Cell(inasafe_class['numeric_default_min']))
                else:
                    row.add(m.Cell(tr('unspecified')))

            if is_hazard:
                # Max may be a single value or a dict of values so we need
                # to check type and deal with it accordingly
                if 'numeric_default_max' in inasafe_class:
                    if isinstance(inasafe_class['numeric_default_max'], dict):
                        bullets = m.BulletedList()
                        maxima = inasafe_class['numeric_default_max']
                        for key, value in sorted(maxima.items()):
                            bullets.add('%s : %s' % (key, value))
                        row.add(m.Cell(bullets))
                    else:
                        row.add(m.Cell(inasafe_class['numeric_default_max']))
                else:
                    row.add(m.Cell(tr('unspecified')))

            table.add(row)
            # Description goes in its own row with spanning
            row = m.Row()
            row.add(m.Cell(''))
            row.add(m.Cell(inasafe_class['description'], span=7))
            table.add(row)

        # For hazard classes we also add the 'not affected' class manually:
        if definition['type'] == definitions.hazard_classification_type:
            row = m.Row()
            colour = definitions.not_exposed_class['color'].name()
            row.add(m.Cell(
                '', attributes='style="background: %s;"' % colour))
            description = definitions.not_exposed_class['description']
            row.add(m.Cell(description, span=7))
            table.add(row)

        message.add(table)

    if 'affected' in definition:
        if definition['affected']:
            message.add(m.Paragraph(tr(
                'Exposure entities in this class ARE considered affected')))
        else:
            message.add(m.Paragraph(tr(
                'Exposure entities in this class are NOT considered '
                'affected')))

    if 'optional' in definition:
        if definition['optional']:
            message.add(m.Paragraph(tr(
                'This class is NOT required in the hazard keywords.')))
        else:
            message.add(m.Paragraph(tr(
                'This class IS required in the hazard keywords.')))

    return message
Exemple #24
0
            LOGGER.debug('get engine impact layer')
            LOGGER.debug(self.analysis is None)
            engine_impact_layer = self.analysis.impact_layer

            # Load impact layer into QGIS
            qgis_impact_layer = read_impact_layer(engine_impact_layer)

            report = self.show_results(qgis_impact_layer, engine_impact_layer)

        except Exception, e:  # pylint: disable=W0703
            # FIXME (Ole): This branch is not covered by the tests
            self.analysis_error(e, self.tr('Error loading impact layer.'))
        else:
            # On success, display generated report
            impact_path = qgis_impact_layer.source()
            message = m.Message(report)
            # message.add(m.Heading(self.tr('View processing log as HTML'),
            #                      **INFO_STYLE))
            # message.add(m.Link('file://%s' % self.parent.wvResults.log_path))
            # noinspection PyTypeChecker
            self.show_static_message(message)
            self.parent.wvResults.impact_path = impact_path

        self.parent.pbProgress.hide()
        self.parent.lblAnalysisStatus.setText('Analysis done.')
        self.parent.pbnReportWeb.show()
        self.parent.pbnReportPDF.show()
        self.parent.pbnReportComposer.show()
        self.hide_busy()
        self.analysisDone.emit(True)
Exemple #25
0
 def test_static_message(self):
     """Test we can send static messages to the message viewer."""
     self.message_viewer.static_message_event(None, m.Message('Hi'))
     text = self.message_viewer.page_to_text()
     self.assertEqual(text, 'Hi\n')
Exemple #26
0
    def show_results(self, qgis_impact_layer, engine_impact_layer):
        """Helper function for slot activated when the process is done.

        .. note:: Adapted from the dock

        :param qgis_impact_layer: A QGIS layer representing the impact.
        :type qgis_impact_layer: QgsMapLayer, QgsVectorLayer, QgsRasterLayer

        :param engine_impact_layer: A safe_layer representing the impact.
        :type engine_impact_layer: ReadLayer

        :returns: Provides a report for writing to the dock.
        :rtype: str
        """
        keywords = self.keyword_io.read_keywords(qgis_impact_layer)

        # write postprocessing report to keyword
        output = self.analysis.postprocessor_manager.get_output(
            self.analysis.aggregator.aoi_mode)
        keywords['postprocessing_report'] = output.to_html(
            suppress_newlines=True)
        self.keyword_io.write_keywords(qgis_impact_layer, keywords)

        # Get tabular information from impact layer
        report = m.Message()
        report.add(LOGO_ELEMENT)
        report.add(m.Heading(self.tr('Analysis Results'), **INFO_STYLE))
        report.add(
            self.keyword_io.read_keywords(qgis_impact_layer, 'impact_summary'))

        # Get requested style for impact layer of either kind
        style = engine_impact_layer.get_style_info()
        style_type = engine_impact_layer.get_style_type()

        # Determine styling for QGIS layer
        if engine_impact_layer.is_vector:
            LOGGER.debug('myEngineImpactLayer.is_vector')
            if not style:
                # Set default style if possible
                pass
            elif style_type == 'categorizedSymbol':
                LOGGER.debug('use categorized')
                set_vector_categorized_style(qgis_impact_layer, style)
            elif style_type == 'graduatedSymbol':
                LOGGER.debug('use graduated')
                set_vector_graduated_style(qgis_impact_layer, style)

        elif engine_impact_layer.is_raster:
            LOGGER.debug('myEngineImpactLayer.is_raster')
            if not style:
                qgis_impact_layer.setDrawingStyle("SingleBandPseudoColor")
                # qgis_impact_layer.setColorShadingAlgorithm(
                #    QgsRasterLayer.PseudoColorShader)
            else:
                setRasterStyle(qgis_impact_layer, style)

        else:
            message = self.tr(
                'Impact layer %s was neither a raster or a vector layer') % (
                    qgis_impact_layer.source())
            # noinspection PyExceptionInherit
            raise ReadLayerError(message)

        # Add layers to QGIS
        layers_to_add = []
        if self.show_intermediate_layers:
            layers_to_add.append(self.analysis.aggregator.layer)
        layers_to_add.append(qgis_impact_layer)
        # noinspection PyArgumentList
        QgsMapLayerRegistry.instance().addMapLayers(layers_to_add)
        # make sure it is active in the legend - needed since QGIS 2.4
        self.iface.setActiveLayer(qgis_impact_layer)
        # then zoom to it
        if self.zoom_to_impact_flag:
            self.iface.zoomToActiveLayer()
        if self.hide_exposure_flag:
            exposure_layer = self.analysis.exposure_layer
            legend = self.iface.legendInterface()
            legend.setLayerVisible(exposure_layer, False)

        # append postprocessing report
        report.add(output.to_html())
        # Layer attribution comes last
        report.add(impact_attribution(keywords).to_html(True))
        # Return text to display in report panel
        return report
Exemple #27
0
    def process_components(self):
        """Process context for each component and a given template.

        :returns: Tuple of error code and message
        :type: tuple

        .. versionadded:: 4.0
        """
        message = m.Message()
        warning_heading = m.Heading(tr('Report Generation issue'),
                                    **WARNING_STYLE)
        message.add(warning_heading)
        failed_extract_context = m.Heading(tr('Failed to extract context'),
                                           **WARNING_STYLE)
        failed_render_context = m.Heading(tr('Failed to render context'),
                                          **WARNING_STYLE)
        failed_find_extractor = m.Heading(
            tr('Failed to load extractor method'), **WARNING_STYLE)
        failed_find_renderer = m.Heading(tr('Failed to load renderer method'),
                                         **WARNING_STYLE)

        generation_error_code = self.REPORT_GENERATION_SUCCESS

        for component in self.metadata.components:
            # load extractors
            try:
                if not component.context:
                    if callable(component.extractor):
                        _extractor_method = component.extractor
                    else:
                        _package_name = (
                            '%(report-key)s.extractors.%(component-key)s')
                        _package_name %= {
                            'report-key': self.metadata.key,
                            'component-key': component.key
                        }
                        # replace dash with underscores
                        _package_name = _package_name.replace('-', '_')
                        _extractor_path = os.path.join(
                            self.metadata.template_folder, component.extractor)
                        _module = imp.load_source(_package_name,
                                                  _extractor_path)
                        _extractor_method = getattr(_module, 'extractor')
                else:
                    LOGGER.info('Predefined context. Extractor not needed.')
            except Exception as e:  # pylint: disable=broad-except
                generation_error_code = self.REPORT_GENERATION_FAILED
                LOGGER.info(e)
                if self.impact_function.debug_mode:
                    raise
                else:
                    message.add(failed_find_extractor)
                    message.add(component.info)
                    message.add(get_error_message(e))
                    continue

            # method signature:
            #  - this ImpactReport
            #  - this component
            try:
                if not component.context:
                    context = _extractor_method(self, component)
                    component.context = context
                else:
                    LOGGER.info('Using predefined context.')
            except Exception as e:  # pylint: disable=broad-except
                generation_error_code = self.REPORT_GENERATION_FAILED
                LOGGER.info(e)
                if self.impact_function.debug_mode:
                    raise
                else:
                    message.add(failed_extract_context)
                    message.add(get_error_message(e))
                    continue

            try:
                # load processor
                if callable(component.processor):
                    _renderer = component.processor
                else:
                    _package_name = '%(report-key)s.renderer.%(component-key)s'
                    _package_name %= {
                        'report-key': self.metadata.key,
                        'component-key': component.key
                    }
                    # replace dash with underscores
                    _package_name = _package_name.replace('-', '_')
                    _renderer_path = os.path.join(
                        self.metadata.template_folder, component.processor)
                    _module = imp.load_source(_package_name, _renderer_path)
                    _renderer = getattr(_module, 'renderer')
            except Exception as e:  # pylint: disable=broad-except
                generation_error_code = self.REPORT_GENERATION_FAILED
                LOGGER.info(e)
                if self.impact_function.debug_mode:
                    raise
                else:
                    message.add(failed_find_renderer)
                    message.add(component.info)
                    message.add(get_error_message(e))
                    continue

            # method signature:
            #  - this ImpactReport
            #  - this component
            if component.context:
                try:
                    output = _renderer(self, component)
                    output_path = self.component_absolute_output_path(
                        component.key)
                    if isinstance(output_path, dict):
                        try:
                            dirname = os.path.dirname(output_path.get('doc'))
                        except:
                            dirname = os.path.dirname(output_path.get('map'))
                    else:
                        dirname = os.path.dirname(output_path)
                    if component.resources:
                        for resource in component.resources:
                            target_resource = os.path.basename(resource)
                            target_dir = os.path.join(dirname, 'resources',
                                                      target_resource)
                            # copy here
                            shutil.copytree(resource, target_dir)
                    component.output = output
                except Exception as e:  # pylint: disable=broad-except
                    generation_error_code = self.REPORT_GENERATION_FAILED
                    LOGGER.info(e)
                    if self.impact_function.debug_mode:
                        raise
                    else:
                        message.add(failed_render_context)
                        message.add(get_error_message(e))
                        continue

        return generation_error_code, message
Exemple #28
0
    def print_map(self, mode='pdf'):
        """Open impact report dialog that used define report options.

        :param mode: Mode for report - defaults to PDF.
        :type mode:
        """
        # Check if selected layer is valid
        impact_layer = self.iface.activeLayer()
        if impact_layer is None:
            # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
            QtGui.QMessageBox.warning(
                self.parent, self.tr('InaSAFE'),
                self.tr('Please select a valid impact layer before trying to '
                        'print.'))
            return

        # Open Impact Report Dialog
        print_dialog = ImpactReportDialog(self.iface)
        print_dialog.button_ok = QtGui.QPushButton(self.tr('OK'))
        print_dialog.buttonBox.addButton(print_dialog.button_ok,
                                         QtGui.QDialogButtonBox.ActionRole)

        # noinspection PyUnresolvedReferences
        print_dialog.button_ok.clicked.connect(print_dialog.accept)

        print_dialog.button_save_pdf.hide()
        print_dialog.button_open_composer.hide()

        if not print_dialog.exec_() == QtGui.QDialog.Accepted:
            # noinspection PyTypeChecker
            self.show_dynamic_message(
                self,
                m.Message(m.Heading(self.tr('Map Creator'), **WARNING_STYLE),
                          m.Text(self.tr('Report generation cancelled!'))))
            return

        # Get the extent of the map for report
        use_full_extent = print_dialog.analysis_extent_radio.isChecked()
        if use_full_extent:
            map_crs = self.iface.mapCanvas().mapRenderer().destinationCrs()
            layer_crs = self.iface.activeLayer().crs()
            layer_extent = self.iface.activeLayer().extent()
            if map_crs != layer_crs:
                # noinspection PyCallingNonCallable
                transform = QgsCoordinateTransform(layer_crs, map_crs)
                layer_extent = transform.transformBoundingBox(layer_extent)
            area_extent = layer_extent
        else:
            area_extent = self.iface.mapCanvas().extent()

        # Get selected template path to use
        if print_dialog.default_template_radio.isChecked():
            template_path = print_dialog.template_combo.itemData(
                print_dialog.template_combo.currentIndex())
        else:
            template_path = print_dialog.template_path.text()
            if not os.path.exists(template_path):
                # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
                QtGui.QMessageBox.warning(
                    self.parent, self.tr('InaSAFE'),
                    self.tr('Please select a valid template before printing. '
                            'The template you choose does not exist.'))
                return

        # Instantiate and prepare Report
        # noinspection PyTypeChecker
        self.show_dynamic_message(
            self,
            m.Message(
                m.Heading(self.tr('Map Creator'), **PROGRESS_UPDATE_STYLE),
                m.Text(self.tr('Preparing map and report'))))

        impact_report = ImpactReport(self.iface, template_path, impact_layer)
        impact_report.extent = area_extent

        # Get other setting
        settings = QSettings()
        logo_path = settings.value('inasafe/organisation_logo_path',
                                   '',
                                   type=str)
        impact_report.organisation_logo = logo_path

        disclaimer_text = settings.value('inasafe/reportDisclaimer',
                                         '',
                                         type=str)
        impact_report.disclaimer = disclaimer_text

        north_arrow_path = settings.value('inasafe/north_arrow_path',
                                          '',
                                          type=str)
        impact_report.north_arrow = north_arrow_path

        template_warning_verbose = bool(
            settings.value('inasafe/template_warning_verbose', True,
                           type=bool))

        # Check if there's missing elements needed in the template
        component_ids = [
            'safe-logo', 'north-arrow', 'organisation-logo', 'impact-map',
            'impact-legend'
        ]
        impact_report.component_ids = component_ids
        if template_warning_verbose and \
                len(impact_report.missing_elements) != 0:
            title = self.tr('Template is missing some elements')
            question = self.tr(
                'The composer template you are printing to is missing '
                'these elements: %s. Do you still want to continue') % (
                    ', '.join(impact_report.missing_elements))
            # noinspection PyCallByClass,PyTypeChecker
            answer = QtGui.QMessageBox.question(
                self.parent, title, question,
                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            if answer == QtGui.QMessageBox.No:
                return

        create_pdf_flag = bool(mode == 'pdf')
        self.show_busy()
        if create_pdf_flag:
            self.print_map_to_pdf(impact_report)
        else:
            self.open_map_in_composer(impact_report)

        self.hide_busy()
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    paragraph = m.Paragraph(
        m.Image(
            'file:///%s/img/screenshots/'
            'batch-calculator-screenshot.png' % resources_path()),
        style_class='text-center'
    )
    message.add(paragraph)
    message.add(m.Paragraph(tr(
        'Depending on which Impact Function you have chosen you have '
        'different options available for adjusting the parameters of the '
        'question you are asking. Some Impact Functions have more '
        'configurable Options than others. To open the Impact Function '
        'Configuration Dialog you need to click on the "Options ..." '
        'button next to the selected impact function paragraph in the '
        'InaSAFE dock. You can have up to 3 tabs visible:'
    )))

    bullets = m.BulletedList()
    bullets.add(m.Text(
        m.ImportantText(tr('Options')),
        tr(
            '- Depending in the Impact function you selected, you can '
            'influence the result of your question here (the Impact Function) '
            'by setting different values to the defaults that will be loaded. '
            'The options available will depend on the Impact Function you '
            'choose (some Impact Functions do not allow users to change the '
            'default parameters).')))
    bullets.add(m.Text(
        m.ImportantText(tr('Post-processors')),
        tr(
            '- Takes the results from the Impact Function and calculates '
            'derivative indicators, for example if you have an affected '
            'population total, the Gender postprocessor will calculate gender '
            'specific indicators such as additional nutritional requirements '
            'for pregnant women.')))
    bullets.add(m.Text(
        m.ImportantText(tr('Minimum Needs')),
        tr(
            '- If the analysis uses population exposure, InaSAFE calculates '
            'the minimum needs of the people affected by the impact scenario. '
            'You should refer to the minimum needs tool for configuring the '
            'global defaults used in these calculations. '),
        m.Image(
            'file:///%s/img/icons/'
            'show-minimum-needs.svg' % resources_path(),
            **SMALL_ICON_STYLE),
        tr(
            ' This panel will let you override global defaults for a specific '
            'analysis run.')))
    message.add(bullets)
    return message
Exemple #30
0
def content():
    """Helper method that returns just the content.

    This method was added so that the text could be reused in the
    dock_help module.

    .. versionadded:: 3.2.2

    :returns: A message object without brand element.
    :rtype: safe.messaging.message.Message
    """
    message = m.Message()
    paragraph = m.Paragraph(tr(
        'InaSAFE is free software that produces realistic natural hazard '
        'impact scenarios for better planning, preparedness and response '
        'activities. It provides a simple but rigourous way to combine data '
        'from scientists, local governments and communities to provide '
        'insights into the likely impacts of future disaster events.'
    ))
    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'The InaSAFE \'dock panel\' helps you to run hazard impact analysis '
        'within the QGIS environment. It helps you create your hazard impact '
        'analysis question and shows the results of this analysis. If you are '
        'a new user, you may also consider using the \'Impact Function '
        'Centric Wizard\' to run the analysis. This wizard will guide you '
        'through the process of running an InaSAFE assessment, with '
        'interactive step by step instructions. You can launch the wizard '
        'by clicking on this icon in the toolbar:'),
        m.Image(
            'file:///%s/img/icons/'
            'show-wizard.svg' % resources_path(),
            **SMALL_ICON_STYLE),

    )
    message.add(paragraph)
    paragraph = m.Paragraph(tr(
        'You can drag and drop the dock panel to reposition it on the screen. '
        'For example, dragging the panel towards the right margin of the QGIS '
        'application will dock it to the right side of the screen.'
    ))
    message.add(paragraph)

    message.add(m.Paragraph(tr(
        'There are three main areas to the dock panel:')))
    bullets = m.BulletedList()
    bullets.add(m.Text(
        # format 'the __questions__ area' for proper i18n
        tr('the %s area') % (
            m.ImportantText(tr(
                'questions')).to_html(),
        )))
    bullets.add(m.Text(
        # format 'the __results__ area' for proper i18n
        tr('the %s area') % (
            m.ImportantText(tr(
                'results')).to_html(),
        )))
    bullets.add(m.Text(
        # format 'the __buttons__ area' for proper i18n
        tr('the %s area') % (
            m.ImportantText(tr(
                'buttons')).to_html(),
        )))
    message.add(bullets)
    message.add(m.Paragraph(tr(
        'You can get help at any time in InaSAFE by clicking on the '
        'help buttons provided on each dock and dialog.')))

    header = m.Heading(tr('The questions area'), **INFO_STYLE)
    message.add(header)
    message.add(m.Paragraph(tr(
        'The intention of InaSAFE is to make it easy to perform your impact '
        'analysis. We start the analysis in the questions area. This area '
        'contains three drop down menus. You create your question by using '
        'these drop down menus to select the hazard and exposure data you '
        'wish to perform the analysis on. '
        'All questions follow this form:'),
        m.EmphasizedText(tr(
            'In the event of a [hazard], how many [exposure] might be '
            '[impacted]?'))))
    message.add(m.Paragraph(tr(
        'For example: "If there is a flood, how many buildings might be '
        'flooded?"')))
    message.add(m.Paragraph(tr(
        'InaSAFE can be used to answer such questions for hazards such as '
        'flood, tsunami, volcanic ash fall and earthquake and exposures '
        'such as population, roads, structures, land cover etc.')))
    message.add(m.Paragraph(tr(
        'The first step in answering these questions is to load layers that '
        'represent either hazard scenarios or exposure data into QGIS. '
        'A hazard, for example, may be represented as a raster layer in '
        'QGIS where each pixel in the raster represents the flood depth '
        'following an inundation event. An exposure layer could be '
        'represented, for example, as vector polygon data representing '
        'building outlines, or a raster outline where each pixel represents '
        'the number of people thought to be living in that cell.')))
    message.add(m.Paragraph(tr(
        'InaSAFE will combine these two layers in a '
        'mathematical model. The results of this model will show what the '
        'effect of the hazard will be on the exposed infrastructure or '
        'people. The plugin relies on simple keyword metadata '
        'associated with each layer to determine what kind of information the '
        'layer represents. You can define these keywords by '
        'selecting a layer and then clicking the InaSAFE Keywords Wizard icon '
        'on the toolbar: '),
        m.Image(
            'file:///%s/img/icons/'
            'show-keyword-wizard.svg' % resources_path(),
            **SMALL_ICON_STYLE),
        tr(
            'The wizard will guide you through the process of defining the '
            'keywords for that layer.')))
    message.add(m.Paragraph(tr(
        'Aggregation is the process whereby we group the analysis results '
        'by district so that you can see how many people, roads or '
        'buildings were affected in each area. This will help you to '
        'understand where the most critical needs are.  Aggregation is '
        'optional in InaSAFE - if you do not use aggregation, the entire '
        'analysis area will be used for the data summaries. Typically '
        'aggregation layers in InaSAFE have the name of the district or '
        'reporting area as attributes. It is also possible to use extended '
        'attributes to indicate the ratio of men and women; youth, adults '
        'and elderly living in each area. Where these are provided and the '
        'exposure layer is population, InaSAFE will provide a demographic '
        'breakdown per aggregation area indicating how many men, women, etc. '
        'were probably affected in that area.'
    )))

    header = m.Heading(tr('The results area'), **INFO_STYLE)
    message.add(header)

    message.add(m.Paragraph(tr(
        'After running an analysis, the question area is hidden to maximise '
        'the amount of space allocated to the results area. You can '
        're-open the question area at any time by pressing the \'show '
        'question form\' button.')))

    message.add(m.Paragraph(tr(
        'The results area is used to display various useful feedback items to '
        'the user. Once an impact scenario has been run, a summary table will '
        'be shown.')))

    message.add(m.Paragraph(tr(
        'If you select an impact layer (i.e. a layer that was produced using '
        'an InaSAFE Impact Function), in the QGIS layers list, this summary '
        'will also be displayed in the results area. When you select a hazard '
        'or exposure layer in the QGIS layers list, the keywords for that '
        'layer will be shown in the results area, making it easy to '
        'understand what metadata exists for that layer.')))

    message.add(m.Paragraph(tr(
        'The results area is also used to display status information. For '
        'example, during the analysis process, the status area will display '
        'notes about each step in the analysis process. The \'Run\' '
        'button will be activated when both a valid hazard and valid exposure '
        'layer have been added in QGIS.'
    )))

    message.add(m.Paragraph(tr(
        'Finally, the results area is also used to display any error messages '
        'so that you can see what went wrong and why. You may need to '
        'scroll down to view the message completely to see all of the error '
        'message details.'
    )))

    message.add(m.Paragraph(tr(
        'After running the impact scenario calculation, the question is '
        'automatically hidden to make the results area as large as possible. '
        'If you want to see what the question used in the analysis was, click '
        'on the \'Show question form\' button at the top of the results area.'
    )))

    message.add(m.Paragraph(tr(
        'If you want to hide the question area again to have more space to '
        'display the results, click on the layer you just calculated '
        'with InaSAFE in the Layers list of QGIS to make it active.'
    )))

    header = m.Heading(tr('The buttons area'), **INFO_STYLE)
    message.add(header)

    message.add(m.Paragraph(tr(
        'The buttons area contains four buttons:')))
    bullets = m.BulletedList()
    bullets.add(m.Text(
        m.ImportantText(tr('Help')),
        tr(
            '- click on this if you need context help, such as the document '
            'you are reading right now!')))
    bullets.add(m.Text(
        m.ImportantText(tr('About')),
        tr(
            '- click on this to see short credits for the InaSAFE project.')))
    bullets.add(m.Text(
        m.ImportantText(tr('Print')),
        tr(
            '... - click on this if you wish to create a pdf of your '
            'impact scenario project or generate a report to open in '
            'composer for further tuning. An impact layer must be active '
            'before the \'Print\' button will be enabled.')))
    bullets.add(m.Text(
        m.ImportantText(tr('Run')),
        tr(
            '- this button is enabled when the combination of hazard and '
            'exposure selected in the questions area\'s drop down menus will '
            'allow you to run a scenario.')))
    message.add(bullets)

    header = m.Heading(tr('Data conversions'), **INFO_STYLE)
    message.add(header)

    message.add(m.Paragraph(tr(
        'When running a scenario, the data being used needs to be processed '
        'into a state where it is acceptable for use by InaSAFE. '
        'In particular it should be noted that:')))

    bullets = m.BulletedList()
    bullets.add(tr(
        'Remote datasets will be copied locally before processing.'))
    bullets.add(m.Text(
        tr(
            'All datasets will be clipped to the behaviours defined in the '
            'analysis extents dialog if you do not use an aggregation layer.'),
        m.Image(
            'file:///%s/img/icons/'
            'set-extents-tool.svg' % resources_path(),
            **SMALL_ICON_STYLE)
    ))
    bullets.add(m.Text(
        tr(
            'You can visualise the area that will be used for the analysis '
            'by enabling the "Toggle Scenario Outlines" tool. When this tool '
            'is enabled, a line (green by default) will be drawn around the '
            'outermost boundary of the analysis area.'),
        m.Image(
            'file:///%s/img/icons/'
            'toggle-rubber-bands.svg' % resources_path(),
            **SMALL_ICON_STYLE)
    ))
    bullets.add(m.Text(
        tr(
            'When you have selected an aggregation layer the analysis area '
            'will be the outline of the aggregation layer. If you select one '
            'or more polygons in the aggregation layer (by using the QGIS '
            'feature selection tools), the analysis boundary will be reduced '
            'to just the outline of these selected polygons. If the "Toggle '
            'Scenario Outlines" tool is enabled, the preview of the effective '
            'analysis area will be updated to reflect the selected features.'),
    ))
    bullets.add(tr(
        'All clipped datasets will be converted (reprojected) to the '
        'Coordinate Reference System of the exposure layer '
        'before analysis.'))
    message.add(bullets)

    header = m.Heading(tr('Generating impact reports'), **INFO_STYLE)
    message.add(header)

    message.add(m.Paragraph(tr(
        'When the impact analysis has completed you may want to generate a '
        'report. Usually the \'Print...\'  button will be enabled immediately '
        'after analysis. Selecting an InaSAFE impact layer in QGIS Layers '
        'panel will also enable it.'
    )))

    # This adds the help content of the print dialog
    message.add(report())
    return message