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