def format_impact_summary(self): """Format impact summary. :returns: The impact summary. :rtype: safe.messaging.Message """ message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None if 'headings' in self.impact_summary.keys(): row = m.Row() row.add(m.Cell(self.impact_summary['headings'][0], header=True)) row.add( m.Cell(self.impact_summary['headings'][1], header=True, align='right')) table.add(row) for category in self.impact_summary['fields']: row = m.Row() row.add(m.Cell(category[0], header=True)) row.add(m.Cell(self.format_int(category[1]), align='right')) # For value field, if existed if len(category) > 2: row.add(m.Cell(self.format_int(category[2]), align='right')) table.add(row) message.add(table) return message
def _start_glossary_table(group): table = m.Table(style_class='table table-condensed table-striped') row = m.Row() row.add(m.Cell(tr('Term')), header_flag=True) row.add(m.Cell(tr('Description')), header_flag=True) table.add(row) return table
def minimum_needs_breakdown(self): """Breakdown by population. :returns: The population breakdown report. :rtype: list """ message = m.Message(style_class='container') message.add( m.Heading(tr('Evacuated population minimum needs'), **styles.INFO_STYLE)) table = m.Table(style_class='table table-condensed table-striped') table.caption = None total_needs = self.total_needs for frequency, needs in total_needs.items(): row = m.Row() row.add( m.Cell(tr('Relief items to be provided %s' % frequency), header=True)) row.add(m.Cell(tr('Total'), header=True, align='right')) table.add(row) for resource in needs: row = m.Row() row.add(m.Cell(tr(resource['table name']))) row.add( m.Cell(tr(format_int(resource['amount'])), align='right')) table.add(row) message.add(table) return message
def impact_summary(self): """The impact summary as per category :returns: The impact summary. :rtype: safe.message.Message """ affected_categories = self.affected_road_categories message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None row = m.Row() row.add(m.Cell(tr('Summary by road type'), header=True)) for _ in affected_categories: # Add empty cell as many as affected_categories row.add(m.Cell('', header=True)) if self.add_unaffected_column: # Add empty cell for un-affected road row.add(m.Cell('', header=True)) # Add empty cell for total column row.add(m.Cell('', header=True)) table.add(row) row = m.Row() row.add(m.Cell(tr('Road Type'), header=True)) for affected_category in affected_categories: row.add(m.Cell(affected_category, header=True, align='right')) if self.add_unaffected_column: row.add(m.Cell(tr('Unaffected'), header=True, align='right')) row.add(m.Cell(tr('Total'), header=True, align='right')) table.add(row) total_affected = [0] * len(affected_categories) for (category, road_breakdown) in self.affected_road_lengths.items(): number_affected = sum(road_breakdown.values()) count = affected_categories.index(category) total_affected[count] = number_affected row = m.Row() row.add(m.Cell(tr('All (m)'))) for total_affected_value in total_affected: row.add( m.Cell(format_int(int(total_affected_value)), align='right')) if self.add_unaffected_column: row.add( m.Cell(format_int( int(self.total_road_length - sum(total_affected))), align='right')) row.add(m.Cell(format_int(int(self.total_road_length)), align='right')) table.add(row) message.add(table) return message
def generate_analysis_result_html(self): """Return a HTML table of the analysis result :return: A file path to the html file saved to disk. """ message = m.Message(style_class='report') # Table for affected population table = m.Table(style_class='table table-condensed table-striped') row = m.Row() total_people = self.tr('%s') % format_int( population_rounding(self.impact_data.total_affected_population)) estimates_idp = self.tr('%s') % format_int( population_rounding(self.impact_data.estimates_idp)) row.add( m.Cell(self.tr('Total affected population (people)'), header=True)) row.add(m.Cell(total_people, style_class="text-right")) table.add(row) row = m.Row() row.add(m.Cell(self.tr('Estimates of IDP (people)'), header=True)) row.add(m.Cell(estimates_idp, style_class="text-right")) table.add(row) message.add(table) # Table for minimum needs for k, v in self.impact_data.minimum_needs.iteritems(): section = self.tr('Relief items to be provided %s :') % k # text = m.Text(section) row = m.Row(style_class='alert-info') row.add(m.Cell(section, header=True, attributes='colspan=2')) # message.add(text) table = m.Table(header=row, style_class='table table-condensed table-striped') for e in v: row = m.Row() need_name = self.tr(e['name']) need_number = format_int(population_rounding(e['amount'])) need_unit = self.tr(e['unit']['abbreviation']) if need_unit: need_string = '%s (%s)' % (need_name, need_unit) else: need_string = need_name row.add(m.Cell(need_string, header=True)) row.add(m.Cell(need_number, style_class="text-right")) table.add(row) message.add(table) path = self.write_html_table('impact_analysis_report.html', message) return path
def _start_glossary_table(group): table = m.Table(style_class='table table-condensed table-striped') row = m.Row() # first col for icons if present row.add(m.Cell(tr('Term'), header=True)) row.add(m.Cell(tr('Description'), header=True)) row.add(m.Cell(tr(''), header=True)) table.add(row) return table
def impact_summary(self): """The impact summary as per category :returns: The impact summary. :rtype: safe.messaging.Message """ message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None row = m.Row() row = self.head_row(row) table.add(row) second_row = m.Row() second_row.add(m.Cell(tr('All'))) second_row = self.total_row(second_row) table.add(second_row) break_row = m.Row() break_row.add( m.Cell(tr('Breakdown by Area'), header=True, align='right')) # intentionally empty right cells break_row.add(m.Cell('', header=True)) break_row.add(m.Cell('', header=True)) break_row.add(m.Cell('', header=True)) break_row.add(m.Cell('', header=True)) break_row.add(m.Cell('', header=True)) break_row.add(m.Cell('', header=True)) table.add(break_row) table = self.impact_calculation(table) last_row = m.Row() last_row.add(m.Cell(tr('Total'))) table.add(self.total_row(last_row)) hazard_table = m.Table( style_class='table table-condensed table-striped') hazard_table = self.hazard_table(hazard_table) message.add(hazard_table) message.add(table) return message
def format_postprocessing(self): """Format postprocessing. :returns: The postprocessing. :rtype: safe.messaging.Message """ # List of post processor that can't be sum up. Issue #3118 no_total = ['Age', 'Gender', 'MinimumNeeds'] if not self.postprocessing: return False message = m.Message() for postprocessor, v in self.postprocessing.items(): table = m.Table(style_class='table table-condensed table-striped') table.caption = v['caption'] attributes = v['attributes'] if attributes: header = m.Row() # Bold and align left the 1st one. header.add(m.Cell(attributes[0], header=True, align='left')) for attribute in attributes[1:]: # Bold and align right. header.add(m.Cell(attribute, header=True, align='right')) if postprocessor not in no_total: header.add(m.Cell('Total', header=True, align='right')) table.add(header) for field in v['fields']: row = m.Row() # First column is string row.add(m.Cell(field[0])) total = 0 for value in field[1:]: try: val = int(value) total += val # Align right integers. row.add(m.Cell(self.format_int(val), align='right')) except ValueError: # Catch no data value. Align left strings. row.add(m.Cell(value, align='left')) if postprocessor not in no_total: row.add( m.Cell(self.format_int(round(total)), align='right')) table.add(row) message.add(table) for note in v['notes']: message.add(m.EmphasizedText(note)) return message
def _create_fields_table(): 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('Field Name'), header=True)) row.add(m.Cell(tr('Type'), header=True)) row.add(m.Cell(tr('Length'), header=True)) row.add(m.Cell(tr('Precision'), header=True)) table.add(row) return table
def _dict_to_row(keyword_value): """Helper to make a message row from a keyword where value is a dict. .. versionadded:: 3.2 Use this when constructing a table from keywords to display as part of a message object. This variant will unpack the dict and present it nicely in the keyword value area as a nested table in the cell. We are expecting keyword value would be something like this: "{'high': ['Kawasan Rawan Bencana III'], " "'medium': ['Kawasan Rawan Bencana II'], " "'low': ['Kawasan Rawan Bencana I']}" Or by passing a python dict object with similar layout to above. i.e. A string representation of a dict where the values are lists. :param keyword_value: Value of the keyword to be rendered. This must be a string representation of a dict, or a dict. :type keyword_value: basestring, dict :returns: A table to be added into a cell in the keywords table. :rtype: safe.messaging.items.table """ if isinstance(keyword_value, basestring): keyword_value = literal_eval(keyword_value) table = m.Table(style_class='table table-condensed') for key, value in keyword_value.items(): row = m.Row() # First the heading if definition(key): name = definition(key)['name'] else: name = tr(key.capitalize()) row.add(m.Cell(m.ImportantText(name))) # Then the value. If it contains more than one element we # present it as a bullet list, otherwise just as simple text if isinstance(value, (tuple, list, dict, set)): if len(value) > 1: bullets = m.BulletedList() for item in value: bullets.add(item) row.add(m.Cell(bullets)) elif len(value) == 0: row.add(m.Cell("")) else: row.add(m.Cell(value[0])) else: row.add(m.Cell(value)) table.add(row) return table
def _make_defaults_exposure_table(): """Build headers for a table related to exposure classes. :return: A table with headers. :rtype: m.Table """ 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 values'), header=True)) table.add(row) return table
def _create_post_processor_subtable(item_list): 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 item in item_list: row = m.Row() row.add(m.Cell(item['key'])) row.add(m.Cell(item['description'])) table.add(row) return table
def _make_defaults_table(): table = m.Table(style_class='table table-condensed table-striped') row = m.Row() # first row is for colour - we dont use a header here as some tables # do not have colour... row.add(m.Cell(tr('')), header_flag=True) row.add(m.Cell(tr('Name')), header_flag=True) row.add(m.Cell(tr('Affected')), header_flag=True) row.add(m.Cell(tr('Displacement rate')), header_flag=True) row.add(m.Cell(tr('Default values')), header_flag=True) row.add(m.Cell(tr('Default min')), header_flag=True) row.add(m.Cell(tr('Default max')), header_flag=True) table.add(row) return table
def impact_summary(self): """The impact summary as per category. :returns: The impact summary. :rtype: safe.messaging.Message """ affect_types = self._impact_breakdown message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None row = m.Row() row.add(m.Cell('', header=True)) # intentionally empty top left cell row.add(m.Cell('Buildings affected', header=True)) for (category, building_breakdown) in self.affected_buildings.items(): total_affected = [0] * len(affect_types) for affected_breakdown in building_breakdown.values(): for affect_type, number_affected in affected_breakdown.items(): count = affect_types.index(affect_type) total_affected[count] += number_affected row = m.Row() row.add(m.Cell(tr(category), header=True)) for affected in total_affected: row.add(m.Cell(format_int(affected), align='right')) table.add(row) if len(self._affected_categories) > 1: row = m.Row() row.add(m.Cell(tr('Affected buildings'), header=True)) row.add( m.Cell(format_int(self.total_affected_buildings), align='right')) table.add(row) # Only show not affected building row if the IF does not use custom # affected categories if self._affected_categories == self.affected_buildings.keys(): row = m.Row() row.add(m.Cell(tr('Not affected buildings'), header=True)) row.add( m.Cell(format_int(self.total_unaffected_buildings), align='right')) table.add(row) row = m.Row() row.add(m.Cell(tr('Total'), header=True)) row.add(m.Cell(format_int(self.total_buildings), align='right')) table.add(row) message.add(table) return message
def _tabulate_zero_impact(self): thresholds = self.parameters['thresholds'].value message = m.Message() table = m.Table(style_class='table table-condensed table-striped') row = m.Row() label = m.ImportantText( tr('People in %.1f m of water') % thresholds[-1]) content = '%s' % format_int(self.total_evacuated) row.add(m.Cell(label)) row.add(m.Cell(content)) table.add(row) table.caption = self.question message.add(table) message = message.to_html(suppress_newlines=True) return message
def format_impact_table(self): """Impact detailed report. :returns: The detailed report. :rtype: safe.messaging.Message """ message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None # Table header row = m.Row() attributes = self.impact_table['attributes'] # Bold and align left the 1st one. row.add(m.Cell(attributes[0], header=True, align='left')) for attribute in attributes[1:]: # Bold and align right. row.add(m.Cell(attribute, header=True, align='right')) table.add(row) # Fields for record in self.impact_table['fields'][:-1]: row = m.Row() # Bold and align left the 1st one. row.add(m.Cell(record[0], header=True, align='left')) for content in record[1:-1]: # Align right. row.add(m.Cell(format_int(int(content)), align='right')) # Bold and align right the last one. row.add( m.Cell(format_int(int(record[-1])), header=True, align='right')) table.add(row) # Total Row row = m.Row() last_row = self.impact_table['fields'][-1] # Bold and align left the 1st one. row.add(m.Cell(last_row[0], header=True, align='left')) for content in last_row[1:]: # Bold and align right. row.add( m.Cell(format_int(int(content)), header=True, align='right')) table.add(row) message.add(table) return message
def impact_summary(self): """The impact summary as per category :returns: The impact summary. :rtype: safe.messaging.Message """ message = m.Message(style_class='container') table = m.Table(style_class='table table-condensed table-striped') table.caption = None row = m.Row() row.add( m.Cell(tr('Population needing evacuation <sup>1</sup>'), header=True)) evacuated = format_int(population_rounding(self.total_evacuated)) row.add(m.Cell(evacuated, align='right')) table.add(row) if len(self.impact_category_ordering): table.add(m.Row()) # add a blank line row = m.Row() row.add(m.Cell(tr('Total affected population'), header=True)) affected = format_int( population_rounding(self.total_affected_population)) row.add(m.Cell(affected, align='right')) table.add(row) for category in self.impact_category_ordering: population_in_category = self.lookup_category(category) population_in_category = format_int( population_rounding(population_in_category)) row = m.Row() row.add(m.Cell(tr(category), header=True)) row.add(m.Cell(population_in_category, align='right')) table.add(row) table.add(m.Row()) # add a blank line row = m.Row() unaffected = format_int(population_rounding( self.unaffected_population)) row.add(m.Cell(tr('Unaffected population'), header=True)) row.add(m.Cell(unaffected, align='right')) table.add(row) message.add(table) return message
def _make_defaults_hazard_table(): """Build headers for a table related to hazard classes. :return: A table with headers. :rtype: m.Table """ table = m.Table(style_class='table table-condensed table-striped') row = m.Row() # first row is for colour - we dont use a header here as some tables # do not have colour... row.add(m.Cell(tr(''), header=True)) row.add(m.Cell(tr('Name'), header=True)) row.add(m.Cell(tr('Affected'), header=True)) row.add(m.Cell(tr('Fatality rate'), header=True)) row.add(m.Cell(tr('Displacement rate'), header=True)) row.add(m.Cell(tr('Default values'), header=True)) row.add(m.Cell(tr('Default min'), header=True)) row.add(m.Cell(tr('Default max'), header=True)) table.add(row) return table
def no_population_impact_message(question): """Create a message that indicates that no population were impacted. :param question: A question sentence that will be used as the table caption. :type question: basestring :returns: An html document containing a nice message saying nobody was impacted. :rtype: basestring """ message = m.Message() table = m.Table(style_class='table table-condensed table-striped') row = m.Row() label = m.ImportantText(tr('People impacted')) content = 0 row.add(m.Cell(label)) row.add(m.Cell(content)) table.add(row) table.caption = question message.add(table) message = message.to_html(suppress_newlines=True) return message
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() translated_name = tr(name) if name == 'building type': table.caption = tr('Closed buildings') elif name == 'road type': table.caption = tr('Closed roads') elif name == 'people': table.caption = 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 = tr( 'Detailed %s report ' '(for people needing evacuation)') % translated_name else: table.caption = tr('Detailed %s report ' '(affected people)') % translated_name if processor in ['Gender', 'Age']: table.caption = tr('Detailed %s report ' '(affected people)') % translated_name 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( tr('Could not compute the %s report.') % translated_name)) 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 = tr('Unnamed Area %s' % null_index) null_index += 1 if name == 'road type': # We add the unit 'meter' as we are counting roads. # proper format for i186 zone_name = tr('%(zone_name)s (m)') % { 'zone_name': tr(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 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 and rows containing only 0 or "%s" values are ' 'excluded from the tables.' % self.aggregator.get_default_keyword('NO_DATA'))) message.add(m.Paragraph(caption, style_class='caption')) return message
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 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 _value_maps_row(value_maps_keyword): """Helper to make a message row from a value maps. Expected keywords: 'value_maps': { 'structure': { 'ina_structure_flood_hazard_classification': { 'classes': { 'low': [1, 2, 3], 'medium': [4], 'high': [5, 6] }, 'active': True }, 'ina_structure_flood_hazard_4_class_classification': { 'classes': { 'low': [1], 'medium': [2, 3, 4], 'high': [5, 6, 7], 'very_high': [8] }, 'active': False } }, 'population': { 'ina_population_flood_hazard_classification': { 'classes': { 'low': [1], 'medium': [2, 3], 'high': [4, 5, 6] }, 'active': False }, 'ina_population_flood_hazard_4_class_classification': { 'classes': { 'low': [1, 2], 'medium': [3, 4], 'high': [4, 5, 6], 'very_high': [6, 7, 8] }, 'active': True } }, } :param value_maps_keyword: Value of the keyword to be rendered. This must be a string representation of a dict, or a dict. :type value_maps_keyword: basestring, dict :returns: A table to be added into a cell in the keywords table. :rtype: safe.messaging.items.table """ if isinstance(value_maps_keyword, basestring): value_maps_keyword = literal_eval(value_maps_keyword) table = m.Table(style_class='table table-condensed table-striped') i = 0 for exposure_key, classifications in value_maps_keyword.items(): i += 1 exposure = definition(exposure_key) exposure_row = m.Row() exposure_row.add(m.Cell(m.ImportantText(tr('Exposure')))) exposure_row.add(m.Cell(exposure['name'])) table.add(exposure_row) classification_row = m.Row() classification_row.add( m.Cell(m.ImportantText(tr('Classification')))) active_classification = None for classification, value in classifications.items(): if value.get('active'): active_classification = definition(classification) if active_classification.get('name'): classification_row.add( m.Cell(active_classification['name'])) break if not active_classification: classification_row.add(m.Cell(tr('No classifications set.'))) continue table.add(classification_row) header = m.Row() header.add(m.Cell(tr('Class name'))) header.add(m.Cell(tr('Values'))) table.add(header) classes = active_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 = classifications[ active_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) if i < len(value_maps_keyword): # Empty row empty_row = m.Row() empty_row.add(m.Cell('')) empty_row.add(m.Cell('')) table.add(empty_row) return table
def _threshold_to_row(thresholds_keyword): """Helper to make a message row from a threshold We are expecting something like this: { 'thresholds': { 'structure': { 'ina_structure_flood_hazard_classification': { 'classes': { 'low': [1, 2], 'medium': [3, 4], 'high': [5, 6] }, 'active': True }, 'ina_structure_flood_hazard_4_class_classification': { 'classes': { 'low': [1, 2], 'medium': [3, 4], 'high': [5, 6], 'very_high': [7, 8] }, 'active': False } }, 'population': { 'ina_population_flood_hazard_classification': { 'classes': { 'low': [1, 2.5], 'medium': [2.5, 4.5], 'high': [4.5, 6] }, 'active': False }, 'ina_population_flood_hazard_4_class_classification': { 'classes': { 'low': [1, 2.5], 'medium': [2.5, 4], 'high': [4, 6], 'very_high': [6, 8] }, 'active': True } }, }, Each value is a list with exactly two element [a, b], where a <= b. :param thresholds_keyword: Value of the keyword to be rendered. This must be a string representation of a dict, or a dict. :type thresholds_keyword: basestring, dict :returns: A table to be added into a cell in the keywords table. :rtype: safe.messaging.items.table """ if isinstance(thresholds_keyword, basestring): thresholds_keyword = literal_eval(thresholds_keyword) for k, v in thresholds_keyword.items(): # If the v is not dictionary, it should be the old value maps. # To handle thresholds in the Impact Function. if not isinstance(v, dict): table = m.Table(style_class='table table-condensed') for key, value in thresholds_keyword.items(): row = m.Row() name = definition(key)['name'] if definition(key) else key row.add(m.Cell(m.ImportantText(name))) pretty_value = tr('%s to %s' % (value[0], value[1])) row.add(m.Cell(pretty_value)) table.add(row) return table table = m.Table(style_class='table table-condensed table-striped') i = 0 for exposure_key, classifications in thresholds_keyword.items(): i += 1 exposure = definition(exposure_key) exposure_row = m.Row() exposure_row.add(m.Cell(m.ImportantText('Exposure'))) exposure_row.add(m.Cell(m.Text(exposure['name']))) exposure_row.add(m.Cell('')) table.add(exposure_row) active_classification = None classification_row = m.Row() classification_row.add(m.Cell(m.ImportantText('Classification'))) for classification, value in classifications.items(): if value.get('active'): active_classification = definition(classification) classification_row.add( m.Cell(active_classification['name'])) classification_row.add(m.Cell('')) break if not active_classification: classification_row.add(m.Cell(tr('No classifications set.'))) classification_row.add(m.Cell('')) continue table.add(classification_row) 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 = active_classification.get('classes') # Sort by value, put the lowest first classes = sorted(classes, key=lambda k: k['value']) for the_class in classes: threshold = classifications[ active_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) if i < len(thresholds_keyword): # Empty row empty_row = m.Row() empty_row.add(m.Cell('')) empty_row.add(m.Cell('')) table.add(empty_row) return table
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', 'classification', 'exposure_unit', 'continuous_hazard_unit', 'value_map', # attribute values 'thresholds', # attribute values 'value_maps', # attribute values 'inasafe_fields', 'inasafe_default_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(tr('Layer keywords:'), **styles.BLUE_LEVEL_4_STYLE)) report.add( m.Text( 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 = tr('Reference system') value = self.layer.crs().authid() row = self._keyword_to_row(keyword, value) table.add(row) # Next the data source keyword = 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 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() 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( '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 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 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 definitions.concepts.iteritems(): 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 = 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( definitions.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.definitions.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 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) ## # 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 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')) # Only show not affected building row if the IF does not use custom # affected categories if self._affected_categories == self.affected_buildings.keys(): row.add(m.Cell(tr('Not Affected'), 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) # Only show not affected building row if the IF does not use custom # affected categories if self._affected_categories == self.affected_buildings.keys(): # And one extra total for the unaffected column impact_totals.append(0) # And one extra total for the cumulative 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)) # Only show not affected building row if the IF does not use custom # affected categories if self._affected_categories == self.affected_buildings.keys(): # Add not affected subtotals impact_subtotals.append(self.buildings[building_type] - sum(impact_subtotals)) # 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 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: safe_extras.parameters.message.Message :param table_of_contents: Table of contents that the headings should be included in. :type message: safe_extras.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 type(note) is 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( '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(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: if type(note) is 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 type(note) is 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(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: if type(note) is 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(u'', attributes='style="background: %s;"' % colour)) else: row.add(m.Cell(u' ')) 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') > 0: # we want to show the rate as a scientific notation rate = html_scientific_notation_rate( inasafe_class['fatality_rate']) rate = u'%s%%' % rate row.add(m.Cell(rate)) elif inasafe_class.get('fatality_rate') == 0: row.add(m.Cell('0%')) else: row.add(m.Cell(tr('unspecified'))) if is_hazard: if 'displacement_rate' in inasafe_class: rate = inasafe_class['displacement_rate'] * 100 rate = u'%.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 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'))) 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 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=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(u'', 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